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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@adonix.org/cloud-spark

v1.0.1

Published

Ignite your Cloudflare Workers with a type-safe library for rapid development.

Readme

Cloud⚡Spark

npm version Apache 2.0 License Build Quality Gate Status Security Rating Coverage

Ignite your Cloudflare Workers with a type-safe library for rapid development.

CloudSpark provides a logical foundation for building Cloudflare Workers. It works well for simple workers or projects that grow in complexity, helping keep code organized and functionality scalable. It is lightweight and designed to let you focus on the logic that powers your worker.

:bulb: If you are new to Cloudflare Workers, create a free Cloudflare account and install their command line interface Wrangler.

Detailed worker documentation can also be found here.

:books: Contents

:package: Install

npm install @adonix.org/cloud-spark

:rocket: Quickstart

:computer: Use Wrangler to create a new project:

wrangler init

╭ Create an application with Cloudflare Step 1 of 3
│
├ In which directory do you want to create your application?
│ dir ./hello-world
│
├ What would you like to start with?
│ category Hello World example
│
├ Which template would you like to use?
│ type Worker only
│
├ Which language do you want to use?
│ lang TypeScript
│
├ Copying template files
│ files copied to project directory
│
├ Updating name in `package.json`
│ updated `package.json`
│
├ Installing dependencies
│ installed via `npm install`
│
╰ Application created

:computer: Install CloudSpark:

cd ./hello-world

npm install @adonix.org/cloud-spark

# Open the project in your IDE, for example, VS Code:
code .

:page_facing_up: index.ts

import { BasicWorker, TextResponse } from "@adonix.org/cloud-spark";

class HelloWorld extends BasicWorker {
    get() {
        return this.response(TextResponse, "Hi from Cloud Spark!");
    }
}

export default HelloWorld.ignite();

:computer: Now to run the worker locally:

wrangler dev

And it's ready on http://localhost:8787

:arrow_right: Basic Worker

As shown in the Quickstart, BasicWorker is the base class for building Cloudflare Workers with CloudSpark. It handles common tasks, including:

  • Dispatching incoming HTTP requests to the corresponding handler (GET, POST, PUT, etc.).
  • Providing defaults for standard HTTP behavior, such as HEAD and OPTIONS requests.
  • Ensuring type safety and consistent response formatting.
  • Support for built-in and custom middleware.
  • Catching unhandled errors.

Subclasses only need to implement the HTTP methods their worker will handle. Each method can be overridden independently, and additional functionality such as middleware can be added as needed.

Building on the Quickstart, what follows is a more complete example:

:page_facing_up: index.ts

import { BasicWorker, JsonResponse, Method, POST, TextResponse } from "@adonix.org/cloud-spark";

/**
 * To access the Cloudflare runtime properties:
 *   • this.request — the incoming Request
 *   • this.env — environment bindings (KV, R2, etc.)
 *   • this.ctx — the execution context for background tasks
 */
export class MyWorker extends BasicWorker {
    /**
     * Override to allow additional method support for the worker.
     * GET and HEAD requests are always allowed.
     *
     * Default: GET, HEAD, OPTIONS
     *
     * For OPTIONS requests, the default response is:
     *     204 No Content
     *     "Allow" response header contains all allowed methods.
     *
     * If a requested method is not listed, the response is:
     *     405 Method Not Allowed
     *
     * If an allowed method is not implemented, the response is:
     *     GET or HEAD: 404 Not Found
     *     All other methods: 501 Not Implemented
     *
     * This example adds POST method support to the defaults.
     */
    public override getAllowedMethods(): Method[] {
        return [...super.getAllowedMethods(), POST];
    }

    /**
     * Example handler for GET requests that returns a simple
     * text response.
     */
    protected override get(): Promise<Response> {
        return this.response(TextResponse, "Hello from Cloud Spark!");
    }

