npx skills add ...
npx skills add darkmatter/skills --skill effect-typescript
Effect is a hard default unless the codebase explicitly states it. Use this skill when writing any Typescript code, typed Effect services, database adapters, HTTP handlers, effect-orpc routes, or Effect agent code.
npx skills add darkmatter/skills --skill effect-typescript
Use Effect for application I/O so failures, dependencies, and resource lifetimes are explicit. Adapt a Promise-based driver or SDK once at its boundary; keep internal operations in Effect. Run Effects at the caller or runtime boundary that requires a Promise or starts the program. Pure transformations remain plain TypeScript.
Follow codebase-design for the shared readability rules and examples. Keep related queries, bindings, decoding, and private helpers with their adapter; extract only when it hides complexity or enables useful reuse. Operations own completion, errors, and cleanup, or explicitly hand that responsibility to their caller.
This skill adapts Effect guidance to darkmatter conventions: use Bun commands instead of pnpm for darkmatter projects, and prefer Alchemy for deployable infrastructure.
If effect-solutions is not on in your $PATH, install it: bun add -g effect-solutions@latest. Prefer the repository's pinned Effect source or installed package for API details; use a version-matching checkout when neither is available.
For new Darkmatter TypeScript repos, align dependencies with the
darkmatter/template root catalog. Use Effect 4, @effect/tsgo,
@effect/vitest, @effect/platform-bun, and effect-solutions; prefer the
Effect agent APIs (effect/unstable/ai) before inventing agent loop contracts.
effect-solutions CLI when writing effectful code.Effect.runPromise calls everywhere.bun install, bun test, bun run <script>, bunx <tool>.pnpm examples mechanically. Example: pnpm test file.test.ts becomes bun test file.test.ts when the project uses Bun test, or bun run test file.test.ts when test is a package script.@effect/platform-bun and BunRuntime.runMain for Bun entrypoints.alchemy.run.ts, create resources with Alchemy, bind them to workers/services, and build up an Alchemy.Stack. Refer to the alchemy skill if writing alchemy code or adding new infra code.pg for query-heavy TypeScript adapters, or Effect SQL Postgres when persistence is exposed as Effect services and Layers. D1/sqlite are exceptions for narrow edge-local state, demos, or an explicit project decision.config/<name>.json) read through Config. There is no selector flag: an interactive run picks from a list (Prompt.select), a non-interactive run reads APP_CONFIG or fails listing the names. Env vars and flags are per-key overrides chained with ConfigProvider.orElse; secrets come from the .sops.json sibling via effect-sops. Do not design the setup of a program as a list of flags or env vars. See ADR-0014.Use effect-orpc when a service needs typed RPC procedures, a generated or
shared oRPC client contract, Effect service injection, and typed Effect errors.
Use raw Effect HttpRouter or Hono when the route is simple HTTP, a webhook,
static content, streaming/SSE, or a framework adapter where an RPC contract
would add ceremony without a client-facing type contract.
When adding effect-orpc, pin the Effect 4 build in the catalog:
Do not install effect-orpc@latest; npm latest is the Effect 3 line
(0.5.0). The effect-v4 dist-tag currently resolves to
1.0.0-effect-v4.8, whose peers require Effect 4
(effect >=4.0.0-beta.98) and the oRPC packages above.
Prefer these upstream shapes:
eos.provide(AppLayer) or makeEffectORPC(runtime).
Use a caller-owned ManagedRuntime when a scoped layer should be acquired
once and disposed during application shutdown, such as a Postgres pool,
telemetry SDK, or shared cache..effect(function* (...) { ... })
and generator .use(function* ({ next }) { return yield* next(...) }) so
service requirements, spans, cancellation, and errors stay in Effect.ORPCTaggedError and pass those classes to
.errors(...) / eoc.errors(...); do not duplicate the same error contract
as unrelated thrown exceptions and response schemas.implementEffect(contract, layerOrRuntime) when a repo is contract-first
and already owns an oRPC contract. Contract leaves keep the declared input,
output, and error surface while adding .effect(...) handlers.effect-orpc/node only when Node AsyncLocalStorage is needed to
preserve FiberRefs across native oRPC middleware or framework boundaries.
Use withFiberContext(() => next()) when request-local FiberRef state is
created outside the oRPC pipeline and must be visible inside handlers.See effect-package-map.md for domain ownership, public package entries, adapters, workflows, tests, and runtime boundaries. It describes responsibilities rather than prescribing a file for each function or Effect export.
Use reference/effect or effect-solutions CLI when you need current Effect source or examples instead of relying on memory:
reference/effect/AGENTS.md — upstream repository rules, including pnpm validation commands, generated barrels, changesets, and it.effect conventions.reference/effect/packages/effect/ — core library source and tests.reference/effect/packages/platform-bun/ and reference/effect/packages/platform-node/ — runtime/platform examples.reference/effect/packages/vitest/ — Effect-aware Vitest helpers.If the submodule is missing in a fresh checkout, initialize it before using local references:
For darkmatter application work, treat the submodule as read-only and translate upstream pnpm commands to the repo's package manager. For direct upstream Effect contributions, follow reference/effect/AGENTS.md exactly; do not apply darkmatter Bun defaults inside the upstream repo.
Use Schema.fromJsonString to parse JSON strings and validate them with your schema in one step. This combines JSON.parse + schema decoding in one step, and JSON.stringify + schema encoding for the reverse:
git submodule update --init skills/effect-typescript/reference/effectimport { Effect, Schema } from "effect"
const Row = Schema.Literals(["A", "B", "C", "D", "E", "F", "G", "H"])
const Column = Schema.Literals(["1", "2", "3", "4", "5", "6", "7", "8"])
class Position extends Schema.Class<Position>("Position")({
row: Row,
column: Column,
}) {}
class Move extends Schema.Class<Move>("Move")({
from: Position,
to: Position,
}) {}
// fromJsonString combines JSON.parse + schema decoding
// MoveFromJson is a schema that takes a JSON string and returns a Move
const MoveFromJson = Schema.fromJsonString(Move)
const program = Effect.gen(function* () {
// Parse and validate JSON string in one step
// Use MoveFromJson (not Move) to decode from JSON string
const jsonString = '{"from":{"row":"A","column":"1"},"to":{"row":"B","column":"2"}}'
const move = yield* Schema.decodeUnknownEffect(MoveFromJson)(jsonString)
yield* Effect.log("Decoded move", move)
// Encode to JSON string in one step (typed as string)
// Use MoveFromJson (not Move) to encode to JSON string
const json = yield* Schema.encodeEffect(MoveFromJson)(move)
return json
})