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

@daloyjs/core

v1.3.1

Published

DaloyJS is a runtime-portable, contract-first TypeScript web framework with built-in OpenAPI (Hey API), typed client generation, large-scale maintainability, and security-first defaults. Hono-grade portability, Elysia-grade DX, FastAPI-grade docs, Fastify

Readme

DaloyJS

License: MIT CI CodeQL Publish Zizmor GitHub last commit npm version JSR OpenSSF Best Practices OpenSSF Scorecard Security Responsible
Disclosure

The first TypeScript REST API framework built for secure AI-assisted services. DaloyJS combines runtime portability, contract-first OpenAPI, generated typed clients, zero-dependency runtime guardrails, and supply-chain-aware project defaults in one framework.

One-line API docs. new App({ openapi: { info: ... }, docs: true }) auto-mounts GET /docs (Scalar), GET /openapi.json, and GET /openapi.yaml — the same DX as FastAPI, without leaving TypeScript.

DaloyJS is maintained in the GitHub organization at https://github.com/daloyjs; the canonical framework repository is https://github.com/daloyjs/daloy.

Acknowledgements

We are grateful to the following companies for supporting DaloyJS with free access to their tools and services.

           


Built for the vibe-coding era

Most backend code is now written with AI. Non-developers describe an app and ship whatever the model produces; engineers let agents install dependencies, run tests, and open PRs. The code works on the happy path and gets deployed within the hour — usually with no body limits, skippable input validation, admin routes left mounted on the public app, and an outbound fetch that will happily call cloud-metadata endpoints. At the same time, the dependency tree itself has become the attack surface: self-replicating npm worms, malicious postinstall scripts, CI cache-poisoning, and slopsquatting — where an attacker pre-registers a package name an AI assistant is likely to hallucinate.

DaloyJS is built for exactly this moment, from two directions at once:

  • A secure-by-default runtime. Body limits, prototype-pollution-safe JSON, path-traversal rejection, request timeouts, header-injection guards, real 405s, and RFC 9457 problem+json with prod-mode redaction are on in the constructor — the dangerous things are off when nobody remembered to turn them off. The app also refuses to boot on unsafe configuration (wildcard CORS with credentials, weak session secrets, a state-changing session route with no CSRF protection, unconfigured X-Forwarded-* in production).
  • A hardened supply chain. @daloyjs/core ships zero runtime dependencies, is published with npm provenance and CycloneDX + SPDX SBOMs, and the pnpm posture (ignore-scripts, a 24-hour release-age cooldown, source-verified lockfiles) plus the CI verify:* gates shrink the blast radius of the campaigns making headlines.

The point is that none of this costs you developer experience or portability: you keep contract-first DX in the league of ts-rest, Elysia, and FastAPI, and Hono-grade portability across Node, Bun, Deno, Workers, Vercel, Fastly, and Lambda. The secure path is simply the path of least resistance. See Vibe Coding Security: what DaloyJS already blocks and the security docs.


DaloyJS exists to be the framework you'd build if you took the best ideas from each modern stack:

| You want | Today's best-of | What DaloyJS gives you | | ---------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Best OpenAPI ergonomics | FastAPI | OpenAPI 3.1 from a single route definition; docs: true mounts /docs and /openapi.json. | | Best Vercel / serverless / edge fit | Hono | Web-standard Request → Response core with adapters for Node, Bun, Deno, Cloudflare, Vercel, Fastly, and Lambda. | | Mature Swagger / docs / ops in Node | Fastify | Encapsulated plugins, structured logger, graceful shutdown, request ids, and lifecycle hooks — all first-party. | | Modern TS-first DX, Bun acceptable | Elysia | End-to-end typed handlers, typed context, and a typed in-process client — no codegen step required. | | Best-in-class typed client codegen for any consumer | Hey API | One pnpm gen command emits a fully-typed fetch SDK from your live OpenAPI spec. | | Contract-first typed client, no codegen | ts-rest | Your route definition is the contract: an in-process typed client with zero codegen, plus OpenAPI 3.1 + a Hey API SDK for consumers that can't import your types. | | Opinionated DI / module architecture for large teams | NestJS | Plugin encapsulation, register() prefixes, and defineDependency() typed-DI with per-request dedup — no decorators. | | Minimalist async middleware cascade | Koa | Koa-style Context on a web-standard core, with validation, OpenAPI, errors, and security headers in-box. | | Services + real-time API framework | FeathersJS | First-party app.ws() with CSWSH refuse-to-boot guards, plus SSE / NDJSON streaming and raw Response passthrough (return a Vercel AI SDK stream straight from a handler) over explicit OpenAPI routes. | | Battle-tested Node middleware compatibility | Express v5 | Regex-free trie router, schema-validated routes, RFC 9457 problem+json, and refuse-to-boot guards on every runtime. | | Portable supply-chain hardening for the apps you build | pnpm defaults + a zero-runtime-dep core | Hardened .npmrc, source-verified lockfiles, zero runtime deps, CycloneDX + SPDX SBOM, and npm provenance attestations. |

framework test suite passing · ≥90% line + function coverage / ≥92% branch coverage · typechecks on TypeScript 7 with `strict: true`
runs on Node, Bun, Deno, Cloudflare, Vercel
~12.3M static-route ops/sec · ~1.5M dynamic-route ops/sec on M-class CPU

Why a new framework?

Each existing stack is excellent at one thing and forces tradeoffs everywhere else:

  • Hono is small and portable but OpenAPI is a plugin afterthought.
  • Elysia has gorgeous typing but pulls you toward Bun.
  • Fastify has the best Node ops story but is Node-only and validation/types/docs are not unified.
  • FastAPI has the best docs ergonomics — but it's Python.
  • Hey API gives you the best typed client — but you still need a server that produces a clean spec.
  • ts-rest gives lovely end-to-end types from a shared contract — but it rides on top of another server (Express/Fastify/Nest/Next), its safety is TypeScript-only, and OpenAPI and security are bring-your-own.
  • npm leaves supply-chain protection up to you.