    /**
     * Example handler for POST requests that echoes the
     * incoming JSON.
     */
    protected override async post(): Promise<Response> {
        const json = await this.request.json();
        // Do something with the request JSON.

        return this.response(JsonResponse, json);
    }

    /**
     * Supported BasicWorker request methods:
     *    protected override get(): Promise<Response>
     *    protected override put(): Promise<Response>
     *    protected override post(): Promise<Response>
     *    protected override patch(): Promise<Response>
     *    protected override delete(): Promise<Response>
     *
     * Implementations are provided but can be overridden for:
     *    protected override head(): Promise<Response>
     *    protected override options(): Promise<Response>
     */
}

/**
 * Connects this worker to the Cloudflare runtime.
 */
export default MyWorker.ignite();

:twisted_rightwards_arrows: Route Worker

RouteWorker extends BasicWorker to provide route-based request handling making it easy to define multiple endpoints in a single worker. It provides:

  • Registering routes individually or in bulk.
  • Matching incoming requests to registered routes by HTTP method and path.
  • Support for URL path patterns using path-to-regexp syntax.
  • Dispatching requests to either a callback function or another worker.

Example:

:page_facing_up: index.ts

import { BasicWorker, GET, PathParams, RouteWorker, TextResponse } from "@adonix.org/cloud-spark";

/**
 * An example worker with path routing.
 */
class GreetingWorker extends RouteWorker {
    /**
     * Called before request processing to enable worker
     * initialization without overriding the constructor.
     */
    protected override init(): void {
        /**
         * Example of path-to-regexp and local method routing.
         */
        this.route(GET, "/hello/:name", this.hello);

        /**
         * Example of simple path to a nested worker.
         */
        this.route(GET, "/goodbye", GoodbyeWorker);
    }

    /**
     * Path parameters are provided via path-to-regexp parsing
     * of the request path.
     *
     * For example, http://localhost:8787/hello/Inigo will yield
     * the text response "Hello Inigo!"
     */
    protected hello(params: PathParams): Promise<Response> {
        return this.response(TextResponse, `Hello ${params["name"]}!`);
    }
}

/**
 * An example nested BasicWorker.
 *
 * The original request, env, and ctx are passed to the nested
 * worker via the constructor.
 *
 * RouteWorkers may also be nested to access path parameters.
 */
class GoodbyeWorker extends BasicWorker {
    /**
     * GET handler for the "/goodbye" path.
     */
    protected override get(): Promise<Response> {
        return this.response(TextResponse, "Goodbye!");
    }
}

/**
 * Connects GreetingWorker to the Cloudflare runtime.
 */
export default GreetingWorker.ignite();

:bulb: Requests with no matching route fall back to the corresponding BasicWorker method.

:gear: Middleware

Middleware extends your worker’s behavior in a modular way. Each middleware can inspect the incoming request, return a custom response early, or modify the response produced by later handlers. It’s a simple way to add logic such as authentication checks, request logging, or response transformations without touching your core code.

CloudSpark includes built-in middleware for common functionality like caching and CORS, and you can easily create your own to handle behavior specific to your application.

CORS

Register the built-in CORS middleware as follows:

:page_facing_up: index.ts

import { BasicWorker, cors } from "@adonix.org/cloud-spark";

class MyWorker extends BasicWorker {
    /**
     * Register middleware in the init method.
     */
    protected override init(): void {
        /**
         * Create and register the built-in CORS middleware
         * with default options:
         *
         * {
         *   allowedOrigins: ["*"],
         *   allowedHeaders: ["Content-Type"],
         *   exposedHeaders: [],
         *   allowCredentials: false,
         *   maxAge: 300,
         * }
         *
         */
        this.use(cors());

        /**
         * To override specific default CORS options:
         *
         * this.use(cors({ allowedOrigins: ["https://www.adonix.org"], maxAge: 604800 }));
         *
         */
    }
}

:bulb: The middleware adds CORS headers to the response ONLY if the request includes an Origin header.

Cache

