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

effect-cf

v0.47.0

Published

Effect-native primitives for Cloudflare Workers and bindings.

Readme

effect-cf

Cloudflare entrypoints and bindings as Effect services.

npm install effect-cf "effect@^4.0.0-rc.115"

The repository tests against workerd 1.20260825.1 and @cloudflare/[email protected]. Use compatibility_date: "2026-08-25" in Wrangler.

See the runtime performance guide for measured alarm batching and telemetry configuration guidance.

For typed WebSocket RPC clients whose connection lifetime is owned by an Effect layer, see the WebSocket RPC guide.

Worker

import { Effect, Layer } from "effect";
import { Worker } from "effect-cf";

export default Worker.make(Layer.empty, {
  fetch: Effect.sync(() => new Response("Hello")),
});

Worker.make owns the Effect runtime. Pass application services as its layer; use Worker.NativeRequest inside the handler to read the request.

Native RPC values

Tagged Worker and Durable Object methods encode declared schemas through their canonical JSON codec by default. Wrap a method argument or success schema with Worker.native(schema) or DurableObject.native(schema) when its encoded value must instead pass directly through Cloudflare RPC.

import { Effect, Layer, Schema } from "effect";
import { Worker } from "effect-cf";

const ByteStream = Schema.declare(
  (value): value is ReadableStream<Uint8Array> => value instanceof ReadableStream,
);

class Documents extends Worker.Tag<Documents>()("Documents", {
  upload: Worker.method({
    args: [Worker.native(ByteStream)],
    success: Schema.Void,
  }),
}) {}

export default Documents.make(Layer.empty, {
  rpc: {
    upload: (body) => Effect.tryPromise(() => new Response(body).arrayBuffer()).pipe(Effect.asVoid),
  },
});

The wrapped schema still validates and transforms values, but its encoded form must be supported by Cloudflare RPC. Native byte streams must be byte-oriented (type: "bytes"). Cloudflare transfers stream ownership to the recipient; use ReadableStream.tee() first if the sender must retain a copy.

Bindings

Define a service, connect it to a Wrangler binding, then yield it in your program.

import { Effect, Schema } from "effect";
import { Kv } from "effect-cf";

class Settings extends Kv.Tag<Settings>()("Settings", {
  key: Schema.String,
  value: Schema.String,
}) {}

const SettingsLive = Settings.layer({ binding: "SETTINGS" });

const greeting = Effect.gen(function* () {
  const settings = yield* Settings;

  return yield* settings.get("greeting");
});

Declare SETTINGS in wrangler.jsonc and pass SettingsLive to Worker.make. Other bindings use the same tag/layer pattern.

The document outbox example saves document revisions and their delivery alarms atomically, then archives them to R2 outside the transaction.

For atomic application writes and alarm changes, see the alarm transaction example and API contract.

Define a typed alarm service with class Alarms extends DurableObjectAlarm.Tag<Alarms>()("Alarms", { ...schemas }) {}. Pass Alarms.handlers({ ...implementations }) to the DO's alarms: option, then yield* Alarms to schedule or cancel alarms and open transactions. All declared handlers are required. The registration provides the service to application layers, initialization, and event handlers; declaring schemas alone does not provide a scheduler.

Scheduling accepts decoded payloads, encodes them with the declared schema, and checks that the result is JSON before storage. Transaction callbacks expose the same typed mutations and retain the raw scheduler's rollback and callback-lifetime rules.

DurableObject.make and tagged definitions' .make still provide the raw DurableObjectAlarm service automatically for dynamic tags and JSON payloads. DurableObjectAlarm.define({ ... }).handlers(...) remains a handler-only helper for raw scheduling. No alarm tables or native alarms are created until the scheduler is used. Outside these entrypoints, provide the raw scheduler layer explicitly. A typed registration exposes layer and run for custom runtimes and tests; custom runtimes must install both.

Unknown stored tags produce StoredAlarmDecodeError and follow the configured delivery failure policy rather than being acknowledged silently. Use the raw scheduler's cancelAlarm to remove retired tags, including repeating alarms.

Cloudflare Observability traces

CloudflareTracer.layer sends existing Effect.withSpan and named Effect.fn spans to Cloudflare's trace waterfall, alongside automatic platform spans.

import { Effect, Layer } from "effect";
import { CloudflareTracer, Worker } from "effect-cf";

export default Worker.make(Layer.empty, {
  eventLayer: CloudflareTracer.layer,
  fetch: Effect.sync(() => new Response("Hello")).pipe(Effect.withSpan("greet")),
});

Enable tracing in wrangler.jsonc:

{
  "compatibility_date": "2026-08-25",
  "observability": {
    "traces": { "enabled": true },
  },
}

Build the layer per invocation with eventLayer. It captures the current Cloudflare async context, so do not put it in a runtime layer cached across requests. Standalone Effect programs can provide it around each invocation. Nested spans, concurrent fibers, and resumed work restore the appropriate span context. Cloudflare handles sampling and export; no exporter endpoint or flush is needed.

Strings, finite numbers, and booleans are forwarded as native scalars. Null, plain objects (including null-prototype objects), and dense arrays are encoded as JSON strings under the original attribute key. For example, { routes: ["email", "sms"], retry: false } becomes the string '{"routes":["email","sms"],"retry":false}', not native object/array metadata. There is no flattening or generated attribute key per nested field.

