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

@oselvar/sveltekit-add-worker-exports

v0.3.0

Published

Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers

Readme

@oselvar/sveltekit-add-worker-exports

A Vite plugin that makes Durable Objects and Workflows work with SvelteKit on Cloudflare, in both dev and production.

Build mode: SvelteKit's adapter-cloudflare generates _worker.js with only a default export (the fetch handler). Cloudflare Workers requires Durable Object and Workflow classes to be named exports. This plugin post-processes the build output to merge your named exports with SvelteKit's default export.

Dev mode: getPlatformProxy (used by adapter-cloudflare in dev) can't run internal Durable Objects or Workflows. This plugin starts a separate wrangler dev server that runs the real DO/Workflow worker with hot-reload. SvelteKit +server.ts handlers call DOs through platform.env.MY_DO.<rpc>() as usual — the plugin rewrites those bindings to point at the sidecar via wrangler's dev registry, so cross-worker calls Just Work. Clients can also connect directly to the sidecar via WebSocket on a separate port (see below).

Install

pnpm add -D @oselvar/sveltekit-add-worker-exports

esbuild, vite, and wrangler are peer dependencies -- your SvelteKit project already has them.

Usage

Create a worker entry point that exports your Durable Object classes and a default fetch handler. The fetch handler is only used by the wrangler dev server — in production, SvelteKit's route handlers handle all requests.

// src/lib/server/index.ts
export { MyDurableObject } from './MyDurableObject';
export { MyWorkflow } from './MyWorkflow';
export { default } from './devHandler';

Workflow classes (extending WorkflowEntrypoint) are exported the same way as Durable Objects — the plugin merges them into _worker.js as named exports. Declare them in wrangler.jsonc under workflows, and they become available as bindings (e.g. env.MY_WORKFLOW.create({ params })) in both dev and production.

Both the dev handler and the production SvelteKit route need to do the same thing: validate the upgrade header and forward the request to a Durable Object. Extract that into a small helper so the two callers stay in sync:

// src/lib/server/forwardWebSocket.ts
export async function forwardWebSocket<T extends Rpc.DurableObjectBranded | undefined>(
  request: Request,
  namespace: DurableObjectNamespace<T>,
  name: string
): Promise<Response> {
  if (request.headers.get('upgrade') !== 'websocket') {
    return new Response('Expected WebSocket', { status: 426 });
  }
  const id = namespace.idFromName(name);
  return namespace.get(id).fetch(request);
}

The dev handler parses the URL and delegates. It's only used by the wrangler dev sidecar:

// src/lib/server/devHandler.ts
import { forwardWebSocket } from './forwardWebSocket';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const match = new URL(request.url).pathname.match(/^\/ws\/(.+)$/);
    if (!match) return new Response('Not found', { status: 404 });
    return forwardWebSocket(request, env.MY_DO, match[1]);
  }
};

In production, the dev handler is not used — SvelteKit serves the same path through a +server.ts route, which delegates to the same helper:

// src/routes/ws/[id]/+server.ts
import { forwardWebSocket } from '$lib/server/forwardWebSocket';
import type { RequestHandler } from './$types';

export const GET: RequestHandler = ({ params, request, platform }) =>
  forwardWebSocket(request, platform!.env.MY_DO, params.id);

Now any change to the upgrade-and-forward logic (auth, rate-limiting, response shape) lives in one place and applies to both dev and production.

Add the plugin to your vite.config.ts after sveltekit():

import { sveltekit } from '@sveltejs/kit/vite';
import { addWorkerExports } from '@oselvar/sveltekit-add-worker-exports';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    sveltekit(),
    addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
  ]
});

Point adapter-cloudflare's platform proxy at the generated .platform-proxy-wrangler.jsonc. The plugin writes this file with internal Durable Object bindings rewritten to cross-worker form (each gets a script_name pointing at the sidecar). Workflows and migrations are stripped — calling a Workflow from platform.env in vite dev isn't supported, but it works in production. Without this config path, getPlatformProxy would try to run the classes itself and warn that it can't:

// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

export default {
  kit: {
    adapter: adapter({
      platformProxy: {
        configPath: '.platform-proxy-wrangler.jsonc'
      }
    })
  }
};

Dev mode: connecting to Durable Objects

In dev mode, the plugin starts a wrangler dev server on a separate port and injects __DEV_WORKER_PORT__ as a compile-time constant. Use it to connect your client:

import { dev } from '$app/environment';

let wsUrl: string;
if (dev) {
  wsUrl = `ws://${window.location.hostname}:${__DEV_WORKER_PORT__}/ws/${id}`;
} else {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  wsUrl = `${protocol}//${window.location.host}/ws/${id}`;
}
const ws = new WebSocket(wsUrl);

Add the type declaration to your src/app.d.ts:

declare global {
  const __DEV_WORKER_PORT__: number;
}

The plugin auto-discovers your wrangler.jsonc (or wrangler.toml) and reads DO bindings, migrations, and compatibility settings from it. It overrides only the main entry point to use your source file instead of the SvelteKit build output.

Testing the production build locally

vite dev exercises the dev handler via the wrangler-dev sidecar; it does not exercise your +server.ts route or the merged _worker.js. To verify the production wiring (named DO exports + SvelteKit routes in the same worker), run wrangler against the build output:

pnpm build              # produces .svelte-kit/cloudflare/_worker.js with merged exports
pnpm wrangler dev       # uses wrangler.jsonc → main: .svelte-kit/cloudflare/_worker.js

This serves the exact bundle that gets deployed, with local Durable Object storage. Connect a WebSocket client to ws://localhost:8787/ws/<id> and confirm you get a 101 Switching Protocols response — that proves the request flowed through the SvelteKit +server.ts and into your DO.

A handy shortcut is to add a preview script to package.json:

{
  "scripts": {
    "preview": "wrangler dev"
  }
}

Note: vite preview is not suitable here — it only serves static assets and cannot run Durable Objects. Always use wrangler dev to preview the production worker.

Options

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | entryPoint | string | Yes | -- | Path to the file that exports your DO/Workflow classes | | outputDir | string | No | .svelte-kit/cloudflare | Directory containing the SvelteKit-generated _worker.js | | wranglerConfig | string | No | auto-discovered | Path to wrangler config file | | devPort | number | No | 8787 | Port for the dev worker server |

How it works

Build mode

The plugin runs in the closeBundle hook (after SvelteKit's adapter has generated _worker.js):

  1. Bundles your entryPoint with esbuild into _extra_exports.js
  2. Renames the original _worker.js to _sveltekit_worker.js
  3. Creates a new _worker.js that re-exports both:
export { default } from './_sveltekit_worker.js';
export * from './_extra_exports.js';

The operation is idempotent -- if _sveltekit_worker.js already exists, the plugin skips.

Dev mode

The plugin reads your wrangler config, creates a temporary config with main pointing to your entryPoint, and starts a wrangler dev server via unstable_startWorker. This gives you:

  • Real workerd runtime (not emulated)
  • Hot-reload when you change DO code
  • Same WebSocket protocol as production
  • Fully typed Durable Object bindings (see below)

Generating typed bindings

The dev plugin creates a temporary .dev-worker-wrangler.jsonc with main pointing to your source entry point. You can use this to generate fully generic Cloudflare types:

wrangler types --config .dev-worker-wrangler.jsonc

This produces typed DO bindings like DurableObjectNamespace<MyDurableObject> instead of the untyped DurableObjectNamespace you get from the default wrangler.jsonc (whose main points to the SvelteKit build output, which doesn't exist during dev).

Add this to your package.json scripts for convenience:

{
  "scripts": {
    "types": "wrangler types --config .dev-worker-wrangler.jsonc"
  }
}

Note: the .dev-worker-wrangler.jsonc file is generated when the dev server starts. Run pnpm dev at least once before running wrangler types.

Why this exists

SvelteKit's adapter-cloudflare does not support named exports from the worker entry point (sveltejs/kit#1712). Additionally, getPlatformProxy (used for local dev) cannot run internal Durable Objects because it uses an empty worker script.

License

MIT