CloudSpark includes built-in caching middleware that stores responses for improving performance. Only responses that are safe to cache are stored, including:

  • Responses to GET requests with a 200 OK status.
  • Responses that specify a time-to-live via Cache-Control headers (max-age or s-maxage).
  • Responses with Vary headers are fully supported, so the cache respects variations based on headers like Accept-Language.
  • Responses that do not include user-specific data (such as Set-Cookie or requests with Authorization/Cookie headers).

Other types of responses (non-GET, errors, partial content, or requests marked no-store) are never cached. This ensures caching is safe and consistent with HTTP standards.

Register the built-in cache middleware as follows:

:page_facing_up: index.ts

import { BasicWorker, cache, CacheControl, JsonResponse, Time } from "@adonix.org/cloud-spark";

class MyWorker extends BasicWorker {
    /**
     * Enable middleware in the worker init method.
     */
    protected override init(): void {
        /**
         * Create and register the built-in cache middleware.
         */
        this.use(cache());

        /**
         * Optionally pass settings to the cache function:
         *
         * 	name — the name of the cache storage to use. If omitted,
         *         the default cache is used.
         *  getKey — a function that maps the incoming request to a
         *           cache key.
         *           Built-in key functions include:
         *               • sortSearchParams (Default)
         *               • stripSearchParams
         *
         * this.use(cache({
         *     name: "my-cache",
         *     getKey: stripSearchParams,
         * }));
         *
         */
    }

    /**
     * Create a cacheable response.
     */
    protected override get(): Promise<Response> {
        /**
         * Example JSON message.
         */
        const json = {
            message: "Hi from Cloud Spark!",
            timestamp: new Date().toLocaleString(),
        };

        /**
         * Cache the response for 10 seconds.
         */
        const cc: CacheControl = {
            "s-maxage": 10 * Time.Second,
        };

        return this.response(JsonResponse, json, cc);
    }
}

/**
 * Connects MyWorker to the Cloudflare runtime.
 */
export default MyWorker.ignite();

:bulb: The cf-cache-status response header will contain HIT when serving from the cache.

WebSocket

The WebSocket middleware ensures upgrade requests are valid before they reach your handler. You can provide a path (default: "/") and register multiple instances for multiple paths. Invalid upgrade requests are intercepted, and the correct error response is returned.

A valid WebSocket upgrade request must use the GET method and include the following:

| Header | Value | | --------------------- | --------- | | Connection | Upgrade | | Upgrade | websocket | | Sec-WebSocket-Version | 13 |

Register the built-in websocket middleware as follows:

:page_facing_up: index.ts

import { GET, PathParams, RouteWorker, websocket } from "@adonix.org/cloud-spark";

class ChatWorker extends RouteWorker {
    /**
     * Register both the upgrade route and middleware.
     */
    protected override init(): void {
        /**
         * Route for WebSocket upgrades.
         */
        this.route(GET, "/chat/:room", this.upgrade);

        /**
         * Register WebSocket middleware to match the
         * upgrade route.
         */
        this.use(websocket("/chat/:room"));
    }

    /**
     * Handles WebSocket upgrade requests.
     *
     * Expects a DurableObject binding named CHAT_ROOM
     * in wrangler.jsonc
     */
    protected upgrade(params: PathParams): Promise<Response> {
        /**
         * Get the Durable Object stub for the chat room
         * given by the "room" path parameter.
         */
        const stub = this.env.CHAT_ROOM.getByName(params["room"]);

        /**
         * The request has already been validated by the
         * WebSocket middleware, so dispatch the WebSocket 
         * upgrade request to the Durable Object.
         */
        return stub.fetch(this.request);
    }
}

/**
 * Connects ChatWorker to the Cloudflare runtime.
 */
export default ChatWorker.ignite();

:bulb: See the complete WebSocket example here.

Custom

Create custom middleware by implementing the Middleware interface and its single handle method, then register it with your worker. Within your middleware, you can inspect requests and modify responses or short-circuit processing entirely.