DaloyJS combines the wins:

  1. Explicit contracts, minimal ceremony. One app.get(path, contract, handler) (or the matching shorthand, or app.route({...}) for reusable contracts) is the source of truth for validation, types, OpenAPI, the typed client, and contract tests.
  2. One source of truth for validation, typing, and docs via Standard Schema — Zod 4 / Valibot / ArkType / TypeBox all work, no lock-in.
  3. Portable core, optional runtime optimizations — the only thing the core knows is Request → Response. Adapters live at the edge.
  4. Security guardrails by default — bad defaults are bugs. The core enforces body limits, prototype-pollution-safe JSON, path-traversal rejection, request timeouts, content-type checks, and RFC 9457 problem+json errors with prod-mode redaction. First-party middleware covers Helmet-grade headers, CORS, CSRF, rate limits, request ids, and signed-cookie sessions.
  5. Tooling and inspectability over magic. app.introspect() is a public API; contract-test runner is built in.
  6. Optimize for large-team maintenance, not only solo-dev speed. Encapsulated plugins, decorators, request ids, structured logger.

Get started

For a new DaloyJS project, the recommended path is the official scaffolder:

pnpm create daloy@latest my-api
# or
npm  create daloy@latest my-api

# add GitHub Actions + governance files for a company repo
pnpm create daloy@latest my-api --with-ci --code-owner @acme/security

create-daloy gives you a working project structure, runtime template selection, docs routes, OpenAPI wiring, production-oriented defaults, and an optional hardened GitHub security bundle without copying code out of the README.

See Scaffold a project for templates and flags.

Install core manually

DaloyJS is distributed via pnpm for supply-chain hygiene and backed by a hardened release pipeline — strict isolation, content-addressable store, deterministic lockfile, no phantom dependencies, SHA-pinned CI actions, npm staged publishing, and provenance attestations.

pnpm add @daloyjs/core zod@^4

Zod 4 is the recommended validator for new DaloyJS apps because it is modern, smaller, and Standard-Schema-compatible. DaloyJS still accepts any Standard Schema validator, so teams can use Valibot, ArkType, TypeBox, or another compatible schema library when that better fits their stack.

The repo ships an .npmrc with hardened defaults:

ignore-scripts=true
minimum-release-age=1440
strict-peer-dependencies=true
prefer-frozen-lockfile=true
verify-store-integrity=true
provenance=true

These defaults block transitive lifecycle scripts, wait 24 hours before resolving freshly published versions, verify the pnpm store, and require provenance on publish. The few dependencies that truly need install-time builds are allowlisted in pnpm-workspace.yaml under allowBuilds (currently esbuild only), and CI runs pnpm verify:lockfile to reject git dependency sources and non-registry tarball URLs in pnpm-lock.yaml. The same defaults blunt slopsquatting — the supply-chain attack where an AI coding assistant hallucinates a package name (request-promise-native2, @types/fastify-helmet, etc.) and an attacker registers it on npm with a malicious payload. minimum-release-age=1440 refuses to install anything published in the last 24 hours (the typical detect-and-unpublish window), ignore-scripts=true suppresses lifecycle payloads, blockExoticSubdeps: true and pnpm verify:lockfile reject non-registry sources, pnpm verify:known-dep-names (scripts/verify-known-dep-names.ts) refuses any top-level dep name across the workspace that is not on an explicit allowlist (so pnpm add <hallucinated-name> cannot land in any package.json without a one-line diff that forces a name-review checkpoint), and @daloyjs/core's zero-runtime-dep posture means a hallucinated dep cannot transitively land in the published tarball. See SECURITY.md § Slopsquatting for the full mapping.

Run pnpm audit --prod regularly (or pnpm run audit in this repo) — and pnpm install --frozen-lockfile --ignore-scripts in CI.


SBOM + release automation

Daloy ships a CycloneDX 1.5 + SPDX 2.3 SBOM for both @daloyjs/core and create-daloy.

If you want to run the SBOM flow locally, the two commands are:

pnpm gen:sbom
pnpm verify:sbom

pnpm gen:sbom regenerates the publishable SBOM files for both packages. pnpm verify:sbom checks that the generated SBOMs match the current package manifests and that @daloyjs/core still declares zero runtime dependencies.

You do not need to remember to run those commands manually for CI or publish:

That means a release will fail before publish if the SBOMs are missing, stale, or inconsistent with package.json. The workflow stages releases on npm instead of making them installable immediately, so a maintainer still has to review the stage ID and approve it with npm MFA.

For maintainers, the safe rule is: use one publish path per version. Either publish through the protected GitHub release workflow, or publish locally for an exceptional case, but do not do both for the same version.


Hello world

import { z } from "zod";
import { App, NotFoundError, secureHeaders, rateLimit, requestId } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";

const app = new App({ bodyLimitBytes: 1024 * 1024, requestTimeoutMs: 5_000 });

// First-party security middleware — usually three plugins in other frameworks.
app.use(requestId());
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 120 }));

app.get(
  "/books/:id",
  {
    tags: ["Books"],
    request: { params: z.object({ id: z.string() }) },
    responses: {
      200: {
        description: "Found",
        body: z.object({ id: z.string(), title: z.string() }),
      },
      404: { description: "Not found" },
    },
  },
  async ({ params }) => ({
    status: 200,
    body: { id: params.id, title: `Book ${params.id}` },
  })
);

serve(app, { port: 3000 });

For a concise route that keeps the full contract, use a method shorthand. The method and path produce a stable operation id (getRoot here), while the response schema preserves validation, OpenAPI, and data-exposure protection:

const app = new App().get(
  "/",
  { responses: { 200: { body: z.object({ hello: z.string() }) } } },
  () => ({ status: 200, body: { hello: "world" } })
);

There is intentionally no app.get(path, handler) form. If a streaming or proxy route genuinely needs to return a raw Response, declare a contract and set acknowledgeNoResponseBodySchema: true; otherwise DaloyJS fails closed rather than silently bypassing response-body validation. The same rule applies when preBody or beforeHandle short-circuits with a successful raw response; ordinary 4xx/5xx hook denials remain secure by default without an opt-out.

Add request schemas and other contract options as the endpoint grows:

app.post(
  "/books",
  {
    request: { body: z.object({ title: z.string().min(1) }) },
    responses: {
      201: {
        body: z.object({ id: z.string(), title: z.string() }),
      },
    },
  },
  ({ body }) => ({ status: 201, body: { id: "1", title: body.title } })
);

