Effect-TS OpenTelemetry style for Maple via @maple-dev/effect-sdk: Maple.layer() bootstrap, Effect.withSpan / Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server / browser / Cloudflare entry points.
Pro shows the line behind each finding and how to fix it
Scanned 10/1/2026
npx -y skills add MapleTechLabs/maple --skill maple-effect-style --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Maple Effect Style?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/mapletechlabs-maple-effect-style)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: maple-effect-style
description: "Effect-TS OpenTelemetry style for Maple via @maple-dev/effect-sdk: Maple.layer() bootstrap, Effect.withSpan / Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server / browser / Cloudflare entry points."
---
# Maple Effect style
For Effect apps, use `@maple-dev/effect-sdk`. It wraps Effect's built-in `Otlp.layerJson` exporter and handles batching, shutdown, and resource attributes.
## Install
```bash
npm install @maple-dev/effect-sdk effect
```
The current release requires Effect 4 (`effect >= 4.0.0-rc.113`). For Effect 3, install `@maple-dev/effect-sdk@effect-v3 effect @effect/platform @effect/opentelemetry`. The API and import paths are the same.
## Bootstrap
Pick the entry point per runtime. Each has different lifecycle requirements:
- **Server (Node.js, Bun, Deno):** background-export fiber, env-var auto-detection, graceful shutdown.
- **Browser:** explicit config (no env vars), browser metadata baked in.
- **Cloudflare Workers:** manual `flush()` in `ctx.waitUntil`, lazy env resolution, in-isolate buffering.
### Server
```ts
import { Maple } from "@maple-dev/effect-sdk"
import { Effect } from "effect"
const TracerLive = Maple.layer({
serviceName: "orders-api",
endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
ingestKey: "MAPLE_TEST", // public ingest key (maple_pk_…), or MAPLE_TEST until the user has one
repositoryUrl: "https://github.com/acme/orders-api",
})
const program = Effect.gen(function* () {
yield* Effect.log("Order received")
}).pipe(Effect.withSpan("order.submit"))
Effect.runPromise(program.pipe(Effect.provide(TracerLive)))
```
The default import resolves to the server build under Node.js. Import `@maple-dev/effect-sdk/server` explicitly when needed.
If `endpoint` is omitted, the server layer reads `MAPLE_ENDPOINT`, then `OTEL_EXPORTER_OTLP_ENDPOINT`, then falls back to the public ingest for the region (`https://ingest.maple.dev`, or `https://ingest.eu.maple.dev` with `region: "eu"` / `MAPLE_REGION=eu`). The key falls back to `MAPLE_INGEST_KEY`. `Maple.layer` always exports. A missing ingest key does not disable it, so keyless local-mode and self-hosted-collector setups keep working. Inline the key when telemetry must flow regardless of env (the maple-onboard inline-key pattern). `MapleFlush.make` and the Cloudflare `make()` differ: they no-op without a key.
The server layer also auto-fills `vcs.ref.head.revision` from `COMMIT_SHA` / `RAILWAY_GIT_COMMIT_SHA` / `VERCEL_GIT_COMMIT_SHA` / `CF_PAGES_COMMIT_SHA` / `RENDER_GIT_COMMIT` (first match wins). For `vcs.repository.url.full`, use the `repositoryUrl` option or `MAPLE_REPOSITORY_URL`. Do not hand-write the attribute. The layer also dual-emits `deployment.environment` and `deployment.environment.name` from the `environment` option or `MAPLE_ENVIRONMENT`.
### Cloudflare Workers
The Cloudflare entry point exports `make()`, not a `Maple` namespace. Build the telemetry object **once at module scope**. It buffers in-isolate and resolves `env` lazily on the first flush:
```ts
import * as MapleCloudflareSDK from "@maple-dev/effect-sdk/cloudflare"
import { Effect } from "effect"
const telemetry = MapleCloudflareSDK.make({
serviceName: "orders-edge",
endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
ingestKey: "MAPLE_TEST",
})
export default {
async fetch(req: Request, env: Env, ctx: ExecutionContext) {
const program = Effect.gen(function* () {
yield* Effect.log("edge request")
return new Response("ok")
}).pipe(Effect.withSpan("edge.handle"))
const response = await Effect.runPromise(program.pipe(Effect.provide(telemetry.layer)))
ctx.waitUntil(telemetry.flush(env))
return response
},
}
```
Call `ctx.waitUntil(telemetry.flush(env))` on every request so telemetry survives the isolate exit. `flush` takes `env`. A missing `waitUntil` is the most common reason Worker traces never arrive. When routes go through `HttpRouter.toWebHandler`, provide `telemetry.layer` to the layer you pass it (`Layer.provideMerge(telemetry.layer)`), not to a separate per-request runtime.
### Browser
```ts
import { Maple } from "@maple-dev/effect-sdk/client"
const TracerLive = Maple.layer({
serviceName: "web-client",
endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
ingestKey: "MAPLE_TEST",
})
```
The browser entry point has no env-var fallback. Pass all config explicitly. An explicit `endpoint` wins over `region`, so EU organizations change the endpoint itself (or drop `endpoint` and set `region: "eu"`). It records session replays by default; opt out with `replay: { enabled: false }`.
## Custom spans
Use `Effect.withSpan` to trace operations and `Effect.annotateCurrentSpan` for attributes. Do not reach for the raw `@opentelemetry/api` tracer when an Effect-native primitive exists.
```ts
const processOrder = (orderId: string) =>
Effect.gen(function* () {
yield* Effect.annotateCurrentSpan("order.id", orderId)
const result = yield* chargePayment(orderId)
return result
}).pipe(Effect.withSpan("order.process"))
```
Maple's service map draws a service-to-service edge by joining a Client span to the downstream service's child Server span. `peer.service` does not draw edges. Make outgoing calls through Effect's `HttpClient`: it creates the Client span and injects `traceparent` by default. Calls to uninstrumented dependencies appear as external nodes keyed on `server.address`.
`Effect.fail` and uncaught defects end the span with status `Error` and an `exception` event. Do not wrap with `try` / `catch` / `finally`.
In Effect code, use the Effect-native span primitives, not the raw `@opentelemetry/api` tracer.
## Logs
`Effect.log` inside a span carries the trace context. No extra setup is needed:
```ts
const program = Effect.gen(function* () {
yield* Effect.log("Processing started")
yield* doWork()
yield* Effect.log("Processing complete")
}).pipe(Effect.withSpan("process"))
```
Logs emitted inside spans are correlated with the active trace in the Maple dashboard.
## Coexistence
If the project already exports through `@effect/opentelemetry` or `Otlp.layerJson` to another vendor (Honeycomb, Datadog), keep that exporter. Do not `Layer.merge` `Maple.layer()` with another tracer layer. Effect runs one `Tracer` service, so the last layer wins and the other vendor stops receiving spans. To ship to both, add Maple as a second destination inside the existing setup: a second OTLP span processor on the `@effect/opentelemetry` SDK, or a fan-out in the team's OpenTelemetry Collector. Ask the user before replacing the incumbent exporter.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!