Here is a simple example:

import { BadRequest, CopyResponse, Middleware, Worker } from "@adonix.org/cloud-spark";

/**
 * Custom middleware example.
 *
 * Demonstrates several key middleware capabilities:
 *   • Options via constructor parameters.
 *   • Inspection of the incoming request.
 *   • Short-circuiting by returning a response directly.
 *   • Modifying outgoing responses dispatched by the worker.
 */
class PoweredBy implements Middleware {
    /**
     * Optional constructor parameter to customize the "X-Powered-By" header.
     */
    constructor(private readonly name = "CloudSpark") {}

    public async handle(worker: Worker, next: () => Promise<Response>): Promise<Response> {
        /**
         * Extract the User-Agent header from the request.
         */
        const userAgent = worker.request.headers.get("User-Agent")?.trim();

        /**
         * If the User-Agent is missing, short-circuit by directly
         * returning 400 Bad Request.
         */
        if (!userAgent) {
            return new BadRequest("Missing User-Agent").response();
        }

        /**
         * Calls the next middleware or worker dispatch method.
         */
        const response = await next();

        /**
         * Wrap the response in a mutable copy.
         */
        const copy = new CopyResponse(response);

        /**
         * Append custom headers to the response.
         */
        copy.setHeader("X-User-Agent", userAgent);
        copy.setHeader("X-Powered-By", this.name);
        copy.setHeader("X-Processed-At", new Date().toUTCString());

        /**
         * Return the modified response.
         */
        return copy.response();
    }
}

/**
 * Factory function for registering the middleware, with an optional
 * name parameter. Workers interact with the `Middleware` interface,
 * and not the concrete implementation.
 *
 * Example:
 *   this.use(poweredby());
 *   this.use(poweredby("My Project Name"));
 */
export function poweredby(name?: string): Middleware {
    return new PoweredBy(name);
}

Ordering

The order in which middleware is registered by a worker can matter depending on the implementation. It helps to visualize ordering as top-down for requests and bottom-up for responses.

Here is a what a full GET request flow with middleware A, B, and C could look like:

                  Full

      Request               Response
         ↓     this.use(A)     ↑
         ↓     this.use(B)     ↑
         ↓     this.use(C)     ↑
           →      get()      →

Now imagine B middleware returns a response early and short-circuits the flow:

            Short Circuit B

      Request               Response
         ↓     this.use(A)     ↑
         ↓     this.use(B)   →
               this.use(C)
                  get()

In this scenario, neither middleware C nor the worker's get() method executes. This is exactly what you want, for example, when using the Cache middleware. If a valid response is found in the cache, that response can and should be returned immediately.

However, this illustrates that different behavior can occur depending on the order of middleware registration.

We can use the built-in Cache and CORS middleware as a more concrete example:

/**
 * This version results in CORS response headers stored in
 * the cache. On the first cacheable response, CORS middleware
 * applies its response headers BEFORE caching.
 */
this.use(cache());
this.use(cors());

/**
 * This version results in CORS response headers NOT stored
 * in the cache, which is likely preferred. Fresh CORS headers
 * are added to every response regardless of cache status.
 */
this.use(cors());
this.use(cache());

The difference in behavior becomes clear when disabling the CORS middleware on the worker. In the first version, CORS headers remain on all cached responses until the cached entry expires. In the second version, disabling CORS takes effect immediately; all responses, cached or not, will no longer include CORS headers.

:left_right_arrow: Web Sockets

Simplify WebSocket connection management with CloudSpark. Features include:

The following is a simple chat with hibernation example:

:page_facing_up: wrangler.jsonc

/**
 * Remember to rerun 'wrangler types' after you change your
 * wrangler.json file.
 */
{
    "$schema": "node_modules/wrangler/config-schema.json",
    "name": "chat-room",
    "main": "src/index.ts",
    "compatibility_date": "2025-11-01",
    "observability": {
        "enabled": true,
    },
    "durable_objects": {
        "bindings": [
            {
                "name": "CHAT_ROOM",
                "class_name": "ChatRoom",
            },
        ],
    },
    "migrations": [
        {
            "tag": "v1",
            "new_sqlite_classes": ["ChatRoom"],
        },
    ],
}