OpenAPI + Hey API typed client

DaloyJS produces a clean OpenAPI 3.1 document with zero plugins, then @hey-api/openapi-ts turns that into a fully typed TypeScript SDK that any consumer (your web app, mobile RN bundle, internal CLI) can drop in.

pnpm gen          # writes generated/openapi.json + generated/client/

That single command runs the two scripts:

// package.json
"scripts": {
  "gen:openapi": "node scripts/dump-openapi.ts",
  "gen:client":  "openapi-ts",
  "gen":         "pnpm gen:openapi && pnpm gen:client"
}

openapi-ts.config.ts:

import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
  input: "./generated/openapi.json",
  output: { path: "./generated/client", postProcess: ["prettier"] },
  plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
});

For TypeScript consumers in the same monorepo you can skip codegen entirely and use the in-process typed client:

import { createInProcessClient } from "@daloyjs/core/client";
const client = createInProcessClient(app);
const r = await client.getBookById({ params: { id: "1" } });
//    ^? { status: 200; body: { id: string; title: string } } | { status: 404; ... }

For multi-file applications, export each contract with defineRoute() and compose the literal tuple with app.registerRoutes([...]). This retains every operation across module boundaries:

import { App, defineRoute } from "@daloyjs/core";
import { createInProcessClient } from "@daloyjs/core/client";
import { listBooksRoute } from "./routes/list-books.js";
import { getBookRoute } from "./routes/get-book.js";

const app = new App().registerRoutes([listBooksRoute, getBookRoute] as const);
const client = createInProcessClient(app); // typed, no socket or port

Chained route() calls remain supported. Avoid widening a composed app back to a bare App annotation, because that deliberately discards its route tuple. Callback-style group() and plugin register() provide runtime encapsulation, but TypeScript cannot widen the already-created parent variable from inside a callback. Export route tuples and compose them with registerRoutes() whenever the no-codegen client must include those module routes.


Built-in docs UI (Scalar / Swagger UI / Redoc)

FastAPI-style. One line on the App constructor mounts GET /docs, GET /openapi.json, and GET /openapi.yaml for you, with a strict CSP and CDN-hosted assets:

import { App } from "@daloyjs/core";

const app = new App({
  openapi: { info: { title: "My API", version: "1.0.0" } },
  docs: true, // mounts GET /docs (Scalar), GET /openapi.json, GET /openapi.yaml
});

Use docs: "auto" to mount only when production: false, or the object form for full control:

new App({
  openapi: { info: { title: "My API", version: "1.0.0" } },
  docs: {
    ui: "scalar", // "scalar" (default) | "swagger" | "redoc"
    path: "/reference",
    openapiPath: "/spec.json",
    openapiYamlPath: "/spec.yaml", // or `false` to disable the YAML route
    scalar: {
      theme: "kepler",
      customCss: ":root { --scalar-color-accent: #2563eb; }",
      hideTestRequestButton: true,
    },
    swagger: {
      docExpansion: "none",
      displayRequestDuration: true,
    },
    auth: {
      loginUrl: "/login", // or Auth0 / Entra ID / Okta / Clerk / Better Auth
      label: "Sign in",
      target: "popup",
    },
    tags: ["Docs"],
  },
});

Switch UIs with one word. Scalar and Swagger UI include developer request consoles for authenticated endpoints: Scalar selects the first configured OpenAPI security scheme by default, and Swagger UI keeps values from the Authorize dialog across reloads. ui: "redoc" renders Redoc instead, and its options are forwarded to Redoc.init via docs.redoc; Redoc displays security requirements but is a read-only reference UI, not a Try It console:

new App({
  openapi: { info: { title: "My API", version: "1.0.0" } },
  docs: {
    ui: "redoc",
    redoc: { hideDownloadButtons: true, sortPropsAlphabetically: true },
  },
});

The scalar option is forwarded to Scalar's HTML API as JSON configuration, with Daloy keeping the live openapiPath as the source. Use it for themes, custom CSS, layout, auth defaults, and client visibility without copying the HTML helper. The swagger option is forwarded to SwaggerUIBundle with Daloy owning url / dom_id. Redoc spins up a blob: Web Worker for search, so the auto-mounted /docs page widens its CSP with worker-src 'self' blob: for ui: "redoc" only — Scalar and Swagger UI keep the tighter default. Add docs.auth when you want all three docs UIs to expose the same visible authorization launcher; point it at a local login route or any OAuth2/OIDC provider entry point.

Prefer to mount manually? Import the helpers directly:

import { swaggerUiHtml, scalarHtml, redocHtml, htmlResponse } from "@daloyjs/core/docs";
import { generateOpenAPI } from "@daloyjs/core/openapi";

The UI is always contract-accurate — never stale. create-daloy templates opt in with docs: true.

If you omit openapi.info, the portable defaults are DaloyJS API / 0.0.0. Set openapi.info (or the top-level title, version, and description) for real services. The core never reads the host filesystem, so the same docs bundle runs unchanged on Workers, Vercel, Bun, Deno, and Node.

Prefer a factory? createApp(options) is exported as an alias of new App(options).

import { createApp } from "@daloyjs/core";

const app = createApp({ docs: true });

daloy dev — one-command watch mode

daloy dev [entry] delegates to the host runtime's native watch tool, with no extra config:

| Runtime | Spawned command | | ------- | --------------------------------------------------------------- | | Node | node --watch <entry> | | Bun | bun --hot <entry> | | Deno | deno run --watch --allow-net --allow-env --allow-read <entry> |

Entry defaults to src/index.ts, src/main.ts, src/server.ts, or src/app.ts. Node.js (>= 22.18) runs TypeScript entries natively via built-in type stripping — no loader needed. Projects that rely on non-erasable syntax (enums, runtime namespaces, parameter properties) or extensionless relative imports can keep using a loader directly, e.g. node --import tsx --watch <entry> (and daloy inspect falls back to tsx automatically when the native load fails and tsx is installed).

Pass --runtime <node|bun|deno> to override runtime detection. This is required when running daloy dev from a package.json script on Bun or Deno, because the CLI binary's #!/usr/bin/env node shebang otherwise forces Node detection. The bun-basic template ships "dev": "daloy dev --runtime bun" for this reason.


