martinffx/typescript-skills · Archived

typescript-functional-patterns

Selective use of functional TypeScript patterns and domain-model responsibilities.

First seen Aug 2, 2026

Installation

$ npx skills add martinffx/typescript-skills --skill typescript-functional-patterns

Summary

  • Selective use of functional TypeScript patterns and domain-model responsibilities.
  • Use when a task involves a state machine, discriminated union, Option/Result/Either/Effect API, branded or opaque type, typed domain decoding, or deciding which layer should own construction and transformation.
  • Do not use to add a domain model when existing schemas and types already cover the requirement.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from martinffx/typescript-skills.

npx skills add martinffx/typescript-skills

Browse all from martinffx/typescript-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,856 B
  • docs SUMMARY.md 427 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 6 installs

SKILL.md

Selective functional patterns

Functional patterns solve specific modeling defects. They are not a default architecture or an extra layer to place around working project types.

Start with the owning boundary

Follow this order and stop at the first step that satisfies the task:

  1. Reuse the owning boundary's existing schema, generated type, library type, or

public contract.

  1. Infer types with the installed tool's supported utilities.
  2. Compose, refine, or extend the canonical schema or type.
  3. Improve the representation at its owner when the task exposes a real gap.
  4. Introduce one minimal custom type only when the hard gate below is satisfied.

Do not continue down the list once the existing representation can express the requirement safely.

Canonical owners

  • TypeBox schemas own their request and response shapes. Use

Static<typeof Schema> and TypeBox composition rather than handwritten mirrors.

  • Drizzle tables own their persistence shapes. Use typeof table.$inferSelect,

typeof table.$inferInsert, or the inference convention already used by the package.

  • DynamoDB Toolbox entities own their item shapes. Use InputItem<typeof Entity>,

FormattedItem<typeof Entity>, and the entity's item schema.

  • Effect Schema and other installed schema libraries own their inferred types,

refinements, parse errors, and tagged schemas.

  • Installed functional libraries own their Option, Result, Either, and

effect types, constructors, and matching conventions.

  • Existing nullable, throwing, promise-based, generated, and plain TypeScript

contracts remain canonical when the project already uses them.

Keep responsibilities with their owner

  • Domain models own construction, invariant-preserving transformations, and

typed decoding. Established models may expose fromRequest, Drizzle fromRow/toRow, or DynamoDB fromItem/toItem methods.

  • Repositories own I/O: queries, transactions, tenant and optimistic-write

predicates, and database or driver error classification.

  • Services own use-case orchestration, including sequencing repositories and

deciding whether to retry a failed operation.

  • Routes own HTTP validation and response serialization.

Keep pure invariant functions pure. Do not impose an import-purity rule on an entire domain/ directory. A domain model may import request, Drizzle row, or DynamoDB Toolbox item types when type-only imports prevent duplicate mirror types. It may also construct an Effect value to represent typed success, failure, or absence when doing so does not execute the Effect or reach external state.

Domain models must not import Fastify handlers, database clients, DynamoDB commands, SQL builders, Layers, runtime execution, configuration, or environment access. Those dependencies belong at the application or repository boundary.

Do not add a middle model

Do not place a handwritten tagged, branded, or class-based model between an API schema and a persistence schema merely to rename fields or repeat validation. When no domain model is justified, convert at the active boundary without inventing one.

Different boundaries may legitimately have different types. A TypeBox request, a Drizzle row, and a DynamoDB item do not need a third universal domain type to connect them. Reuse an existing behavior-rich domain entity when the package already has one, but do not create an entity class solely to wrap a row or item.

Hard gate for a custom type

Add a custom tagged union, brand, opaque type, Option, or Result only when all of these conditions hold:

  1. The changed code contains a concrete defect, invalid state, or unsafe

interchange that the type should prevent.

  1. The owning schema, installed library, and current project types cannot express

the distinction through composition, refinement, literals, constraints, or their native error model.

  1. The new type prevents the defect instead of restating validation or giving an

existing value another name.

  1. One boundary can own construction and validation consistently.
  2. The type does not introduce routine adapters, duplicate serializers, or a

second representation across callers, persistence, and tests.

If any condition fails, keep the existing representation.

Choose the smallest representation

  • Use a literal union such as "pending" | "settled" before wrapping each value

in a tagged object.

  • Use a discriminated union when variants carry different data or when it removes

a demonstrated invalid combination of fields.

  • Use a brand only when values with the same primitive representation remain easy

to confuse after applying the existing schema and library tools.

  • Use the established nullable or failure contract before considering Option or

Result.

  • Keep runtime validation in the owning schema. A custom compile-time type must

not replace boundary validation.

Validation, nullability, recoverable failure, identifiers, and domain terminology do not by themselves justify a custom type.

Working method

  1. Inspect imports, package dependencies, schemas, generated types, public

contracts, and immediate callers.

  1. Name the specific unsafe state or operation required by the task.
  2. Reuse or extend the highest existing owner that can prevent it.
  3. Put construction and persistence transformations on an established domain

model; otherwise keep conversion at the active boundary.

  1. Test the changed behavior using the package's existing test utilities.

References

Read only the reference needed for the active problem:

  • [ADTs](./references/adts.md) for deciding between literal and discriminated

unions

  • [Option and Result](./references/option-result.md) for absence and failure

contracts

  • [Branded types](./references/branded-types.md) for nominal distinctions and

units

  • [Migration guide](./references/migration-guide.md) for a focused change to an

existing codebase

Review checklist

  • The owning schema, library, or project type was identified first.
  • Schema-derived and generated types remain canonical at their boundaries.
  • Domain, repository, service, and route responsibilities remain separate.
  • No unnecessary middle model or generic functional helper was added.
  • Every new custom type passes the hard gate.
  • The change fixes the named problem without spreading a second representation.