Each encoded value is limited to 4096 UTF-8 bytes, four container levels, and 64 values including the root. Keys and JSON punctuation count toward bytes. The entire value is dropped if it exceeds a limit or contains unsupported input: cycles, accessors, undefined, non-finite numbers, bigint, symbols, functions, class instances (including errors and Effect Redacted), or sparse arrays. Only own enumerable string keys and array elements are included; getters and toJSON are never called. Serialization/host errors are ignored. Dropped updates leave any previous native attribute value intact. Original Effect-local values remain available. These are adapter limits; Cloudflare can apply further limits. Existing scalar strings are forwarded unchanged.

The adapter reserves effect.*; caller attributes in that namespace remain Effect-local and cannot overwrite its metadata. Existing annotations using that prefix should move to an application namespace if they need native export.

| Native attribute | Representation | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | effect.trace_id, effect.span_id | Effect IDs, distinct from Cloudflare's opaque IDs | | effect.parent.trace_id, effect.parent.span_id | Effect parent IDs, when present | | effect.span.kind | Effect's internal/server/client/producer/consumer kind | | effect.span.links | JSON array of { traceId, spanId } for the first eight links at span end, including addLinks updates | | effect.span.links_dropped | Omitted link count; the candidate array is dropped whole if it exceeds the JSON limits | | effect.exit | success, failure, or interrupted | | effect.error.kind | On failure: failure, defect, mixed (typed failures and defects), or interrupted (interruptions only) |

Link attributes remain local. Correlation and error metadata add no spans or logs. No Cause, error payload, message, stack, schema input, or request body is automatically serialized for errors. Optional diagnostics require an explicit sanitizing formatter:

const tracing = CloudflareTracer.layerWith({
  formatError: () => ({
    type: "OperationFailed",
    message: "The operation could not complete",
  }),
  spanEvents: true,
});

export default Worker.make(Layer.empty, {
  eventLayer: tracing,
  fetch: Effect.sync(() => new Response("Hello")).pipe(Effect.withSpan("greet")),
});

formatError receives the failed span's Cause, including interruption, and may return undefined or sanitized type/message strings. These become effect.error.type and effect.error.message; each is dropped above 4096 UTF-8 bytes. Extra returned fields are ignored. The formatter runs once at end only for sampled failed spans; formatter exceptions cannot change the application exit. Do not pass through raw errors or use a full Cause formatter here.

spanEvents defaults to false. Opting in forwards at most the first 16 event attempts per span through console.log in that span's captured async context. Each structured log contains effect.event (the original name), effect.event.time_unix_nano (the original bigint timestamp as a decimal string), effect.trace_id, effect.span_id, and nested attributes. The complete log uses the same JSON limits, including its envelope; an unsupported/oversized log is dropped whole and still consumes one attempt. Unsampled and ended spans emit no event logs. Reentrant forwarding and console failures are suppressed. This adds log volume and may increase Observability costs; log retention/export also depends on your Cloudflare logging configuration.

This is searchable correlation and optional log forwarding. Cloudflare's custom span API does not expose native IDs, manual parent wiring, an outcome setter, addLink, or addEvent. It does not create native graph edges, clickable links, native OTel span events, or cross-system propagation. Explicit external parents cannot join a Cloudflare trace by ID, and root: true starts under the invocation's captured context.

This layer replaces the active Effect tracer. When combining it with CloudflareOtlp, select only logs and/or metrics in the OTLP layer. See Cloudflare's custom span API for platform limitations.

Native RPC tracing

call(), scopedCall(), and definition methods create one CLIENT span named binding/method, covering argument encoding, the native RPC wait, and success decoding. Raw rpc() retains Cloudflare's pipelined result without creating a span. Wrap its complete lifetime with RpcTracing.withRpcClientSpan when tracing raw calls.

Live parent propagation requires both Contract.layer({ binding: "COUNTERS", rpcTracing: true }) on the client and rpcTracing: { service: "COUNTERS" } in the receiver's make options. Namespace and service binding clients accept the same boolean option. Enable receivers before clients. Disabled clients preserve argument lists exactly; receivers strip only a valid trailing effect-cf/RpcTraceContext/v1 argument after opting in. Opting in reserves that complete tagged shape in the final argument position, so do not use it there as a domain argument, including during receiver-first rollout.

Applications own SERVER spans. Override the exported DurableObject.RunSymbol or Worker.RunSymbol, wrap the effect with RpcTracing.withRpcServerSpan(effect, options.rpc) when options.rpc exists, and call super with the original options. See the typed receiver example. The receiver installs the validated parent before instrumentation and event-layer setup. No additional layer input is required.

RunOptions.event identifies the native event before work starts. RunOptions.rpc includes service, method, native args, and the validated parent. Its decodedArgs becomes available after definition decoding succeeds, before the handler runs. Never log these arguments. Span helpers record stable RPC attributes and failure status without error payloads; original typed failures still reach the caller.

This metadata belongs only to the live native call. Do not store it in domain envelopes, alarms, queues, or WebSocket attachments, or reuse it for resumed work. Sampling and exporter configuration remain application choices.

API

See the exports and tests for the remaining APIs.

Optional integrations have separate imports: effect-cf/hyperdrive-pg, effect-cf/computer-workspace, effect-cf/computer-artifacts, effect-cf/computer-workspace-host, effect-cf/sandbox, and effect-cf/vitest. Install the matching SDK or driver listed in peerDependencies. Computer Git operations also require @platformatic/vfs.

Changelog · MIT license