Security guardrails

Some protections are enforced by the App core whenever the relevant request path is used. Others are first-party middleware so applications can choose the right CORS policy, rate-limit key, CSP, session secret, or CSRF rollout for their deployment.

| Threat | Built-in behavior | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Body-size DoS | Core-enforced streamed read with a hard cap (default 1 MiB); Content-Length checked first. | | Prototype pollution | Core JSON parser strips __proto__ / constructor / prototype via reviver. | | Header / response splitting | Core header sanitizers reject CRLF + NUL. | | Path traversal | Core router rejects .. segments and // before walking. | | Slow-loris / hung handlers | Core requestTimeoutMs aborts handlers (default 30 s); Node adapter sets requestTimeout + headersTimeout + maxHeaderSize. | | HTTP/2 Bomb / header-count flood | Core maxHeaderCount rejects requests with more than 100 header fields (431) before routing; Node adapter sets server.maxHeadersCount. See SECURITY.md for the upstream HTTP/2 mitigations. | | MIME sniffing | First-party secureHeaders() sets X-Content-Type-Options: nosniff; scaffolded apps enable it. | | Clickjacking | First-party secureHeaders() sets X-Frame-Options: DENY + CSP frame-ancestors 'none'; scaffolded apps enable it. | | XSS via injected scripts | First-party secureHeaders() provides a strict CSP default-src 'self' baseline; the directives-object form supports per-request nonces and Trusted Types (require-trusted-types-for 'script'). | | Cross-origin leakage | First-party secureHeaders() sets cross-origin-opener-policy + cross-origin-resource-policy to same-origin; scaffolded apps enable it. | | CSRF | First-party csrf() ships two strategies: double-submit cookie (default) and Fetch-Metadata (Sec-Fetch-Site-based, tokenless); both with timing-safe verification. | | Information disclosure (5xx) | Production mode strips detail from 5xx problem+json automatically. | | Credential timing attacks | First-party timingSafeEqual() helper for tokens & signatures. | | Brute-force / scraping | First-party rateLimit() with token-bucket + Retry-After; Node/Bun/Deno scaffolded apps enable it. | | Method confusion | Real 405 with Allow header, not a misleading 404. | | CORS misconfig | First-party cors() requires an explicit allowlist and throws for * with credentials. | | Request correlation | First-party requestId() uses cryptographic ids; scaffolded apps enable it. | | Supply chain (portable) | pnpm scaffolds keep ignore-scripts=true, minimum-release-age=1440, verified store, reproducible lockfile, and pnpm verify:lockfile source verification; every app also installs a zero-runtime-dependency @daloyjs/core published with CycloneDX + SPDX SBOM and npm provenance you can verify on install — regardless of where you host your repo. |

Portable vs. GitHub-only. The runtime protections and the published @daloyjs/core SBOM/provenance travel with every app you scaffold, no matter which CI host you use — GitLab, Bitbucket, Azure DevOps, Jenkins, on-prem, or laptop. The strongest install-time bundle is available when you choose pnpm, because minimum-release-age, blockExoticSubdeps, and the workspace gates are pnpm features. The @daloyjs/core release pipeline itself is separately hardened on GitHub Actions — no pull_request_target, no Actions cache, top-level permissions: {}, step-security/harden-runner, a protected release.yml, npm trusted publishing with --provenance, CodeQL + Opengrep dual SAST, OpenSSF Scorecard, zizmor, Dependabot, and CODEOWNERS — and create-daloy --with-ci ships the app-safe parts as an optional GitHub Actions bundle for teams on GitHub. See SECURITY.md and the supply-chain security docs.


Authentication, OAuth2 & OpenID Connect

DaloyJS is a resource server (and a toolkit for building a relying party), not an identity provider. Like Hono, Express, Fastify, or ASP.NET Core, it verifies and enforces tokens on each request — it does not ship a login UI, a user database, or an OAuth2 authorization server. It is not an "IdentityServer": it cannot, on its own, do what Duende IdentityServer, Keycloak, or Auth0 do (run login pages, manage clients/consent, mint tokens).

To add login you bring an OpenID Connect provider. It does not have to be Auth0/Okta/Clerk specifically — any standards-compliant IdP works, including managed (Auth0, Okta, Clerk, Microsoft Entra ID, AWS Cognito) and self-hosted open source (Keycloak, Zitadel, Ory, Authentik, Logto, SuperTokens, Dex). Don't build your own authorization server — verify tokens from a vetted one.

  • API as a resource server (default): verify JWTs with jwk() against the provider's JWKS (asymmetric-only algorithm allowlist, issuer/audience enforced), then authorize per route with requireScopes().
  • Browser app: use the back-end-for-frontend (BFF) pattern — run the authorization-code + PKCE flow server-side, keep tokens in a session() cookie (never in JavaScript), and protect mutations with csrf().

Read Auth architecture: where DaloyJS fits in OAuth2 & OpenID Connect for the full picture, plus the per-provider guides under /docs/auth.


Performance

$ pnpm bench
static route lookup         25,810,420 ops/sec
dynamic 4-segment lookup     2,105,121 ops/sec
miss                         7,742,635 ops/sec
  • After traversal checks, exact static routes resolve with an allocation-free Map.get fast path — ~26M ops/sec.
  • Dynamic routes walk a trie, O(path-segments) regardless of route count.
  • Body parsing is lazy and only runs when a route declares a body schema.
  • Path normalization and splitting use index/character scans rather than regular expressions.

Cold-start tip (serverless / edge)

For deployments where every millisecond of startup matters (Lambda, Vercel, Cloudflare Workers, Fastly Compute), import App from the deep entry point instead of the barrel:

import { App } from "@daloyjs/core/app"; // ~13 ms faster cold start than "@daloyjs/core"
import { serve } from "@daloyjs/core/node";

@daloyjs/core/app resolves to the same App class with the same secure-by-default constructorsecureHeaders, requestId, body limits, request timeouts, fetchGuard, prototype-pollution guards, problem+json redaction, and every other guardrail are still wired automatically. The deep import only skips loading unrelated peripheral modules (jwk, jwt, multipart, websocket, streaming, compression, subdomains, etc.) that the barrel re-exports for convenience. If you use any of those, import them directly from their own subpaths (@daloyjs/core/jwk, @daloyjs/core/multipart, …) so each one is paid for only when used.

