AgentSkillsCN

patterns

参考 Outfitter Stack 的各项约定,包括 Result 类型、Handler 合约、错误分类以及与传输无关的模式。适用于学习该栈、查找模式、理解 @outfitter/* 包,或当提及“Result”、“Handler”、“错误分类”或“OutfitterError”时使用。

SKILL.md
--- frontmatter
name: patterns
version: 0.1.0
description: Reference for Outfitter Stack conventions including Result types, Handler contract, Error taxonomy, and transport-agnostic patterns. Use when learning the stack, looking up patterns, understanding @outfitter/* packages, or when "Result", "Handler", "error taxonomy", or "OutfitterError" are mentioned.
allowed-tools: Read Grep Glob

Outfitter Stack Patterns

Primary reference for @outfitter/* package conventions.

Handler Contract

Handlers are pure functions that:

  • Accept typed input and context
  • Return Result<TOutput, TError>
  • Know nothing about transport (CLI flags, HTTP headers, MCP tool schemas)
typescript
type Handler<TInput, TOutput, TError extends OutfitterError> = (
  input: TInput,
  ctx: HandlerContext
) => Promise<Result<TOutput, TError>>;

Example

typescript
import { Result, NotFoundError, type Handler } from "@outfitter/contracts";

export const getUser: Handler<{ id: string }, User, NotFoundError> = async (input, ctx) => {
  ctx.logger.debug("Fetching user", { userId: input.id });
  const user = await db.users.findById(input.id);

  if (!user) {
    return Result.err(new NotFoundError("user", input.id));
  }
  return Result.ok(user);
};

Why? Testability (just call the function), reusability (same handler for CLI/MCP/HTTP), type safety (explicit types), composability (handlers wrap handlers).

Result Types

Uses Result<T, E> from better-result for explicit error handling.

typescript
import { Result } from "@outfitter/contracts";

// Create
const ok = Result.ok({ name: "Alice" });
const err = Result.err(new NotFoundError("user", "123"));

// Check
if (result.isOk()) {
  console.log(result.value);  // TypeScript knows T
} else {
  console.log(result.error);  // TypeScript knows E
}

// Pattern match
const message = result.match({
  ok: (user) => `Found ${user.name}`,
  err: (error) => `Error: ${error.message}`,
});

// Combine
const combined = combine2(result1, result2);  // tuple or first error

Error Taxonomy

Ten categories map to exit codes and HTTP status:

CategoryExitHTTPWhen to Use
validation1400Invalid input, schema failures
not_found2404Resource doesn't exist
conflict3409Already exists, version mismatch
permission4403Forbidden action
timeout5504Operation took too long
rate_limit6429Too many requests
network7503Connection failures
internal8500Unexpected errors, bugs
auth9401Authentication required
cancelled130499User interrupted (Ctrl+C)
typescript
import { ValidationError, NotFoundError, getExitCode } from "@outfitter/contracts";

new ValidationError("Invalid email", { field: "email" });
new NotFoundError("user", "user-123");

getExitCode(error.category);   // 2 for not_found
getStatusCode(error.category); // 404 for not_found

Validation

Use Zod with createValidator for type-safe validation returning Results:

typescript
import { createValidator } from "@outfitter/contracts";
import { z } from "zod";

const InputSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

const validateInput = createValidator(InputSchema);

// In handler
const inputResult = validateInput(rawInput);
if (inputResult.isErr()) return inputResult;
const input = inputResult.value;  // typed as z.infer<typeof InputSchema>

Context

HandlerContext carries cross-cutting concerns:

typescript
import { createContext } from "@outfitter/contracts";

const ctx = createContext({
  logger: myLogger,           // structured logger
  config: resolvedConfig,     // merged configuration
  signal: controller.signal,  // cancellation
  workspaceRoot: "/project",
});

// ctx.requestId is auto-generated UUIDv7 for tracing
FieldTypeDescription
requestIdstringAuto-generated UUIDv7
loggerLoggerStructured logger
configResolvedConfigMerged config
signalAbortSignalCancellation signal
workspaceRootstringProject root
cwdstringCurrent directory

Bun-First APIs

Prefer Bun-native APIs:

NeedBun API
HashingBun.hash()
GlobbingBun.Glob
SemverBun.semver
ShellBun.$
ColorsBun.color()
String widthBun.stringWidth()
SQLitebun:sqlite
UUID v7Bun.randomUUIDv7()

References