agentsclimarketplace

Workflow usecase

Skill marzun9620/agent_skills/workflow/skills/workflow-usecase

UseCase implementation guide. Define as Effect<A, E, R>; depend on infrastructure through Ports. Make all error types explicit in the E channel. In unit tests, swap Ports via mock Layers. Triggers: usecase creation, business logic, usecase implementation, Effect.gen, Effect.fn, domain logic.From its SKILL.md

Install
npx -y skills add marzun9620/agent_skills --skill workflow-usecase

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.0 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

UseCase 実装手順

ADR-0004 準拠。usecase 層は domain と ports のみに依存。infrastructure/adapter の直接 import 禁止。

Import ルール(重要)

// ✅ 正しい — barrel 経由 + domain/ports のみ
import { Entity, EntityId } from "~/domain/{module}/index.js";
import { EntityRepository, RepositoryError } from "~/usecase/ports/index.js";

// ❌ 禁止 — 直接ファイル import
import { EntityRepository } from "~/usecase/ports/entityRepository.js";

// ❌ 禁止 — infrastructure/adapter への依存
import { DrizzleClient } from "~/infrastructure/db/index.js";
import { usersRoute } from "~/adapter/http/index.js";

1. UseCase 定義

ファイル: apps/datahub/src/usecase/{module}/{usecaseName}.ts

import { Effect } from "effect";
import { Entity, EntityNotFoundError } from "~/domain/{module}/index.js";
import { EntityRepository } from "~/usecase/ports/index.js";
import { RepositoryError } from "~/usecase/ports/index.js";

export const findEntity = Effect.fn("findEntity")(
  (id: EntityId): Effect.Effect<Entity, EntityNotFoundError | RepositoryError, EntityRepository> =>
    Effect.gen(function* () {
      const repo = yield* EntityRepository;
      return yield* repo.findById(id);
    }),
);
  • Effect.fn(name) でラップ(observability 対応)
  • E channel に全エラー型を明示(型推論に頼らない)
  • R channel に必要な Context.Tag を明示
  • yield* PortTag で DI 取得

2. Context Tag(usecase 固有のコンテキストが必要な場合)

ファイル: apps/datahub/src/usecase/{module}/{contextName}.ts

import { Context } from "effect";

export class SomeContext extends Context.Tag("SomeContext")<
  SomeContext,
  {
    readonly someField: string;
    readonly anotherField: number;
  }
>() {}

3. Barrel Export

ファイル: apps/datahub/src/usecase/{module}/index.ts

export { findEntity } from "./findEntity.js";
export { SomeContext } from "./someContext.js";

usecase/index.ts にもモジュールの re-export を追加。

4. Unit Test

ファイル: apps/datahub/tests/usecase/{module}/{usecaseName}.test.ts

import { Effect, Either, Layer } from "effect";
import { EntityRepository } from "~/usecase/ports/index.js";
import { findEntity } from "~/usecase/{module}/index.js";
import { Entity, EntityId, EntityNotFoundError } from "~/domain/{module}/index.js";
import { RepositoryError } from "~/usecase/ports/index.js";

type FindEntityCase = {
  scenario: string;
  entityId: EntityId;
  mockFindById: (id: EntityId) => Effect.Effect<Entity, EntityNotFoundError | RepositoryError>;
  expected: Either.Either<Entity, EntityNotFoundError | RepositoryError>;
};

const successCases: FindEntityCase[] = [
  {
    scenario: "returns entity when found",
    entityId: EntityId.make("test-id"),
    mockFindById: () => Effect.succeed(new Entity({ /* ... */ })),
    expected: Either.right(new Entity({ /* ... */ })),
  },
];

const failureCases: FindEntityCase[] = [
  {
    scenario: "returns error when not found",
    entityId: EntityId.make("missing-id"),
    mockFindById: (id) => Effect.fail(new EntityNotFoundError({ entityId: id })),
    expected: Either.left(new EntityNotFoundError({ entityId: EntityId.make("missing-id") })),
  },
];

it.each([...successCases, ...failureCases])("$scenario", async ({ entityId, mockFindById, expected }) => {
  const layer = Layer.succeed(EntityRepository, { findById: mockFindById });
  const result = await Effect.runPromise(
    Effect.either(findEntity(entityId).pipe(Effect.provide(layer))),
  );
  expect(result).toEqual(expected);
});
  • Port を Layer.succeed で mock
  • Effect.either で Either に変換
  • expect(result).toEqual(expected) で比較
  • Table-driven(.each()
  • AAA パターン

テスト踏襲ルール

実装前に、plan の Ref test に指定された既存テストファイルを読むこと:

  • 基本: tests/usecase/ 内の既存テストを参考にする
  • 構造を合わせる: type 定義、table-driven(.each())、AAA パターン
  • Port は Layer.succeed(PortTag, { method: mockImpl }) で mock
  • Effect.either(usecase(...).pipe(Effect.provide(layer))) パターンを踏襲
  • expect(result).toEqual(expected) を使う

チェックリスト

  • domain と usecase/ports のみに依存(infrastructure/adapter の import なし)
  • E channel に全エラー型を明示
  • R channel に必要な Context.Tag を明示
  • Effect.fn(name) でラップ
  • Unit test で Port を mock Layer で差し替え
  • barrel export が存在
  • any を使っていない

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,367. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.