Long-lived Node servers will not notice the difference. This is purely a cold-start optimization for serverless.


Test client + contract tests

const res = await app.request("/books/1");

import { runContractTests } from "@daloyjs/core/contract";
const report = await runContractTests(app);
if (!report.ok) process.exit(1);

The contract runner verifies that declared examples actually match their schemas, flags duplicate/missing operationIds, dead routes, and accidental body schemas on safe methods.

Gate it in CI two ways: daloy inspect --check <entry> exits non-zero on any error-level issue, or assert report.ok inside your test suite. Every create-daloy template ships a contract-gate test (tests/contract.test.ts, tests/contract_test.ts on Deno) wired into its test task, so scaffolded projects fail CI on a broken contract out of the box. For a localhost-only gate that runs before code leaves your machine, each template also ships an opt-in pre-push hook (.githooks/pre-push, enabled with hooks:install which points core.hooksPath at it); it runs daloy inspect --check on every git push and is bypassable with git push --no-verify.


Plugin encapsulation (Fastify-style)

const usersPlugin = {
  name: "users",
  register(app) {
    app.get(
      "/me",
      {
        operationId: "me",
        responses: { 200: { description: "ok" } },
      },
      async () => ({ status: 200, body: { user: "alice" } })
    );
  },
};
app.register(usersPlugin, { prefix: "/users", tags: ["Users"] });
await app.ready();

Multi-runtime

import { serve } from "@daloyjs/core/node"; // Node (Heroku, Railway, Render, Fly.io, any PaaS)
import { serve } from "@daloyjs/core/bun"; // Bun
import { serve } from "@daloyjs/core/deno"; // Deno
import { toFetchHandler } from "@daloyjs/core/cloudflare"; // Cloudflare Workers
import {
  toFetchHandler as toVercelFetchHandler,
  toRouteHandlers,
  toWebHandler,
} from "@daloyjs/core/vercel"; // Vercel Node / Edge / Next.js / Netlify Edge
import { installFastlyListener } from "@daloyjs/core/fastly"; // Fastly Compute
import { toLambdaHandler } from "@daloyjs/core/lambda"; // AWS Lambda / Netlify Functions / Lambda Function URLs

The core only ever sees Request → Response. Adapters live at the edge.


References


Status

DaloyJS is at 1.1.1 — the public API is frozen and follows SemVer from here: no 1.x minor changes the API, and any deprecation gets at least one minor cycle before removal. @daloyjs/core (npm), create-daloy (npm), and @daloyjs/daloy (JSR) ship together at matching versions.

The release-candidate train that led here was largely adversarial: rc.1 through rc.9 carried remediations from live over-the-wire engagements against realistic multi-tenant apps, and the findings clustered in one place worth naming — composition, not individual modules. A responseCache() mounted ahead of the network-identity gates silently disabled them; the same order left rateLimit() never counting the requests a cache hit or an idempotent replay served; a forwarded-header resolver read the one X-Forwarded-For slot an attacker controls; idempotency() replayed a stored Set-Cookie. Each is fixed, each has a regression test, and several are now refuse-to-boot guards so the unsafe wiring cannot ship quietly. See the CHANGELOG for the full train and boot guards for the orders the framework now refuses.

Security fixes after 1.0.0 follow the published patch SLA (48h critical / 7d high / 30d medium) with a GitHub Security Advisory per confirmed issue, and the 1.x line carries a minimum 5-year security-update support period (see Support lifetime).

Release quality bar. Every release ships with ≥90% line + function coverage (pnpm coverage) and ≥92% branch coverage (pnpm coverage:branches on compiled JS), strict TypeScript, OpenSSF Scorecard, CodeQL + Opengrep dual SAST, zizmor workflow linting, and npm provenance. Coverage was relaxed from a former 100% gate so complex security work isn't blocked chasing throwaway tests for unreachable defensive branches or tsx source-map phantoms; see AGENTS.md for the policy.

