home / skills / andrueandersoncs / claude-skill-effect-ts / platform

platform skill

/skills/platform

This skill helps you understand and use Effect platform abstractions for HTTP, filesystem, key-value storage, and terminal I/O across environments.

npx playbooks add skill andrueandersoncs/claude-skill-effect-ts --skill platform

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.8 KB
---
name: Platform
description: This skill should be used when the user asks about "@effect/platform", "Effect HTTP client", "Effect HTTP server", "FileSystem", "KeyValueStore", "Terminal", "platform services", "HttpClient", "HttpServer", "Effect file operations", "Effect networking", or needs to understand Effect's platform-agnostic I/O capabilities.
version: 1.0.0
---

# Effect Platform

## Overview

`@effect/platform` provides cross-platform abstractions for:

- **HTTP Client** - Make HTTP requests
- **HTTP Server** - Build HTTP servers
- **FileSystem** - File operations
- **KeyValueStore** - Persistent storage
- **Terminal** - CLI interactions
- **Worker** - Background workers

## HTTP Client

### Installation

```bash
npm install @effect/platform
# For Node.js:
npm install @effect/platform-node
# For Bun:
npm install @effect/platform-bun
```

### Basic Requests

```typescript
import { HttpClient } from "@effect/platform"
import { NodeHttpClient } from "@effect/platform-node"
import { Effect } from "effect"

const program = Effect.gen(function* () {
  const client = yield* HttpClient.HttpClient

  // GET request
  const response = yield* client.get("https://api.example.com/users")
  const data = yield* response.json

  return data
}).pipe(
  Effect.provide(NodeHttpClient.layer)
)
```

### Request Configuration

```typescript
const program = Effect.gen(function* () {
  const client = yield* HttpClient.HttpClient

  // POST with body
  const response = yield* client.post("https://api.example.com/users", {
    body: HttpClientRequest.jsonBody({ name: "Alice", email: "[email protected]" })
  })

  // With headers
  const response = yield* client.get("https://api.example.com/protected", {
    headers: { Authorization: "Bearer token123" }
  })

  // With timeout
  const response = yield* client.get("https://api.example.com/slow").pipe(
    Effect.timeout("5 seconds")
  )
})
```

### Response Handling

```typescript
const program = Effect.gen(function* () {
  const client = yield* HttpClient.HttpClient
  const response = yield* client.get("https://api.example.com/data")

  // Parse as JSON
  const json = yield* response.json

  // Parse as text
  const text = yield* response.text

  // Get status
  const status = response.status

  // Get headers
  const contentType = response.headers["content-type"]
})
```

### Schema Validation

```typescript
import { HttpClient, HttpClientResponse } from "@effect/platform"
import { Schema } from "effect"

const User = Schema.Struct({
  id: Schema.Number,
  name: Schema.String,
  email: Schema.String
})

const program = Effect.gen(function* () {
  const client = yield* HttpClient.HttpClient
  const response = yield* client.get("https://api.example.com/users/1")

  // Validate response with schema
  const user = yield* HttpClientResponse.schemaBodyJson(User)(response)
  return user
})
```

## HTTP Server

### Basic Server

```typescript
import { HttpServer, HttpServerResponse } from "@effect/platform"
import { NodeHttpServer } from "@effect/platform-node"
import { Effect, Layer } from "effect"
import { createServer } from "node:http"

const app = HttpServer.router.empty.pipe(
  HttpServer.router.get("/", HttpServerResponse.text("Hello, World!")),
  HttpServer.router.get("/users", HttpServerResponse.json([
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" }
  ]))
)

const ServerLive = NodeHttpServer.layer(createServer, { port: 3000 })

const program = Effect.gen(function* () {
  yield* Effect.log("Server starting on port 3000")
  yield* Effect.never // Keep running
}).pipe(
  Effect.provide(HttpServer.router.Live(app)),
  Effect.provide(ServerLive)
)
```

### Route Parameters

```typescript
const app = HttpServer.router.empty.pipe(
  HttpServer.router.get(
    "/users/:id",
    Effect.gen(function* () {
      const params = yield* HttpServer.router.params
      const id = params.id
      return HttpServerResponse.json({ id, name: "User " + id })
    })
  )
)
```

### Request Body

```typescript
import { HttpServerRequest } from "@effect/platform"

const app = HttpServer.router.empty.pipe(
  HttpServer.router.post(
    "/users",
    Effect.gen(function* () {
      const request = yield* HttpServerRequest.HttpServerRequest
      const body = yield* request.json

      // Or with schema validation
      const validated = yield* HttpServerRequest.schemaBodyJson(CreateUser)(request)

      return HttpServerResponse.json({ created: true, user: validated })
    })
  )
)
```

## FileSystem

### Reading Files

```typescript
import { FileSystem } from "@effect/platform"
import { NodeFileSystem } from "@effect/platform-node"

const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem

  const content = yield* fs.readFileString("./config.json")

  const bytes = yield* fs.readFile("./image.png")

  const exists = yield* fs.exists("./file.txt")
}).pipe(
  Effect.provide(NodeFileSystem.layer)
)
```

### Writing Files

```typescript
const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem

  yield* fs.writeFileString("./output.txt", "Hello, World!")

  yield* fs.writeFile("./data.bin", new Uint8Array([1, 2, 3]))

  yield* fs.appendFileString("./log.txt", "New log entry\n")
})
```

### Directory Operations

```typescript
const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem

  yield* fs.makeDirectory("./new-dir", { recursive: true })

  const files = yield* fs.readDirectory("./src")

  yield* fs.remove("./temp", { recursive: true })

  yield* fs.copy("./source.txt", "./dest.txt")

  yield* fs.rename("./old.txt", "./new.txt")
})
```

### File Info

