Skip to content
Release1.0.0 rc

DaloyJS 1.0.0-rc.0: The First Release Candidate

The beta said 'nothing changed, on purpose.' The release candidate says 'the door is now locked.' Here is what the RC means, what actually landed across the beta train (spoiler: MCP), and the short honest list of what still stands between us and 1.0.0 GA.

Devlin DuldulaoFullstack cloud engineer7 min read

A couple of weeks ago I wrote a post whose whole punchline was that the 1.0.0 beta changed nothing on purpose. Today I get to write the sequel, and the tone is different. We just tagged 1.0.0-rc.0, the first release candidate. If the beta was me quietly hoping the API was ready, the RC is me locking the door and putting the key on the table.

The rule for a release candidate is short and unglamorous: from here to the 1.0.0 GA, only bug fixes and documentation land. No new middleware, no new adapter, no new helper. The public API you see in 1.0.0-rc.0 is the public API you get in 1.0.0, minus whatever bugs you help me find. That is the entire promise, and it is the reason an RC feels heavier to tag than a beta.

So the beta really was the freeze

When I said nothing changed at beta.0, I meant the shape was set. But betas .1 through .7 were not a nap. They were the part of a release where you stop building and start proving, and a few real things landed in that window that are worth calling out before the door shuts.

The biggest one: DaloyJS grew a dependency-free Model Context Protocol server. If you are building anything an AI client talks to, you can now expose tools, resources, and prompts over MCP Streamable HTTP from the same framework, with the same body limits, timeouts, auth middleware, and problem+json errors as any other route. It validates Origin against DNS rebinding by default, because that is a spec requirement and also because leaving it off is exactly the kind of quiet footgun this framework exists to remove.

ts
import { App, createMcpHandler, mcpRoutes } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";

const mcp = createMcpHandler({
  serverInfo: { name: "inventory-mcp", version: "1.0.0" },
  // Origin is validated against DNS rebinding out of the box.
  // Add browser origins you actually trust; everything else gets 403.
  allowedOrigins: ["https://app.example.com"],
  tools: [
    {
      name: "inventory_lookup",
      description: "Look up available units by SKU.",
      inputSchema: {
        type: "object",
        properties: { sku: { type: "string", minLength: 1 } },
        required: ["sku"],
        additionalProperties: false,
      },
      handler: async ({ sku }) => `SKU ${sku}: 42 units`,
    },
  ],
});

const app = new App();
for (const route of mcpRoutes("/mcp", mcp)) app.route(route);
serve(app, { port: 3001 });

The rest of the beta window was the unglamorous work I actually respect most. The Node hot path got faster (lazy request and response shims plus sync-first validation, which is a 21% bump on the full-contract benchmark and 53% on the bare echo path, with zero behavior change and every security check still in place). The create-daloy templates were brought into line with the security and contract guidance they ship with, so a freshly scaffolded app actually follows the patterns the docs preach. And the docs site itself got a navigation and hydration pass so reading it stops fighting you.

Nothing to do if you are already on the beta

1.0.0-rc.0 is a version bump from beta.7. If you were on ^1.0.0-beta.7, upgrading is a lockfile change and a good night's sleep. We moved @daloyjs/core, create-daloy, and the JSR package @daloyjs/daloy together, as always, and every create-daloy template now pins @daloyjs/core@^1.0.0-rc.0. A plain install gets you the RC, no dist-tag archaeology required:

bash
# Scaffold a fresh project on the release candidate
pnpm create daloy@latest my-api

# Or add the core to an existing project
pnpm add @daloyjs/core

# Pin it explicitly if you like being specific
pnpm add @daloyjs/core@1.0.0-rc.0

The honest part: what is between here and GA

I could pretend a release candidate means we are basically done. We are not, and the roadmap says so out loud. The engineering bar is met: the API has been additive across the whole beta train, coverage sits around 99% lines and 92% branches, the supply-chain gates are green, and the benchmark suite is public. What is left before 1.0.0 GA is deliberately not code.

Three things. I want at least three production users on file, real services depending on this, not a to-do app I wrote to feel good. I want the security disclosure process exercised at least once, because a policy you have never run is a policy you do not actually have. And I want migration guides from the frameworks people are actually leaving. The Express guide is up; Fastify and Hono are the next writing I owe you, and the RC window is when I pay that debt.

What I want from you

Same ask as the beta, higher stakes. A release candidate is a bet that the API is right, placed in public so you can call it. The most useful thing you can do in the next few weeks is build a real thing on 1.0.0-rc.0 and find the corner I sanded wrong. Report the bug. Tell me the name that reads badly. Show me the adapter that drifts from the docs. Once GA ships, that feedback costs a deprecation cycle to act on. Right now it is free.

If you are new here, start with the case for using DaloyJS today and the defenses you inherit on your very first route in Secure by Default. Then run pnpm create daloy@latest and come tell me what broke.

Tagging a release candidate is the moment a project stops being "almost ready" and becomes "prove it." That is the scary-in-a-good-way part, and I would rather be here nervous than still adding features I would have to defend forever. Thanks for being early. Let us go find the bugs.

About the author: Filipino developer in Norway who has cut enough releases at odd hours to know the scary ones are the boring ones with a big version number.