Routing, validation, and docs

  • Contract-first routing with Standard Schema validation (Zod 4, Valibot, ArkType, TypeBox) and OpenAPI 3.1 generated from a single source of truth.
  • Live OpenAPI 3.1 spec served as both JSON (GET /openapi.json) and YAML (GET /openapi.yaml) when docs: true, with a choice of Scalar (default), Swagger UI, or Redoc via docs.ui, plus Scalar theming/custom CSS/auth defaults via docs.scalar, Swagger UI options via docs.swagger (including persisted Authorize credentials), Redoc options via docs.redoc, and a provider-neutral docs.auth launcher for local login routes or third-party OAuth2/OIDC providers.
  • Filesystem-free OpenAPI metadata: explicit openapi.info / top-level metadata wins, with portable DaloyJS API / 0.0.0 fallbacks on every runtime.
  • Contract-required get() / post() / put() / patch() / delete() / head() shorthands with deterministic method+path operation IDs and the same validation, OpenAPI, response-exposure protection, and client typing as route(); there is no silent two-argument schema bypass.
  • Multi-file contract composition through defineRoute() + app.registerRoutes([...]), plus a typed createInProcessClient(app) that traverses the real pipeline without a socket.
  • Header/JWT/basic/mTLS authentication runs in the preBody phase before request-body I/O; body-aware WAF, idempotency, signature, and application middleware keep the validated beforeHandle phase.
  • RFC 7231 + RFC 5789 HTTP-method allowlist enforced inside app.route() (WebDAV, TRACE, CONNECT rejected at the framework boundary).
  • AI-friendly route metadata via optional meta: { examples, extensions, summary, description, tags }; examples are validated against your schemas at build time, surfaced as OpenAPI examples + x-daloy-* extensions, and dumped as routes.json / routes.yaml via daloy inspect --ai.
  • Project docs site serves a curated /llms.txt index (Markdown map + .md siblings of every docs page, blog under Optional) so coding agents can load documentation without scraping HTML; see llms.txt docs.
  • Dependency-free MCP Streamable HTTP server helpers at @daloyjs/core/mcp, speaking the stateless MCP 2026-07-28 revision and every earlier one on the same endpoint: createMcpHandler() exposes tools (with outputSchema, annotations, and icons), resources, RFC 6570 resource templates, and prompts (with required-argument enforcement) over JSON-RPC 2.0 and validates Origin against DNS rebinding (with an allowedOrigins allowlist), while mcpRoutes("/mcp", handler) mounts the POST / GET / OPTIONS Daloy routes — with the JSON-RPC envelope schema surfaced in OpenAPI — for a dedicated MCP service with the same auth, rate-limit, body-limit, and timeout middleware as any other app. Modern requests get server/discover, per-request _meta validation, resultType + _meta.serverInfo on every result, ttlMs / cacheScope caching hints (defaulting to no caching and private scope so an authorization-scoped tool list is never shared by a proxy), multi round-trip requests (input_required + client retry, replacing server-initiated elicitation/sampling/roots), and x-mcp-header parameter mirroring — with a missing or disagreeing MCP-Protocol-Version / Mcp-Method / Mcp-Name / Mcp-Param-* header rejected as -32020 so a gateway can never route on one value while the tool executes another. Legacy clients keep the initialize handshake unchanged. Every tools/call argument is validated server-side against the tool's inputSchema (a dependency-free JSON-Schema subset — type/required/properties/additionalProperties/enum/const/bounds; exposed as validateMcpInput()) before the handler runs, rejecting a mismatch with JSON-RPC -32602; the JSON-RPC body is parsed with prototype-pollution-safe safeJsonParse; and an unauthenticated mcpRoutes() endpoint refuses to boot in production unless opted out with mcpRoutes(path, handler, { public: true }).
  • API lifecycle and breaking-change detection: mark routes deprecated or give them a sunset date to emit RFC 8594 Deprecation / Sunset headers and an x-sunset OpenAPI extension, then gate CI with diffOpenAPI() / the daloy diff command, which fail on a breaking change versus the last published spec.
  • In-process test client (app.request()), contract-test runner (gated in CI via daloy inspect --check and shipped as a default test in every create-daloy template), in-process typed client, and Hey API codegen via pnpm gen.

Runtimes and deployment

  • Adapters for Node (Heroku, Railway, Render, Fly.io), Bun, Deno, Cloudflare Workers, Vercel Node / Edge / Next.js / Netlify Edge, Fastly Compute, and AWS Lambda / Netlify Functions / Lambda Function URLs, including backpressure-safe Lambda response streaming via toLambdaStreamHandler().
  • daloy dev watch loop delegates to the host runtime's native watcher (node --watch, bun --hot, or deno run --watch) with a --runtime override for cross-runtime package.json scripts.
  • pnpm create daloy scaffolder with Node, Bun, Deno, Cloudflare Worker, and Vercel templates, plus optional --with-ci GitHub Actions / Dependabot / CODEOWNERS / SECURITY.md hardening. The completion summary surfaces official install links (nodejs.org, pnpm.io, bun.sh) for any runtime or package manager your selections need but that is missing from PATH, and skips a doomed dependency install when the chosen package manager is absent.
  • Container-first templates: HEALTHCHECK to /readyz, STOPSIGNAL SIGTERM, non-root user, tini as PID 1.
  • Generated deploy.yml for container templates signs every pushed GHCR image with Sigstore Cosign (keyless OIDC) and attaches an SPDX SBOM attestation so consumers can cosign verify and cosign verify-attestation --type spdxjson instead of trusting the registry alone.
  • Pretty printStartupBanner() / formatStartupBanner() helpers at @daloyjs/core/banner, used by every starter template (TTY + NO_COLOR / FORCE_COLOR aware, ASCII fallback for dumb terminals).

Core security primitives

  • Body limits, prototype-pollution-safe JSON, path-traversal guard, request timeouts, header injection guards.
  • Request-smuggling defense: duplicate Host, Content-Length, and Transfer-Encoding headers are rejected.
  • Server and X-Powered-By headers stripped by default.
  • Structured-log redaction defaults for authorization, cookie, password, token, and JWT-shaped values.
  • secureHeaders() auto-applied; user-installed instances automatically replace the auto one.
  • Cross-origin state-changing requests rejected with 403 unless a route's cors() policy allows the origin.
  • Production mode strips detail from 5xx problem+json automatically.
  • Real 405 with Allow header instead of a misleading 404.
  • Cache-Control: no-store baked into UnauthorizedError / ForbiddenError / TooManyRequestsError so every first-party auth 401 / 403 / 429 response is uncacheable.

Refuse-to-boot guardrails

