Skip to content
Content
Skill

/use-case-pattern

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.

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill use-case-pattern --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/use-case-pattern

Context 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.

SKILL.md

use-case-pattern.SKILL.md
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.

UseCase Pattern

What It Is

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 Shape

interface SomeUseCase.Interface {
    execute(input: Input): Promise<Result<ReturnType, ErrorType>>;
}
  • **Input** — a typed object specific to the use case
  • **Result** — always returns `Result<T, E>` from `webiny/api`
  • **Error** — extends `BaseError` with a unique `code`

How to Use a UseCase

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]
});

How to Override a UseCase

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: []
});

Registration

**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`

---

Error Handling Pattern

Domain-Specific Errors

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 } });
  }
}

Typed Error Unions in Abstractions

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>>;
}

Result Pattern

// 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.

---

UseCase Implementa

Read more
Ships withwebiny-js

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.

Get the whole plugin
Stats
8,048
Stars
682
Forks
Active
Maintenance
TypeScript
Language
2h ago
Last commit
8y ago
Created
9h ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.