jpskill.com
🛠️ 開発・MCP コミュニティ

meta-design-expressive-typescript

読みやすい関数型プログラミングのパターン、例えばオーケストレーターや純粋関数、名前付き抽象などを活用し、TypeScriptで表現力豊かなメタデザインを効率的に実現するSkill。

📜 元の英語説明(参考)

Readable functional patterns — orchestrators, pure functions, named abstractions

🇯🇵 日本人クリエイター向け解説

一言でいうと

読みやすい関数型プログラミングのパターン、例えばオーケストレーターや純粋関数、名前付き抽象などを活用し、TypeScriptで表現力豊かなメタデザインを効率的に実現するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o meta-design-expressive-typescript.zip https://jpskill.com/download/10256.zip && unzip -o meta-design-expressive-typescript.zip && rm meta-design-expressive-typescript.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10256.zip -OutFile "$d\meta-design-expressive-typescript.zip"; Expand-Archive "$d\meta-design-expressive-typescript.zip" -DestinationPath $d -Force; ri "$d\meta-design-expressive-typescript.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して meta-design-expressive-typescript.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → meta-design-expressive-typescript フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

表現力豊かな TypeScript

クイックガイド: コードを読む人がその一部を頭の中でシミュレーションする必要なく、意図が伝わるコードを書きましょう。二層パターンを適用します。最上位には擬似コードのように読めるオーケストレーター、最下位にはそれぞれが1つのことを行う純粋関数を配置します。コードが散文のように読めるまで抽出します。ユーティリティライブラリは、プレーンな JS よりも本当に可読性が向上する場合にのみ使用します。


<critical_requirements>

重要: このスキルを使用する前に

すべてのコードは、CLAUDE.md のプロジェクト規約に従う必要があります (kebab-case、名前付きエクスポート、インポート順序、import type、名前付き定数)

(すべての重要でない関数は、二層オーケストレーターとして構成する必要があります: 最上部にガード句、中央に名前付き関数呼び出し、最下部に組み立て -- オーケストレーター内でのインラインデータ変換は行わないこと)

(理解するために頭の中でシミュレーションが必要な式は、名前付き関数または名前付き定数に抽出する必要があります)

(関数には、HOW (どのように) ではなく、WHAT (何を) するかを名前を付ける必要があります -- checkLineStartsWithPlusButNotTriplePlus(line) ではなく isContentAddition(line))

(リファクタリングする前に既存のコードを必ず読んでください -- 現在の構造を理解してから、改善してください)

(プレーンな JS メソッド (.map().filter().reduce()) がすでに明確に読める場合は、ユーティリティライブラリよりも優先する必要があります)

</critical_requirements>


自動検出: orchestrator pattern、two-tier function、extract function、named predicate、named constant、readability refactor、expressive code、function decomposition、pure function extraction、readable TypeScript、guard clause、early return、flatten conditionals、discriminated union、exhaustive switch、as const satisfies、async orchestrator

使用する場合:

  • バリデーション、変換、および組み立てロジックを混在させる関数を作成する場合
  • フローを理解するためにステップを頭の中でシミュレーションする必要がある関数をリファクタリングする場合
  • 意図を伝えるために、述語、定数、または変換に名前を付ける場合
  • ユーティリティライブラリ関数またはプレーンな JS のどちらを使用するかを決定する場合
  • 大きな関数をオーケストレーター + 純粋なヘルパーに分解する場合
  • コードをレビューし、頭の中でシミュレーションが必要なブロックを見つける場合
  • 深くネストされた if/else ブロックをガード句に平坦化する場合
  • 複数の独立した操作を調整する非同期関数を作成する場合
  • 排他的な処理のために判別共用体で状態またはイベントをモデル化する場合

使用しない場合:

  • すでに明確な単純な1行の関数を作成する場合
  • 学術的な関数型プログラミング (モナド、ファンクター、Either/Option 型)
  • 引数が暗黙的なポイントフリースタイル
  • 自明に単純な式を名前付き関数に過剰に抽出する場合
  • 関数呼び出しのオーバーヘッドが重要なパフォーマンスが重要なホットパス

