stack-patterns

📁 outfitter-dev/agents 📅 Jan 29, 2026
1
总安装量
1
周安装量
#46046
全站排名
安装命令
npx skills add https://github.com/outfitter-dev/agents --skill stack-patterns

Agent 安装分布

github-copilot 1

Skill 文档

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)
type Handler<TInput, TOutput, TError extends OutfitterError> = (
  input: TInput,
  ctx: HandlerContext
) => Promise<Result<TOutput, TError>>;

Example

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.

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:

Category Exit HTTP When to Use
validation 1 400 Invalid input, schema failures
not_found 2 404 Resource doesn’t exist
conflict 3 409 Already exists, version mismatch
permission 4 403 Forbidden action
timeout 5 504 Operation took too long
rate_limit 6 429 Too many requests
network 7 503 Connection failures
internal 8 500 Unexpected errors, bugs
auth 9 401 Authentication required
cancelled 130 499 User interrupted (Ctrl+C)
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:

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:

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
Field Type Description
requestId string Auto-generated UUIDv7
logger Logger Structured logger
config ResolvedConfig Merged config
signal AbortSignal Cancellation signal
workspaceRoot string Project root
cwd string Current directory

Package Reference

Package Purpose When to Use
@outfitter/contracts Result types, errors, Handler contract Always (foundation)
@outfitter/types Type utilities, collection helpers Type manipulation
@outfitter/cli CLI commands, output modes, formatting CLI applications
@outfitter/mcp MCP server, tool registration, Zod schemas AI agent tools
@outfitter/config XDG paths, config loading, env handling Configuration needed
@outfitter/logging Structured logging, sinks, redaction Logging needed
@outfitter/daemon Background services, IPC, health checks Long-running services
@outfitter/file-ops Secure paths, atomic writes, file locking File operations
@outfitter/state Pagination, cursor state Paginated data
@outfitter/testing Test harnesses, fixtures, Bun test Testing

Selection guidance:

  • All projects start with @outfitter/contracts
  • CLI apps add @outfitter/cli (includes UI components)
  • MCP servers add @outfitter/mcp
  • Projects with config add @outfitter/config
  • File operations need @outfitter/file-ops for safety

Type Utilities

@outfitter/types provides collection helpers and type utilities:

Collection Helpers

import { sortBy, dedupe, chunk } from "@outfitter/types";

// Sort by property
const users = [{ name: "Bob" }, { name: "Alice" }];
sortBy(users, "name");         // [{ name: "Alice" }, { name: "Bob" }]
sortBy(users, u => u.name);    // Same, with accessor function

// Remove duplicates
dedupe([1, 2, 2, 3, 3, 3]);    // [1, 2, 3]
dedupe(users, u => u.name);    // Dedupe by property

// Split into chunks
chunk([1, 2, 3, 4, 5], 2);     // [[1, 2], [3, 4], [5]]

Type Utilities

Standard TypeScript utility types for common patterns:

import type { Prettify, DeepPartial, Nullable } from "@outfitter/types";

// Prettify: Flatten complex intersection types for better IntelliSense
type Combined = { a: string } & { b: number };
type Pretty = Prettify<Combined>;  // Shows { a: string; b: number }

// DeepPartial: Make all properties optional recursively
type Config = { db: { host: string; port: number } };
type PartialConfig = DeepPartial<Config>;

// Nullable: T | null
type MaybeUser = Nullable<User>;

Domain Error Mapping

Map your domain errors to the 10 taxonomy categories:

Domain Error Stack Category Error Class Exit HTTP
Not found not_found NotFoundError 2 404
Invalid input validation ValidationError 1 400
Already exists conflict ConflictError 3 409
No permission permission PermissionError 4 403
Auth required auth AuthError 9 401
Timed out timeout TimeoutError 5 504
Connection failed network NetworkError 7 503
Limit exceeded rate_limit RateLimitError 6 429
Bug/unexpected internal InternalError 8 500
User cancelled cancelled CancelledError 130 499

Mapping examples:

// "User not found" -> NotFoundError
new NotFoundError("user", userId);

// "Invalid email format" -> ValidationError
new ValidationError("Invalid email", { field: "email" });

// "User already exists" -> ConflictError
new ConflictError("Email already registered", { email });

// "Cannot delete admin" -> PermissionError
new PermissionError("Cannot delete admin users");

// Unexpected errors -> InternalError
new InternalError("Database connection failed", { cause: error });

Bun-First APIs

Prefer Bun-native APIs:

Need Bun API
Hashing Bun.hash()
Globbing Bun.Glob
Semver Bun.semver
Shell Bun.$
Colors Bun.color()
String width Bun.stringWidth()
SQLite bun:sqlite
UUID v7 Bun.randomUUIDv7()

References

Core Patterns

Package Deep Dives