```typescript
const program = Effect.gen(function* () {
  const fs = yield* FileSystem.FileSystem

  const stat = yield* fs.stat("./file.txt")

  console.log({
    size: stat.size,
    isFile: stat.type === "File",
    isDirectory: stat.type === "Directory",
    modified: stat.mtime
  })
})
```

## KeyValueStore

Persistent key-value storage:

```typescript
import { KeyValueStore } from "@effect/platform"
import { NodeKeyValueStore } from "@effect/platform-node"

const program = Effect.gen(function* () {
  const store = yield* KeyValueStore.KeyValueStore

  yield* store.set("user:1", JSON.stringify({ name: "Alice" }))

  const value = yield* store.get("user:1")

  yield* store.remove("user:1")

  const exists = yield* store.has("user:1")
}).pipe(
  Effect.provide(NodeKeyValueStore.layerFileSystem("./data"))
)
```

## Terminal

CLI interactions:

```typescript
import { Terminal } from "@effect/platform"
import { NodeTerminal } from "@effect/platform-node"

const program = Effect.gen(function* () {
  const terminal = yield* Terminal.Terminal

  const name = yield* terminal.readLine

  yield* terminal.display(`Hello, ${name}!`)
}).pipe(
  Effect.provide(NodeTerminal.layer)
)
```

## Complete Example: REST API

```typescript
import { HttpServer, HttpServerResponse, HttpServerRequest } from "@effect/platform"
import { NodeHttpServer } from "@effect/platform-node"
import { Effect, Layer, Schema } from "effect"
import { createServer } from "node:http"

// Schemas
const CreateUser = Schema.Struct({
  name: Schema.String,
  email: Schema.String
})

const User = Schema.Struct({
  id: Schema.Number,
  name: Schema.String,
  email: Schema.String
})

// In-memory store
let users: Schema.Schema.Type<typeof User>[] = []
let nextId = 1

// Routes
const app = HttpServer.router.empty.pipe(
  HttpServer.router.get("/users", HttpServerResponse.json(users)),

  HttpServer.router.post("/users", Effect.gen(function* () {
    const body = yield* HttpServerRequest.schemaBodyJson(CreateUser)
    const user = { id: nextId++, ...body }
    users.push(user)
    return HttpServerResponse.json(user, { status: 201 })
  })),

  HttpServer.router.get("/users/:id", Effect.gen(function* () {
    const { id } = yield* HttpServer.router.params
    const user = users.find(u => u.id === parseInt(id))
    return user
      ? HttpServerResponse.json(user)
      : HttpServerResponse.json({ error: "Not found" }, { status: 404 })
  }))
)

// Server
const ServerLive = NodeHttpServer.layer(createServer, { port: 3000 })

const main = HttpServer.serve(app).pipe(
  Effect.provide(ServerLive),
  Effect.catchAllCause(Effect.logError)
)

Effect.runPromise(main)
```

## Best Practices

1. **Use Schema validation** - Validate all external data
2. **Provide platform layers** - NodeHttpClient.layer, etc.
3. **Handle errors** - Network failures, file not found, etc.
4. **Use Effect.scoped for resources** - Files, connections
5. **Configure timeouts** - Prevent hanging requests

## Additional Resources

For comprehensive platform documentation, consult `${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt`.

Search for these sections:
- "HTTP Client" for making requests
- "HTTP Server" for building servers
- "FileSystem" for file operations
- "KeyValueStore" for persistent storage
- "Terminal" for CLI interactions

Overview

This skill explains @effect/platform, a platform-agnostic set of I/O abstractions for Effect programs. It covers HTTP client and server APIs, filesystem and key-value storage, terminal I/O, and worker/background capabilities across Node, Bun, and other runtimes. Use it to learn how to wire platform layers, validate inputs, and manage resources safely with Effect.

How this skill works

The skill describes Effect services (HttpClient, HttpServer, FileSystem, KeyValueStore, Terminal) as dependency-injected capabilities accessed inside Effect.gen or Effect effects. Concrete runtime implementations are provided via layers (for example NodeHttpClient.layer or NodeFileSystem.layer) so the same application code runs across environments. It also shows common patterns: making requests, handling responses, schema validation, file operations, and serving routes with request/body access.

When to use it

  • You need to make HTTP requests or call external APIs from an Effect program.
  • You are building an HTTP server using Effect's router and response builders.
  • You want cross-platform file operations (read, write, stat, copy, remove) in Effect code.
  • You need simple persistent storage via a KeyValueStore.
  • You are implementing CLI input/output or background workers within Effect.

Best practices

  • Always provide the appropriate platform layer for the runtime (Node/Bun) before running effects.
  • Validate all external input and responses with Schema to avoid runtime shape errors.
  • Scope resources (files, connections) with Effect.scoped or managed layers to ensure cleanup.
  • Configure request timeouts and handle network/file errors explicitly.
  • Keep server handlers pure effects and return HttpServerResponse builders for clarity.

Example use cases

  • Call third-party REST APIs with HttpClient, parse JSON, and validate against a Schema.
  • Build a REST API with HttpServer.router, route params, and schema-validated request bodies.
  • Implement file-based configuration and content storage using FileSystem read/write/exists.
  • Store user session or cache data in KeyValueStore with get/set/remove semantics.
  • Create CLI tools that prompt users and display results using Terminal and NodeTerminal.layer.

FAQ

How do I make the same code work in Node and Bun?

Write code against the generic services (HttpClient, FileSystem, etc.) and provide the runtime-specific layer (NodeHttpClient.layer or Bun equivalent) when running the effect.

How should I validate HTTP responses?

Parse the response (response.json) and then validate with Schema; use provided helpers like HttpClientResponse.schemaBodyJson to combine parsing and validation.