grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
UseCase implementation pattern — DI, Result handling, error types, decorators, CMS repositories, entry mappers, and schema-based permissions. Use this skill to implement, inject, override, or decorate any Webiny UseCase, or to build repositories that persist data via CMS.
$ npx -y skills add webiny/webiny-js --skill use-case-pattern --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/use-case-patternContext preview
The summary Claude sees to decide when to auto-load this skill.
UseCase implementation pattern — DI, Result handling, error types, decorators, CMS repositories, entry mappers, and schema-based permissions. Use this skill to implement, inject, override, or decorate any Webiny UseCase, or to build repositories that persist data via CMS.
name: webiny-use-case-pattern description: > UseCase implementation pattern — DI, Result handling, error types, decorators, CMS repositories, entry mappers, and schema-based permissions. Use this skill to implement, inject, override, or decorate any Webiny UseCase, or to build repositories that persist data via CMS.
A **UseCase** is a single-method orchestrator that encapsulates one business operation (e.g., `CreateTenantUseCase`, `PublishEntryUseCase`). Each UseCase is a DI abstraction with an `execute` method that returns `Result<T, E>`.
interface SomeUseCase.Interface {
execute(input: Input): Promise<Result<ReturnType, ErrorType>>;
}UseCases are injected as dependencies into EventHandlers, other UseCases, or GraphQL resolvers via DI.
import { SomeUseCase } from "webiny/api/<category>";
import { SomeEventHandler } from "webiny/api/<category>";
class MyHandler implements SomeEventHandler.Interface {
constructor(private someUseCase: SomeUseCase.Interface) {}
async handle(event: SomeEventHandler.Event) {
const result = await this.someUseCase.execute({/* input */});
if (result.isFail()) {
console.error(result.error.message);
return;
}
const value = result.value;
// ... use value
}
}
export default SomeEventHandler.createImplementation({
implementation: MyHandler,
dependencies: [SomeUseCase]
});To replace the default implementation, register your own:
import { SomeUseCase } from "webiny/api/<category>";
class CustomImplementation implements SomeUseCase.Interface {
async execute(input) {
// Custom logic
return Result.ok(/* ... */);
}
}
export default SomeUseCase.createImplementation({
implementation: CustomImplementation,
dependencies: []
});**YOU MUST include the full file path with the `.ts` extension in the `src` prop.** For example, use `src={"@/extensions/my-extension.ts"}`, NOT `src={"@/extensions/my-extension"}`. Omitting the file extension will cause a build failure.
**YOU MUST use `export default` for the `createImplementation()` call** when the file is targeted directly by an Extension `src` prop. Using a named export (`export const Foo = SomeFactory.createImplementation(...)`) will cause a build failure. Named exports are only valid inside files registered via `createFeature`.
// In your app's configuration
<Api.Extension src={"@/extensions/my-extension.ts"} />Deploy with: `yarn webiny deploy api --env=dev`
---
Every feature defines errors extending `BaseError`. Never use generic `Error` for validation or business rule failures.
// domain/errors.ts
import { BaseError } from "webiny/api";
export class EntityNotFoundError extends BaseError {
override readonly code = "Entity/NotFound" as const;
constructor(id: string) {
super({ message: `Entity with id "${id}" was not found!` });
}
}
export class EntityPersistenceError extends BaseError<{ error: Error }> {
override readonly code = "Entity/Persist" as const;
constructor(error: Error) {
super({ message: error.message, data: { error } });
}
}
export class EntityValidationError extends BaseError<{ message: string }> {
override readonly code = "Entity/Validation" as const;
constructor(message: string) {
super({ message, data: { message } });
}
}Define an `IErrors` interface mapping error names to types, then create a union via `[keyof IErrors]`:
// features/createEntity/abstractions.ts
import { createAbstraction, Result } from "webiny/api";
import { NotAuthorizedError } from "webiny/api/security";
import {
EntityPersistenceError,
EntityModelNotFoundError,
EntityCreationError
} from "~/api/domain/errors.js";
// REPOSITORY errors
export interface ICreateEntityRepositoryErrors {
persistence: EntityPersistenceError;
modelNotFound: EntityModelNotFoundError;
creation: EntityCreationError;
}
type RepositoryError = ICreateEntityRepositoryErrors[keyof ICreateEntityRepositoryErrors];
export interface ICreateEntityRepository {
execute(entity: Entity): Promise<Result<Entity, RepositoryError>>;
}
export const CreateEntityRepository = createAbstraction<ICreateEntityRepository>(
"MyExt/CreateEntityRepository"
);
export namespace CreateEntityRepository {
export type Interface = ICreateEntityRepository;
export type Error = RepositoryError;
export type Return = Promise<Result<Entity, RepositoryError>>;
}
// USE CASE errors — superset of repository errors
export interface ICreateEntityUseCaseErrors {
persistence: EntityPersistenceError;
modelNotFound: EntityModelNotFoundError;
creation: EntityCreationError;
notAuthorized: NotAuthorizedError;
}
type UseCaseError = ICreateEntityUseCaseErrors[keyof ICreateEntityUseCaseErrors];
export interface ICreateEntityUseCase {
execute(input: CreateEntityInput): Promise<Result<Entity, UseCaseError>>;
}
export const CreateEntityUseCase = createAbstraction<ICreateEntityUseCase>(
"MyExt/CreateEntityUseCase"
);
export namespace CreateEntityUseCase {
export type Interface = ICreateEntityUseCase;
export type Input = CreateEntityInput;
export type Error = UseCaseError;
export type Return = Promise<Result<Entity, UseCaseError>>;
}// Success
return Result.ok(value);
// Failure
return Result.fail(new EntityNotFoundError(id));
// Check result
if (result.isFail()) {
return Result.fail(result.error);
}
// Access value
const value = result.value;Never use `result.isError()`, `result.getError()`, or `result.getValue()` — these do not exist.
---
Open-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.
Repo: webiny/webiny-js
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Turn a PRD into a multi-phase implementation plan using tracer-bullet vertical slices, saved…
Webiny-only. Run all checks required before packages are ready for publish: deps, build,…
Use when running tests. Shows how to run tests for a single package, including OpenSearch…
Generate, refresh, and maintain Webiny MCP server skills from source documentation and…
Create a PRD through user interview, codebase exploration, and module design, then submit as…