#Effect Services
HyperKit ships first-class Effect services for common runtime concerns. Each service is a typed layer you compose into your Effect runtime — no singletons, no hidden globals, and every failure mode is a typed error.
| Service | What it does |
|---|---|
LoggingService |
Structured log stream with transports, enrichment, redaction, and sampling |
SessionService |
Create, list, activate, and remove named sessions |
WebSocketService |
Auto-reconnecting WebSocket with typed message stream |
FileSystemService |
Directory listing, parent resolution, and isDirectory checks |
ClipboardService |
Copy/paste with typed ClipboardError |
Exported error types: WebSocketError, WebSocketConnectionError, SessionNotFoundError, SessionCreationError, FileSystemError, DirectoryNotFoundError, ClipboardError, ApiError.
#LoggingService
interface LoggingService {
readonly stream: Stream.Stream<LogEntry>;
readonly getHistory: Effect.Effect<ReadonlyArray<LogEntry>>;
readonly clear: Effect.Effect<void>;
}Create a layer with makeLoggingLayer:
import { makeLoggingLayer, ConsoleTransport, HttpTransport } from '@ybouhjira/hyperkit';
const LoggingLayer = makeLoggingLayer({
maxHistory: 500,
context: { app: 'my-app', env: 'production' },
redact: ['token', 'password'], // masked as [REDACTED]
sampling: { rate: 0.1 }, // 10% of sessions sent to external transports
transports: [
ConsoleTransport({ format: 'pretty' }),
HttpTransport({ url: 'https://logs.example.com/ingest' }),
],
});The pipeline runs enrichment → redaction → sampling before entries reach transports.
Built-in transports: ConsoleTransport (pretty / json / logfmt), HttpTransport (batched POST), BeaconTransport (navigator.sendBeacon on page unload), SentryTransport (accepts any SentryLike interface). Build custom transports with the SimpleTransport / ScopedTransport wrappers.
LogEntry fields: id, timestamp, level, message, fiberId, spans, annotations, cause.
Subscribe from SolidJS components with useLogger:
import { useLogger } from '@ybouhjira/hyperkit';
const { entries, latest, active, stop } = useLogger(loggingService.stream);#SessionService
interface SessionService {
readonly create: (params: CreateSessionParams) => Effect.Effect<Session, SessionCreationError>;
readonly get: (id: string) => Effect.Effect<Session, SessionNotFoundError>;
readonly list: Effect.Effect<ReadonlyArray<Session>>;
readonly remove: (id: string) => Effect.Effect<void, SessionNotFoundError>;
readonly setActive: (id: string) => Effect.Effect<void, SessionNotFoundError>;
readonly getActive: Effect.Effect<Session | null>;
}Session: { id, name, workingDirectory, createdAt, model? }.
#WebSocketService
interface WebSocketService {
readonly connect: (url: string) => Effect.Effect<void, WebSocketConnectionError>;
readonly disconnect: Effect.Effect<void, WebSocketError>;
readonly send: (message: WsMessage) => Effect.Effect<void, WebSocketError>;
readonly messages: Stream.Stream<WsMessage, WebSocketError>;
readonly isConnected: Effect.Effect<boolean>;
}import { Effect, Stream } from 'effect';
import { WebSocketService } from '@ybouhjira/hyperkit';
const program = Effect.gen(function* () {
const ws = yield* WebSocketService;
yield* ws.connect('wss://api.example.com/ws');
yield* Stream.runForEach(ws.messages, (msg) => handleMessage(msg));
});#FileSystemService
interface FileSystemService {
readonly listDirectory: (
path: string
) => Effect.Effect<ReadonlyArray<FileEntry>, FileSystemError | DirectoryNotFoundError>;
readonly getParentDirectory: (path: string) => Effect.Effect<string>;
readonly isDirectory: (path: string) => Effect.Effect<boolean, FileSystemError>;
}FileEntry: { name, path, isDirectory, size?, modifiedAt? }. This service backs FileExplorer and DirectoryPicker.
#ClipboardService
interface ClipboardService {
readonly copy: (text: string) => Effect.Effect<void, ClipboardError>;
readonly paste: Effect.Effect<string, ClipboardError>;
}#Bridging Effect and SolidJS
Two hooks connect Effect programs to SolidJS reactivity — see Hooks:
createEffectResource(effectFn)—{ data, error, loading, refetch }createEffectStream(stream)—{ items, latest, error, active, stop }
Both fork fibers with Effect.runFork and interrupt them on component cleanup.