The framework refuses to start (or to construct) when configuration is unsafe:

  • Weak session secrets, cors({ origin: "*" }) with credentials, session() + state-changing route without csrf(), and unconfigured forwarded / client-IP headers (X-Forwarded-*, X-Real-IP, and vendor headers CF-Connecting-IP / Fly-Client-IP / True-Client-IP) in production.
  • Shadow auth: a route that declares an auth: requirement (advertised as protected in the OpenAPI security list) but installs no authentication hook to enforce it. Built-in auth middlewares (bearerAuth / basicAuth / jwk / httpSignatureAuth / clientCertAuth) satisfy the guard automatically; mark a custom auth hook (or upstream-gateway-enforced auth) with markAuthHook().
  • Unauthenticated MCP: an mcpRoutes() endpoint with no auth hook — MCP tools are model-controlled and side-effecting. Opt out for a genuinely public server with mcpRoutes(path, handler, { public: true }).
  • secureDefaults: false in production unless acknowledgeInsecureDefaults: true is set, plus a once-per-process error log naming every disabled default.
  • preset: "internal-service" topology preset for service-to-service deployments behind a mesh / sidecar / private network: turns OFF the browser-only guards (auto secureHeaders, corsCrossOriginGuard, csrf boot guard, unconfigured X-Forwarded-* guard) while keeping every input, parser, credential, SSRF, weak-secret, and refuse-to-boot guard ON. Per-knob options still win, the choice is logged at boot under event: "security.preset.applied", and the live posture is auditable via app.getSecurityPosture().
  • createJwtSigner() / createJwtVerifier() refuse alg: "none", accept only an explicit allowlist, refuse HS + JWK combinations, refuse to sign without exp, refuse HS-shaped secrets under 32 bytes (RFC 7518 §3.2), and refuse JWS crit headers at both sign and verify time (RFC 7515 §4.1.11 — no extensions are implemented, so none may be silently ignored). The verifier also accepts an opt-in maxLifetimeSeconds cap that rejects any token whose exp - (iat ?? now) exceeds it (lifetime_exceeded), closing the signer/verifier lifetime asymmetry.
  • Node adapter header-count hardening: because Node's llhttp parser silently truncates headers past server.maxHeadersCount instead of rejecting, the adapter answers 431 for any HTTP request or WebSocket upgrade whose raw header-field count reaches the cap — a truncated flood is indistinguishable from a handshake sitting exactly at it, so it is refused rather than completed with dropped fields (usable default budget: 99 fields).
  • secureHeaders() refuses to construct with frameOptions: false AND no CSP frame-ancestors directive (no clickjacking defense).
  • cors() refuses methods: ['*'] at construction; default allowMethods narrowed to [GET, HEAD, POST] so PUT / PATCH / DELETE become explicit opt-ins.
  • cspReportRoute() refuses non-application/json (415) and refuses maxBodyBytes > 64 KiB at construction. The default production logger sink omits the parsed report body unless logCspReportBodies: true is set explicitly.
  • session() and csrf() refuse cookies that violate the __Secure- prefix policy.
  • Plugin dependencies: string[] refuse-to-boot when a prerequisite is missing; topoSortExtensions() refuses cycles, and refuses two extensions declaring overlapping responseHeaders without a before / after relationship.
  • app.ws() scans the effective hook stack and refuses-at-registration when header-mutating middleware (secureHeaders(), cors(), csrf(), compression()) is present, unless the handler opts in via acknowledgeHeaderMutatingMiddleware: true.