カバーされる主要なパターン:

  • 二層パターン (オーケストレーター + 純粋関数)
  • 可読性テスト ("シミュレーションせずに理解できますか?")
  • 名前付き述語、定数、および変換
  • ガード句: 早期リターンによるネストされた条件の平坦化
  • 型安全な制御フローのための判別共用体 + 完全なスイッチ
  • 独立した操作のための Promise.all を使用した非同期オーケストレーター
  • 意図を明らかにする構成のための as const satisfies
  • 抽出の意思決定フレームワーク
  • ユーティリティライブラリの使用法: 80/20 ルール

詳細なリソース

  • examples/core.md - 二層パターン、ガード句、名前付き述語、名前付き定数、判別共用体、非同期オーケストレーター
  • examples/data-transforms.md - データ変換パターン、プレーンな JS で十分な場合、ユーティリティライブラリが役立つ場合
  • reference.md - 意思決定テーブルを含むクイックリファレンスチートシート

<philosophy>

哲学

表現力豊かな TypeScript は、実用的で、可読性に焦点を当てた 80/20 の関数型プログラミングです。コードのあらゆるブロックに対するコアテスト:

"コードを読む人は、その一部をシミュレーションせずにコードのフローを理解できますか?"

答えがノーの場合は、シミュレーションが必要な部分を名前付き関数または定数に抽出します。答えがイエスの場合、理論的には「よりクリーン」になる可能性がある場合でも、そのままにしておきます。

これは以下ではありません:

  • モナド、ファンクター、または Either/Option 型 -- それらは別のスキルに属します
  • ポイントフリースタイル -- 暗黙的な引数は、ほとんどの読者にとって意図を不明瞭にします
  • 宗教的な関数型の純粋さ -- オーケストレーターでの副作用は問題ありません。重要なのは、その下にある純粋関数です
  • 過剰な抽出 -- 3つの類似したコード行は、時期尚早な抽象化よりも優れています

2つのコアアイデア:

  1. オーケストレーターは擬似コードのように読めます。 ガード句、名前付き関数呼び出し、組み立て。シミュレーションが必要なインラインロジックはありません。
  2. 純粋関数は1つのことを行います。 それぞれに、その目的を伝える名前が付いています。それぞれが独立してテスト可能です。

このスキルを適用する場合:

  • 関心が混在する約15行を超える関数
  • 読者が意図を理解するためにロジックをたどる必要がある式
  • 説明的な名前がない繰り返されるロジックパターン

適用しない場合:

  • すでに明確に読める単一の .map() または .filter()
  • すでに1つの抽象化レベルにある関数
  • 抽出がノイズを追加する自明に単純なコード

</philosophy>


<patterns>

コアパターン

パターン 1: 二層パターン

すべての重要でない関数は、同じ構造に従います。最上位には擬似コードのように読めるオーケストレーター、最下位にはそれぞれが1つのことを行う純粋関数を配置します。

オーケストレーター (最上位)


// オーケストレーター: ステップごとの計画のように読めます
function processUserImport(rawData: RawImportData): ImportResult {
  // 1. ガード句
  if (!rawData.users.length) {
    return { imported: 0, skipped: 0, errors: [] };
  }

  // 2. 各ステップの名前付き関数呼び出し
  const validated = rawData.users.filter(isValidUser);
  const normalized = validated.map(normalizeUserRecord);
  const deduped = removeDuplicatesByEmail(normalized);
  const { existing, newUsers } = sep

(原文はここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Expressive TypeScript

Quick Guide: Write code that communicates its intent without requiring the reader to mentally simulate any of its parts. Apply the two-tier pattern: orchestrators at the top that read like pseudocode, pure functions at the bottom that each do one thing. Extract until the code reads like prose. Use utility libraries only when they genuinely improve readability over plain JS.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST structure every non-trivial function as a two-tier orchestrator: guard clauses at the top, named function calls in the middle, assembly at the bottom -- NO inline data transformations in orchestrators)

(You MUST extract any expression that requires mental simulation to understand into a named function or named constant)

(You MUST name functions for WHAT they do, not HOW they do it -- isContentAddition(line) not checkLineStartsWithPlusButNotTriplePlus(line))

(You MUST read the existing code before refactoring -- understand the current structure, then improve it)

(You MUST prefer plain JS methods (.map(), .filter(), .reduce()) over utility libraries when they already read clearly)

</critical_requirements>


Auto-detection: orchestrator pattern, two-tier function, extract function, named predicate, named constant, readability refactor, expressive code, function decomposition, pure function extraction, readable TypeScript, guard clause, early return, flatten conditionals, discriminated union, exhaustive switch, as const satisfies, async orchestrator

When to use:

  • Writing any function that mixes validation, transformation, and assembly logic
  • Refactoring a function where you need to mentally simulate steps to understand the flow
  • Naming predicates, constants, or transforms to communicate intent
  • Deciding whether to use a utility library function or plain JS
  • Decomposing a large function into orchestrator + pure helpers
  • Reviewing code and finding blocks that require mental simulation
  • Flattening deeply nested if/else blocks into guard clauses
  • Writing async functions that orchestrate multiple independent operations
  • Modeling state or events with discriminated unions for exhaustive handling

When NOT to use:

  • Writing simple one-liner functions that are already clear
  • Academic functional programming (monads, functors, Either/Option types)
  • Point-free style where arguments are implicit
  • Over-extracting trivially simple expressions into named functions
  • Performance-critical hot paths where function call overhead matters

Key patterns covered:

  • The two-tier pattern (orchestrator + pure functions)
  • The readability test ("can you understand without simulating?")
  • Named predicates, constants, and transforms
  • Guard clauses: flattening nested conditionals with early returns
  • Discriminated unions + exhaustive switch for type-safe control flow
  • Async orchestrators with Promise.all for independent operations
  • as const satisfies for intent-revealing configuration
  • The extraction decision framework
  • Utility library usage: the 80/20 rule

Detailed Resources

  • examples/core.md - Two-tier pattern, guard clauses, named predicates, named constants, discriminated unions, async orchestrators
  • examples/data-transforms.md - Data transformation patterns, when plain JS is enough, when utility libraries help
  • reference.md - Quick-reference cheat sheet with decision tables

<philosophy>

Philosophy

Expressive TypeScript is practical, 80/20 functional programming focused on readability. The core test for any block of code:

"Can someone understand the code's flow without simulating any of its parts?"

If the answer is no, extract the part that requires simulation into a named function or constant. If the answer is yes, leave it alone -- even if it could theoretically be "cleaner."

This is NOT:

  • Monads, functors, or Either/Option types -- those belong in a different skill
  • Point-free style -- implicit arguments obscure intent for most readers
  • Religious functional purity -- side effects in orchestrators are fine; the pure functions underneath are what matter
  • Over-extraction -- three similar lines of code is better than a premature abstraction

The two core ideas:

  1. Orchestrators read like pseudocode. Guard clauses, named function calls, assembly. No inline logic that requires simulation.
  2. Pure functions do one thing. Each has a name that communicates its purpose. Each is independently testable.

When to apply this skill:

  • Any function longer than ~15 lines that mixes concerns
  • Any expression where a reader would need to trace through logic to understand intent
  • Any repeated logic pattern that lacks a descriptive name

When NOT to apply:

  • A single .map() or .filter() that already reads clearly
  • Functions that are already one level of abstraction
  • Trivially simple code where extraction would add noise

</philosophy>


<patterns>

Core Patterns

Pattern 1: The Two-Tier Pattern

Every non-trivial function follows the same structure: an orchestrator at the top that reads like pseudocode, calling pure functions at the bottom that each do one thing.

The Orchestrator (Top Tier)

// Orchestrator: reads like a step-by-step plan
function processUserImport(rawData: RawImportData): ImportResult {
  // 1. Guard clauses
  if (!rawData.users.length) {
    return { imported: 0, skipped: 0, errors: [] };
  }

  // 2. Named function calls for each step
  const validated = rawData.users.filter(isValidUser);
  const normalized = validated.map(normalizeUserRecord);
  const deduped = removeDuplicatesByEmail(normalized);
  const { existing, newUsers } = separateExistingUsers(deduped);

  // 3. Assembly
  return {
    imported: newUsers.length,
    skipped: existing.length,
    errors: rawData.users.length - validated.length,
  };
}

Why good: Each line communicates intent through its function name. A reader knows WHAT happens at each step without reading HOW any step works. Guard clauses are at the top. No inline lambdas obscure the flow.

The Pure Functions (Bottom Tier)

// Pure function: one job, named for purpose
function isValidUser(user: RawUser): boolean {
  return user.email.includes("@") && user.name.trim().length > 0;
}

function normalizeUserRecord(user: RawUser): NormalizedUser {
  return {
    email: user.email.toLowerCase().trim(),
    name: user.name.trim(),
    role: user.role ?? DEFAULT_ROLE,
  };
}

function removeDuplicatesByEmail(users: NormalizedUser[]): NormalizedUser[] {
  const seen = new Set<string>();
  return users.filter((user) => {
    if (seen.has(user.email)) return false;
    seen.add(user.email);
    return true;
  });
}

Why good: Each function does one thing, has a descriptive name, and is testable in isolation without mocks or state setup.

Full before/after examples: See examples/core.md for complete orchestrator transformations.


Pattern 2: The Readability Test

Before extracting or refactoring, apply this test to any block of code:

"Can someone understand the code's flow without mentally simulating any of its parts?"

// Fails the readability test -- requires simulation
const result = items
  .filter(
    (item) =>
      item.status === "active" &&
      item.createdAt > cutoffDate &&
      !excludedIds.has(item.id),
  )
  .map((item) => ({
    ...item,
    displayName: item.firstName + " " + item.lastName,
    age: Math.floor((Date.now() - item.birthDate.getTime()) / MS_PER_YEAR),
  }));

Why bad: A reader must mentally execute the filter predicate and the map transform to understand what this produces. The intent is buried in implementation.

// Passes the readability test -- intent is clear from names
const activeItems = items.filter(isActiveAfterCutoff);
const displayRecords = activeItems.map(toDisplayRecord);

Why good: Each step communicates intent. A reader knows the filter selects active items after a cutoff and the map creates display records -- without reading either function's body.

When the Test Says "Leave It Alone"

// Already passes -- don't over-extract
const names = users.map((user) => user.name);
const activeUsers = users.filter((user) => user.isActive);
const total = prices.reduce((sum, price) => sum + price, 0);

Why good: These are already clear single-expression operations. Extracting getName, isActive, or sumPrices would add indirection without improving clarity.


Pattern 3: Named Abstractions

When an expression requires simulation, give it a name that communicates intent.

Named Predicates

// Before: requires simulation to understand filter criteria
const lines = diff.filter(
  (line) => line.startsWith("+") && !line.startsWith("+++"),
);

// After: name communicates intent
const lines = diff.filter(isContentAddition);

function isContentAddition(line: string): boolean {
  return line.startsWith("+") && !line.startsWith("+++");
}

Why good: isContentAddition tells the reader WHAT is being checked. The implementation (startsWith logic) is available but not required to understand the flow.

Named Constants

// Before: magic setup with no context
const skill = createMockSkill("react", {
  conflictsWith: ["vue", "angular"],
  requires: ["typescript"],
});

// After: name communicates purpose
const REACT_WITH_FRAMEWORK_CONFLICTS = createMockSkill("react", {
  conflictsWith: ["vue", "angular"],
  requires: ["typescript"],
});

Why good: When this constant appears in a test, the reader knows its purpose without scrolling to its definition. The name encodes the relevant characteristics.

Named Transforms

// Before: inline formatting logic obscures orchestrator flow
const message = results
  .map((r) =>
    r.success ? `  Installed ${r.name}` : `  Failed ${r.name}: ${r.error}`,
  )
  .join("\n");

// After: name communicates intent
const message = results.map(formatInstallResult).join("\n");

function formatInstallResult(result: InstallResult): string {
  if (result.success) return `  Installed ${result.name}`;
  return `  Failed ${result.name}: ${result.error}`;
}

Why good: The orchestrator reads as "format each result, join with newlines." The formatting details are available but not in the way.

More examples: See examples/core.md for before/after named abstraction patterns.


Pattern 4: The Extraction Decision Framework

Not every expression should be extracted. Use this decision tree:

Does this block require mental simulation to understand?
|-- NO -> Leave it inline. Don't over-extract.
+-- YES -> Does the same logic appear in 2+ places?
    |-- YES -> Extract to shared function (DRY + readability).
    +-- NO -> Would a named function communicate intent the expression doesn't?
        |-- YES -> Extract. The name adds value.
        +-- NO -> Leave it. Extraction would just move code without adding clarity.

Extract: Block Requires Simulation

// Before: simulation required to understand what this produces
const categories = Object.entries(skills).reduce<Record<string, Skill[]>>(
  (acc, [id, skill]) => {
    const cat = skill.category;
    if (!acc[cat]) acc[cat] = [];
    acc[cat].push(skill);
    return acc;
  },
  {},
);

// After: intent is clear from the name
const categories = groupSkillsByCategory(skills);

Don't Extract: Already Clear

// Already clear -- extracting would just move one line
const ids = skills.map((skill) => skill.id);
const hasConflicts = conflicts.length > 0;
const displayName = `${firstName} ${lastName}`;

Why good: These are trivially understandable. A function named getIds, checkHasConflicts, or buildDisplayName would add indirection without improving readability.

Don't Extract: Name Would Be As Complex As Implementation

// Don't extract this:
const isSelected = selectedIds.has(item.id);

// Because the extraction would be:
function isItemSelected(item: Item, selectedIds: Set<string>): boolean {
  return selectedIds.has(item.id);
}
// The name doesn't communicate more than the expression itself

Pattern 5: Guard Clauses (Flatten Nested Conditionals)

Deeply nested if/else blocks force a reader to maintain a mental stack of conditions. Guard clauses invert conditions and return early, keeping the happy path un-nested.

// Flat: each guard exits early, happy path has zero nesting
function getDiscount(user: User, cart: Cart): number {
  if (!user.isActive) return 0;
  if (cart.items.length === 0) return 0;
  if (!user.membership) return STANDARD_DISCOUNT;

  return calculateMemberDiscount(user.membership, cart.total);
}

Why good: Each guard clause handles one concern and exits. The reader never needs to track which else branch they are in. The final line is the happy path, visible at a glance.

Full before/after transformation: See examples/core.md for deeply nested code flattened to guard clauses.


Pattern 6: Discriminated Unions + Exhaustive Switch

When a value can be one of several states, a discriminated union with an exhaustive switch makes the type system enforce that every case is handled. Adding a new variant causes a compile error wherever handling is missing.

type TaskStatus = "pending" | "running" | "completed" | "failed";

function getStatusMessage(status: TaskStatus): string {
  switch (status) {
    case "pending":
      return "Waiting to start";
    case "running":
      return "In progress...";
    case "completed":
      return "Done";
    case "failed":
      return "Something went wrong";
    default: {
      const _exhaustive: never = status;
      return _exhaustive;
    }
  }
}

Why good: The never default guarantees compile-time exhaustiveness. If a fifth status is added to the union, this switch will error until the new case is handled.

Full pattern with discriminated object unions: See examples/core.md for the complete discriminated union pattern.


Pattern 7: Async Orchestrators

The two-tier pattern applies equally to async code. Async orchestrators await named functions in sequence for dependent steps and use Promise.all for independent steps.

async function onboardNewUser(input: OnboardInput): Promise<OnboardResult> {
  const user = await createUserRecord(input);

  // Independent operations -- run in parallel
  const [profile, settings] = await Promise.all([
    initializeProfile(user.id, input.preferences),
    applyDefaultSettings(user.id),
  ]);

  await sendWelcomeEmail(user.email, profile.displayName);

  return { user, profile, settings };
}

Why good: The orchestrator reads as a step-by-step plan. Independent operations are grouped in Promise.all -- signaling to the reader that they don't depend on each other. Each called function is a pure async operation named for its purpose.

Full before/after example: See examples/core.md for an async function decomposed from mixed-concern code.


Pattern 8: as const satisfies for Configuration

When defining static configuration objects, as const satisfies Shape preserves literal types (for autocompletion and typeof/keyof usage) while validating the shape at compile time.

interface RouteConfig {
  path: string;
  auth: boolean;
}

const ROUTES = {
  home: { path: "/", auth: false },
  dashboard: { path: "/dashboard", auth: true },
  settings: { path: "/settings", auth: true },
} as const satisfies Record<string, RouteConfig>;

// Type of ROUTES.home.path is "/", not string
// typeof ROUTES gives you the full literal structure for keyof usage

Why good: The satisfies operator catches typos and missing fields at compile time. The as const preserves exact literal types so downstream code benefits from precise inference. Without satisfies, a typo like { pth: "/" } would silently pass.

</patterns>


<decision_framework>

Decision Framework

When to Apply the Two-Tier Pattern

Is this function > ~15 lines?
|-- NO -> Is it mixing multiple concerns (validate + transform + assemble)?
|   |-- YES -> Apply two-tier pattern
|   +-- NO -> Leave it. Short single-concern functions are fine as-is.
+-- YES -> Does it have clear logical steps?
    |-- YES -> Apply two-tier pattern: orchestrator calls named functions
    +-- NO -> Consider breaking into separate functions by responsibility first

When to Use a Utility Library vs Plain JS

Is this a single .map(), .filter(), or .reduce()?
|-- YES -> Use plain JS. A utility library adds dependency without clarity.
+-- NO -> Is the operation a known concept (group-by, count-by, set-difference)?
    |-- YES -> Does the utility library name match the concept?
    |   |-- YES -> Use the library. groupBy(skills, s => s.category) is
    |   |          clearer than a manual reduce.
    |   +-- NO -> Use plain JS with a named function.
    +-- NO -> Are there 3+ chained transformations?
        |-- YES -> Consider pipe() if intermediate variables would obscure flow.
        +-- NO -> Use plain JS with named intermediate variables.

The 80/20 Rule for Utility Libraries

Use utility functions when the function name IS the documentation:

Use utility library Use plain JS
groupBy(items, fn) -- groups by key items.map(fn) -- single transform
countBy(items, fn) -- counts per category items.filter(fn) -- single filter
difference(a, b) -- set subtraction items.find(fn) -- single lookup
partition(items, fn) -- split into two items.some(fn) / items.every(fn)
indexBy(items, fn) -- array to lookup map items.reduce(fn, init) -- simple sum
pipe(data, ...fns) -- 3+ chained ops One or two chained .map().filter() calls

Full data transformation examples: See examples/data-transforms.md

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • An orchestrator function contains inline lambdas longer than a single expression -- extract to named functions
  • A function mixes guard clauses, data transformation, side effects, and return assembly without clear separation
  • Variable names describe implementation (filteredMappedItems) instead of intent (activeDisplayRecords)
  • Code uses .reduce() to build a lookup object when groupBy or indexBy communicates intent directly
  • Deeply nested if/else blocks (3+ levels) instead of guard clauses with early returns
  • A switch on a union type is missing the default: never exhaustiveness check -- adding a new variant will silently fall through

Medium Priority Issues:

  • Using a utility library for a single .map() or .filter() that already reads clearly
  • Extracting trivially simple expressions into named functions (over-extraction)
  • Using pipe() for one or two operations where a variable would be clearer
  • Named constants that are as complex as what they replace

Common Mistakes:

  • Applying point-free style (items.filter(isActive)) when the predicate needs context that point-free obscures -- use an explicit lambda if the predicate needs closure variables
  • Extracting a function that is used exactly once and whose name is just a restatement of the single line it contains
  • Creating a "utils" file that becomes a dumping ground -- group extracted functions by domain, near their callers

Gotchas & Edge Cases:

  • A well-named inline lambda IS a named abstraction: .filter((user) => user.isActive) is already clear because isActive is self-documenting. Don't extract this to a separate function.
  • Utility library pipe() can HURT readability when the reader doesn't know the library -- consider your team's familiarity
  • Guard clauses should return early, not wrap the entire function body in an if block
  • When extracting pure functions, place them at the bottom of the file or in a shared module -- not interspersed with the orchestrators they serve
  • as const satisfies Shape -- order matters. satisfies Shape as const does not work. Lock literals first, then validate shape.
  • In async orchestrators, sequential await on independent operations is a hidden performance bug -- use Promise.all when steps don't depend on each other's results
  • The never exhaustiveness trick requires noUnusedLocals to not flag the variable -- use return _exhaustive instead of just assigning to avoid lint warnings

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST structure every non-trivial function as a two-tier orchestrator: guard clauses at the top, named function calls in the middle, assembly at the bottom -- NO inline data transformations in orchestrators)

(You MUST extract any expression that requires mental simulation to understand into a named function or named constant)

(You MUST name functions for WHAT they do, not HOW they do it -- isContentAddition(line) not checkLineStartsWithPlusButNotTriplePlus(line))

(You MUST read the existing code before refactoring -- understand the current structure, then improve it)

(You MUST prefer plain JS methods (.map(), .filter(), .reduce()) over utility libraries when they already read clearly)

Failure to follow these rules will produce code that requires mental simulation to understand -- the exact opposite of expressive TypeScript.

</critical_reminders>