:page_facing_up: index.ts

import { DurableObject } from "cloudflare:workers";

import {
    GET,
    PathParams,
    RouteWorker,
    websocket,
    WebSocketSessions,
    WebSocketUpgrade,
} from "@adonix.org/cloud-spark";

/**
 * Metadata attached to each session.
 */
interface Profile {
    name: string;
    lastActive: number;
}

export class ChatRoom extends DurableObject {
    /**
     * Manage all active connections for this room.
     */
    protected readonly sessions = new WebSocketSessions<Profile>();

    constructor(ctx: DurableObjectState, env: Env) {
        super(ctx, env);

        /**
         * Restore all active connections on wake from
         * hibernation.
         */
        this.sessions.restoreAll(this.ctx.getWebSockets());
    }

    public override fetch(request: Request): Promise<Response> {
        /**
         * For demo purposes, get the user's name from the `name`
         * query parameter.
         */
        const name = new URL(request.url).searchParams.get("name") ?? "Anonymous";

        /**
         * Create a new connection and initialize its `Profile`
         * attachment.
         */
        const con = this.sessions.create({
            name,
            lastActive: Date.now(),
        });

        /**
         * Accept the WebSocket with recommended hibernation enabled.
         *
         * To accept without hibernation, use `con.accept()` and
         * `con.addEventListener()` methods instead.
         */
        const client = con.acceptWebSocket(this.ctx);

        /**
         * Return the upgrade response with the client WebSocket.
         */
        return new WebSocketUpgrade(client).response();
    }

    /**
     * Send a message to all active sessions.
     */
    public broadcast(message: string): void {
        for (const session of this.sessions) {
            session.send(message);
        }
    }

    public override webSocketMessage(ws: WebSocket, message: string): void {
        /**
         * Get the sender's WebSocket session from the active sessions.
         */
        const con = this.sessions.get(ws);
        if (!con) return;

        /**
         * Update the sender's `Profile` with current `lastActive` time.
         */
        con.attach({ lastActive: Date.now() });

        /**
         * Broadcast the message to all sessions, prefixed with the
         * sender’s name.
         */
        this.broadcast(`${con.attachment.name}: ${message}`);
    }

    public override webSocketClose(ws: WebSocket, code: number, reason: string): void {
        /**
         * Closes and removes the WebSocket from active sessions.
         */
        this.sessions.close(ws, code, reason);
    }
}

class ChatWorker extends RouteWorker {
    protected override init(): void {
        /**
         * Define the WebSocket connection route.
         */
        this.route(GET, "/chat/:room", this.upgrade);

        /**
         * Register the middleware to validate WebSocket
         * connection requests.
         */
        this.use(websocket("/chat/:room"));
    }

    private upgrade(params: PathParams): Promise<Response> {
        /**
         * Get the Durable Object stub for the chat room
         * given by the "room" path parameter.
         */
        const stub = this.env.CHAT_ROOM.getByName(params["room"]);

        /**
         * Dispatch the WebSocket upgrade request to the
         * Durable Object.
         */
        return stub.fetch(this.request);
    }
}

/**
 * Connects ChatWorker to the Cloudflare runtime.
 */
export default ChatWorker.ignite();

:computer: To run this chat example locally:

wrangler dev

:bulb: Apps like Postman can be used to create and join local chat rooms for testing:

ws://localhost:8787/chat/fencing?name=Inigo

:partly_sunny: Wrangler

First, create a FREE Cloudflare account.

:computer: Install Wrangler

npm install -g wrangler

:computer: Login

wrangler login

:computer: Initialize a new Cloudflare Worker project

wrangler init

Install Cloud Spark

:link: Links

:arrow_up: