一键导入
effect-service
Effect service definition, interface design, error types, retries. Use when creating new services or defining error hierarchies.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Effect service definition, interface design, error types, retries. Use when creating new services or defining error hierarchies.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
AXM - Agent Extension Manager: Use for any operation (install/create/new/edit/update/add/remove/delete/publish/find/discover) on agent skills, subagents, slash commands/stored prompts, MCP servers, context packages, rule extensions, hook extensions, or packs — e.g. "create a skill", "make a /command", "add a subagent", "build an MCP server", "publish an extension". Use this BEFORE hand-authoring or editing any SKILL.md, slash-command, subagent, MCP, rule, hook, or extension manifest file: route extension authoring through AXM instead of writing these files directly.
Native skill manifest with two unknown top-level keys.
Effect CLI + Effect architecture. Use when adding commands, defining flags, or wiring handlers. Covers file organization, argument/flag patterns, and testing.
"unterminated
Native skill with an invalid extension name in manifest.
Native skill whose manifest has an invalid owner handle.
| name | effect-service |
| description | Effect service definition, interface design, error types, retries. Use when creating new services or defining error hierarchies. |
| user-invocable | false |
Apply these patterns when designing Effect services in this codebase.
See /effect-layers for layer construction, composition, and provision.
For services with a single implementation, define the interface inline on
the ServiceMap.Service class. This is concise and gives you full Layer-based
dependency injection and testability.
class NotificationService extends ServiceMap.Service<
NotificationService,
{
readonly send: (to: string, message: string) => Effect.Effect<void, AppError>;
readonly sendBatch: (
notifications: ReadonlyArray<Notification>,
) => Effect.Effect<void, AppError>;
}
>()("axm.sh/NotificationService") {}
The service class decouples consumers from implementations — you get testability and composability without additional abstraction.
Appropriate for:
Use a separate interface when a service genuinely has multiple implementations that must conform to a shared contract.
// The contract
interface DocumentStore {
readonly get: (id: DocumentId) => Effect.Effect<Document, DocumentNotFound>;
readonly put: (doc: Document) => Effect.Effect<void, StoreError>;
readonly delete: (id: DocumentId) => Effect.Effect<void, StoreError>;
}
// The service, typed to the interface
const DocumentStore = ServiceMap.Service<DocumentStore>("axm.sh/DocumentStore");
// Implementation A
const S3DocumentStoreLayer = Layer.succeed(DocumentStore, {/* ... */});
// Implementation B
const PostgresDocumentStoreLayer = Layer.succeed(DocumentStore, {/* ... */});
This pattern is warranted when:
Ask: does this service have, or will it concretely have, more than one implementation?
ServiceMap.Service<Shape>(id).Avoid speculative interfaces. The Layer system makes it cheap to introduce one later, so let actual requirements drive the decision.
Service methods must not leak implementation dependencies. Dependencies belong in the layer, not the service interface.
// BAD: dependency leaks into the interface
interface MyService {
readonly query: (sql: string) => Effect.Effect<Row[], Error, Config | Logger>;
}
// GOOD: dependencies resolved at layer construction
interface MyService {
readonly query: (sql: string) => Effect.Effect<Row[], Error>;
}
See /effect-layers for how to capture dependencies in layers while keeping
R = never.
Services should not expose mutable state. Use readonly on all properties.
interface CounterService {
readonly increment: () => Effect.Effect<void>;
readonly get: () => Effect.Effect<number>;
}
Each service handles one domain concern. If a service has methods spanning multiple concerns, split it.
Design leaf service interfaces before implementations. This lets you model higher-level orchestration that type-checks immediately.
// 1. Leaf services: contracts only (no implementation yet)
class Users extends ServiceMap.Service<
Users,
{ readonly findById: (id: UserId) => Effect.Effect<User, UserNotFound> }
>()("@app/Users") {}
class Tickets extends ServiceMap.Service<
Tickets,
{ readonly issue: (eventId: EventId, userId: UserId) => Effect.Effect<Ticket> }
>()("@app/Tickets") {}
// 2. Orchestration service: uses leaf contracts
class Events extends ServiceMap.Service<
Events,
{ readonly register: (eventId: EventId, userId: UserId) => Effect.Effect<Registration> }
>()("@app/Events") {
static readonly layer = Layer.effect(
Events,
Effect.gen(function* () {
const users = yield* Users;
const tickets = yield* Tickets;
return Events.of({
register: (eventId, userId) =>
Effect.gen(function* () {
const user = yield* users.findById(userId);
const ticket = yield* tickets.issue(eventId, userId);
return { user, ticket };
}),
});
}),
);
}
Benefits:
Use axm.sh/<ServiceName> as the identifier string:
ServiceMap.Service<Workspace, {/* ... */}>()("axm.sh/Workspace");
ServiceMap.Service<SourceHostProviders, {/* ... */}>()("axm.sh/SourceHostProviders");
See /effect-layers for full naming conventions and module structure.
| Suffix | Usage |
|---|---|
layer | Production layer |
layerTest | Test / fake layer |
layerMemory | In-memory variant |
layerDev | Development / local variant |
Yield the service to access it in an effect:
const program = Effect.gen(function* () {
const ws = yield* Workspace;
const sources = yield* SourceHostProviders;
const refs = yield* sources.find(source);
});
// Effect<..., ..., Workspace | SourceHostProviders>
The R parameter automatically tracks required services as a union type.
Use Data.TaggedError for pattern matching and structural equality:
export class ApiError extends Data.TaggedError("ApiError")<{
readonly status?: number;
readonly message: string;
readonly retryable: boolean;
}> {}
Map external errors at boundaries:
const mapApiError = (error: unknown): ApiError => {
if (error instanceof ExternalApiError) {
return new ApiError({
status: error.status,
message: error.message,
retryable: error.status === 429 || error.status >= 500,
});
}
return new ApiError({
message: `Unexpected error: ${String(error)}`,
retryable: false,
});
};
Data.TaggedErrorunknownDefine retry behavior using Schedule:
const retryPolicy = Schedule.exponential(Duration.seconds(1)).pipe(
Schedule.intersect(Schedule.recurs(3)),
Schedule.whileInput((error: ApiError) => error.retryable),
);
const doSomething = (input: string) =>
Effect.tryPromise({
try: () => api.call(input),
catch: mapApiError,
}).pipe(Effect.retry(retryPolicy));
retryable fieldSchedule.recurs(n) to limit attemptsSchedule.exponential for external callsSchedule.whileInput for retryable onlyaxm.sh/<ServiceName>