First-party middleware

  • secureHeaders with strict CSP baseline, per-request nonces, Trusted Types (require-trusted-types-for 'script'), frame-ancestors, cross-origin-opener-policy / cross-origin-resource-policy, and reporting endpoints.
  • cors with explicit-allowlist enforcement.
  • csrf with double-submit cookie (default) and Fetch-Metadata (Sec-Fetch-Site-based, tokenless) strategies; timing-safe verification.
  • rateLimit with token-bucket + Retry-After, shared groupId buckets, and a Redis-backed store at @daloyjs/core/rate-limit-redis.
  • loadShedding() event-loop-pressure middleware (auto-503 + Retry-After).
  • loginThrottle() credential-entry preset and rotateSession() privilege-change session rotation.
  • ipRestriction() with CIDR-aware IPv4 / IPv6 allow / deny lists.
  • combine primitives: every, some, except.
  • requestId() with cryptographic ids; trustIncoming: false by default so client-supplied X-Request-ID headers cannot poison logs.
  • bearerAuth() and basicAuth() with per-scheme verify(credentials, ctx) revalidation hooks, typed-context onAuthSuccess callback, and Cache-Control: no-store on every 401 challenge.
  • jwk() asymmetric-only JWKS middleware: refuses HS* at construction, cross-checks kid and JWT-vs-JWK alg, requires https:// JWKS URLs with TTL caching + in-flight-promise dedup, normalizes scope / scp / scopes claims.
  • requireScopes() with RFC-6750 WWW-Authenticate: Bearer challenge and per-request scope aggregation.
  • session() with signed cookies and pluggable stores.
  • idempotency() with Idempotency-Key fingerprinting + byte-for-byte response replay, in-flight 409, 422 on key reuse with a different payload, and a pluggable IdempotencyStore (in-memory default) at @daloyjs/core/idempotency.
  • responseCache() server-side body cache (cache-key + TTL with s-maxage/max-age orchestration, request no-store/no-cache directives, recursion-safe stale-while-revalidate, proactive varyHeaders keying, X-Cache HIT/MISS/STALE marker, pluggable ResponseCacheStore whose in-memory default is bounded on both entry count and retained bytes) at @daloyjs/core/response-cache. Never caches Set-Cookie, private/no-store/no-cache, or Vary: * responses, and strips Age/hop-by-hop/X-Request-Id from stored entries so a hit never replays another request's correlation id. Fail-closed on every principal dimension (CWE-524): the key is the full effective request URI including the authority (RFC 9111 §4), so hostnames never share entries; requests carrying Authorization or Cookie bypass the shared cache unless a principal names the caller (then each gets its own entry) or the header is explicitly declared shareable; a tenant resolved by tenancy() is folded into the key automatically — with a boot guard that refuses to start if the cache is mounted ahead of tenancy(); and the response's own Vary header is honoured as a secondary key (RFC 9111 §4.1), so the Vary: Origin written by cors() and the Vary: Accept-Encoding written by compression() keep one caller's allowed origin — or their gzipped bytes — from being served to the next, with each variant stored separately so they all stay warm. Complements etag()/compression(), which do not cache bodies.
  • paginationQuery() / encodeCursor() / decodeCursor() / buildPageLinks() / buildLinkHeader() cursor-pagination helpers at @daloyjs/core/pagination: opaque base64url cursors (length-capped, prototype-pollution-safe decode → 400 on tamper), RFC 8288 Link header emission with CRLF / header-injection guards, and a Standard Schema that validates cursor/limit and auto-wires both into the OpenAPI spec + typed client via toJSONSchema().
  • app.metrics() + MetricsRegistry / httpMetrics() Prometheus / OpenMetrics exposition at @daloyjs/core/metrics: dependency-free counters / gauges / histograms, RED instrumentation (http_requests_total, http_request_duration_seconds, http_requests_in_flight, route from the matched template) plus unprefixed process gauges (process_resident_memory_bytes), exposition-injection-safe name/label validation, a per-metric cardinality cap (daloy_metrics_series_dropped_total), and an opt-in /metrics route with the same hardened posture as app.healthcheck() (bearer token + portable timingSafeEqual, per-IP rate limit, refuse-to-boot unauthenticated in production). Pull scrape is for long-lived processes; on Workers / Lambda / Vercel use telemetry: true (OTLP push). The repo ships an examples/observability/ Docker Compose stack that starts a pre-configured Prometheus + Grafana pair (with an auto-provisioned RED + heatmap dashboard) against any local app via docker compose -f examples/observability/docker-compose.yml up.
  • otelTracing() OpenTelemetry-compatible distributed tracing at @daloyjs/core/tracing: a dependency-free Hooks bundle that opens one SERVER span per request, attaches HTTP semantic-convention attributes (http.request.method, url.path, server.address / server.port, http.response.status_code, …), records exceptions + escalates 5xx to ERROR, guarantees a single span.end(), and exposes the live span on ctx.state.otelSpan. Bring any tracer matching the small TracingTracer interface (the real @opentelemetry/api SDK on Node, or a custom exporter on Workers/Deno) plus your own propagator via contextFromRequest for traceparent continuation — no OTel SDK is forced into your install. The examples/observability/ stack also runs Jaeger, and examples/otel-tracing-demo.ts ships a ~120-line dependency-free OTLP/HTTP exporter that streams spans straight to it.
  • new App({ telemetry: true }) native OpenTelemetry OTLP push export at @daloyjs/core/otlp: one flag tees the app logger to the collector as OTLP logs and records http.server.request.duration per the OTel HTTP semantic conventions (spec attributes incl. http.route from the matched route template via the new ctx.routePath, spec bucket boundaries), pushed as dependency-free OTLP/HTTP JSON to the endpoint in the standard OTEL_EXPORTER_OTLP_* env vars — zero config on collector-based platforms that inject them, silent no-op without them. Standalone createOtlpLogExporter() / createOtlpMetricsExporter() (cumulative temporality — totals survive failed pushes) / semconvHttpMetrics() exports; fail-safe by contract (bounded queues, series-cardinality cap, a 5s export timeout, a dead collector never affects serving, tenant-routing header values never logged). Isolate runtimes flush per request through toFetchHandler (Cloudflare waitUntil, Vercel) and toLambdaHandler (awaited); Node/Bun/Deno flush on an interval plus shutdown.
  • tenancy() secure-by-default multitenancy at @daloyjs/core/tenancy: a dependency-free Hooks bundle that resolves the calling tenant once per request and exposes it on ctx.state.tenant. Pluggable resolution (tenantFromSubdomain PSL-aware, tenantFromHeader, tenantFromPathPrefix, tenantFromClaim, or a custom (ctx) => string, tried in array order). Refuse-unresolved by default (no ambient "default" tenant leak), format-validated ids (rejects key/log-injection + cache-poisoning payloads before they reach a key), no-enumeration 404 for unknown tenants, and host-spoof-safe subdomain resolution. A tenantScope() key helper drops straight into rateLimit keyGenerator and concurrencyLimit / idempotency scope to partition each per tenant (CWE-524 cross-tenant cache defense); responseCache() needs no wiring at all — it reads the resolved tenant itself and refuses to boot if mounted ahead of tenancy(). Runnable examples/multitenancy-demo.ts.
  • resilientFetch() + CircuitBreaker outbound resilience at @daloyjs/core/fetch-resilience: a dependency-free circuit breaker (closed → open → half-open), retry-with-backoff (exponential + full jitter, idempotent-method/transient-status scoped, honours Retry-After), and a per-call timeout (AbortControllerFetchTimeoutError) designed to layer on top of fetchGuard() — an SsrfBlockedError is a terminal refusal that is never retried and never trips the breaker, so SSRF protection stays intact under the resilience layer.
  • createWebhookSender() + MemoryWebhookDeadLetterSink outbound webhook delivery at @daloyjs/core/webhook-delivery: the outbound counterpart to verifyWebhookSignature() — timestamped HMAC-signed POSTs (webhook-id / webhook-timestamp / webhook-signature, computed over "<timestamp>.<body>" and reused across retries for safe deduping), bounded retry-with-backoff (transient-status + network scoped, honours Retry-After), per-attempt timeout, and dead-letter semantics. Transport defaults to fetchGuard(), so a subscriber URL pointing at cloud metadata or a private range is refused with a terminal SsrfBlockedError (never retried, dead-lettered once). Zero runtime dependencies.
  • app.cron() + standalone Scheduler in-process scheduled tasks at @daloyjs/core/scheduler: a queue-agnostic schedule primitive for periodic housekeeping (cache sweeps, token refresh, reconciliation). Fixed intervals or 5-field cron expressions (lists / ranges / steps / named months & days / @hourly@yearly aliases / optional IANA timeZone), arithmetic cron parsing (no backtracking regex), fixed-rate single-flight (overlapping ticks are skipped, never run concurrently), per-run timeoutMs with AbortSignal, and graceful-shutdown integration (stop arming → await in-flight → abort after grace). Timers are unref'd. parseCron() / nextCronRun() exported standalone. Zero runtime dependencies.
  • Background jobs (queue-agnostic) at @daloyjs/core/jobs: durable { name, payload } units that outlive the HTTP request and the process — JobStore SPI (all durability lives behind it; Redis/Postgres/SQS are user adapters, never core deps), MemoryJobStore for tests and single-process apps, createJobQueue() (name/queue charset allowlists, plain-JSON payloads capped at 64 KiB with prototype-pollution rejection, idempotency-key dedupe with conflict-on-reuse, delayed runAt, integer priority), and createJobWorker() (atomic claims with leases + fencing, auto-heartbeat plus ctx.heartbeat(), bounded concurrency, per-attempt timeoutMs via AbortSignal, retries with exponential backoff + full jitter, dead letters, graceful stop() drain, runOnce() for tests). Delivery is at-least-once — handlers must be idempotent (pass a key through to downstream APIs like Stripe's Idempotency-Key); EnqueueOptions.idempotencyKey collapses duplicate producers the way idempotency() collapses duplicate POSTs. app.useJobs({ store, handlers, startWorker }) drains the worker on graceful shutdown and warns on a Memory store under production config; app.cronEnqueue() turns a cron tick into an idempotent enqueue so multi-replica crons do not double-fire. This is not a workflow/replay engine — no durable functions, no worlds, no await sleep("7 days"). Zero runtime dependencies.
  • clientCertAuth() mTLS / client-certificate auth at @daloyjs/core/mtls: