| name | coding-ts |
| description | Enforces TypeScript + Effect functional/ROP style, type discipline, polymorphic surfaces, and module organization standards. Use when writing, editing, reviewing, refactoring, or debugging .ts/.tsx modules, implementing domain models, Effect services, persistence adapters, or boundary handlers, or configuring TypeScript, Effect, or lint/type-check posture. |
[H1][CODING-TS]
Dictum: TypeScript + Effect style, type discipline, and module organization govern all TS work.
All code follows five governing principles:
- Polymorphic โ one entrypoint per concern, generic over specific, extend over duplicate
- Functional + ROP โ pure pipelines, typed error rails, monadic composition
- Strongly typed โ inference-first, one canonical shape per concept, zero
any/unknown leakage
- Programmatic โ variable-driven dispatch, bounded vocabularies, zero stringly-typed routing
- Algorithmic โ drive functionality through transforms, folds, and discriminant projection; reduce branching to composable pipelines
Paradigm
- Immutability:
S.Class copy-update transitions, Ref for managed mutable state, zero let in domain code. Effect data structures (HashMap, HashSet, Chunk, List) over JS stdlib (Map, Set, Array) โ structural sharing, referential transparency, and Equal/Hash integration by default. JS stdlib collections only at system boundaries (FFI, serialization)
- Typed error channels:
Data.TaggedEnum for file-internal errors (never exported), class extends Data.TaggedError for cross-cutting domain errors (polymorphic, few per system), composed via mapError/catchTag/catchTags
- Exhaustive dispatch: vocabulary-driven dispatch (
Record lookup) for keyed domains โ vocabulary objects are the sole dispatch mechanism when a domain is keyed by string/enum; Match is reserved for structural/predicate matching on non-keyed shapes only. When a vocabulary object defines thresholds/tiers, classification iterates or indexes the vocabulary โ never reimplements the vocabulary's knowledge as Match.when chains
- Type anchoring: use
S.Class/Model.Class for external codecs, persisted models, and domain authorities; use inferred plain objects for internal config/state when no runtime authority is needed. Derive projections via pick/omit/partial/extend, never parallel structs
- Expression control flow:
pipe + monadic combinators (map, flatMap, tap, filterOrFail), zero statement branching
- Programmatic logic: bounded vocabulary objects as discriminant sources,
Record-driven dispatch, zero stringly-typed routing
- Private integration: module logic is the export's implementation, not its neighbor โ
_-prefixed internals are closures, scoped captures, or inline compositions inside the exported class/service/function, not standalone module-level declarations consumed by a single caller
- Surface ownership: 1โ2 exports per module; every non-exported symbol carries
_ prefix; one polymorphic entrypoint per concern, no helpers, no extraction, no method-family inflation
- Cross-cutting composition:
Layer + Effect.Service for DI, Effect.withSpan/Effect.annotateLogs for observability
Conventions
Effect is the sole ecosystem โ no third-party alternatives for concerns Effect owns.
One library's types per module boundary. Bridge at layer edges via Schema decode/encode.
Effect data structures are the default โ JS stdlib equivalents are boundary-only:
HashMap/HashSet over Map/Set โ structural equality via Equal/Hash, persistent updates via HashMap.set returning new map
Chunk over Array for streaming contexts โ Chunk.append/Chunk.concat are O(1) amortized
Array module (import { Array as A } from "effect") over native .map/.filter/.reduce โ richer combinators (A.getSomes, A.match, A.groupBy, A.dedupeWith)
Option over null/undefined โ Option.fromNullable at boundaries, Option<T> in domain
Either over throw โ Either.right/Either.left for pure branching without Effect overhead
Duration over raw milliseconds โ Duration.seconds(5) not 5000
Order/Equivalence over custom comparators โ composable via Order.struct, Order.combine
Predicate module over inline boolean expressions โ Predicate.not, Predicate.and, Predicate.or
Function.pipe/Function.flow over manual composition
STM/TMap/TRef over mutable state for concurrent contexts
Record module over Object.keys/Object.entries/Object.fromEntries
Struct module over manual object picking/omitting
Selection rules โ when multiple Effect types compete:
HashMap for accumulator state in Stream.mapAccum โ never new Map() with .set() mutation
Chunk for Stream operations (Sink, mapAccum emissions, batching) โ ReadonlyArray for small fixed collections
Option for any absence in domain code โ null only at JSON serialization boundary
Duration for timeouts, delays, schedule intervals, vocabulary policy fields โ raw number only for arithmetic
STM/TMap for shared state across fibers โ HashMap for immutable accumulator threaded through mapAccum/reduce โ Ref for single-fiber mutable state
Contracts
Type discipline
- One canonical runtime authority per boundary concept:
S.Class/Model.Class for decoded or persisted concepts; inferred plain objects for internal config/state. Derive all projections (pick/omit/partial/extend), never parallel S.Struct variants.
- Search existing shapes before creating new ones โ extend or modify fields over declaring fresh schemas.
- Avoid module-level
type/interface when inference from runtime declarations suffices.
- No parallel schemas/brands/types for the same domain concept.
Control flow
- Zero
if/else/switch/for/while/try/catch/throw in domain transforms.
- Expression dispatch:
Match.valueTags/Match.tagsExhaustive for closed tagged domains, monadic combinators elsewhere.
- Boundary adapters may use required statement forms with explicit marker:
// BOUNDARY ADAPTER โ reason.
Error handling
Data.TaggedEnum for file-internal errors โ bounded discriminants, never exported, never crosses module boundaries.
class extends Data.TaggedError for domain-level errors โ polymorphic, boundary-crossing, co-located in the owning folder/package (no dedicated error files). Few per system (1-3 typical).
- Error classes and enums belong in the
[ERRORS] section โ never in [SCHEMA]. Schema defines data shapes; errors define failure modes. Even when an error uses Schema internally, its declaration site is [ERRORS].
- Domain error classes carry polymorphic/agnostic logic reusable across all call sites.
- One canonical
reason โ policy projection table per domain error class โ zero inline status/retry/transport literals outside it.
- Decode unknown input at boundaries, map unknown causes immediately into bounded tagged errors.
Surface
- Private-by-default: every non-exported symbol (values, functions, AND types) carries
_ prefix. Module exports 1โ2 symbols maximum โ no exceptions. Branded primitives integrate into the owning class/service (fields, static factories), never exported as standalone module-level symbols.
- Internal logic integrates INTO exports โ closures inside scoped constructors, inline compositions inside pipe chains, static methods on classes. Not defined alongside as standalone module-level functions consumed by a single caller. Exception: pure data transforms serving as semantic anchors (algorithm implementations, reusable projections) may remain module-level when no scoped constructor exists; they must still carry
_ prefix and serve 2+ call sites.
- Pipeline factory closure pattern: when a module has no
Effect.Service (stateless pipeline, pure transform), the exported factory function IS the scoped constructor โ it captures shared state (vocabularies, metrics, config) in its closure and returns a composed pipeline. Single-caller helper functions still inline into this factory rather than floating at module level.
- One polymorphic entrypoint per concern โ no
run/runSafe/runV2 family inflation.
- No helper files, no single-caller extracted functions, no module-level one-use
const values except _-prefixed private anchors (vocabularies, schedules, metrics).
- No convenience wrappers that rename or forward external APIs.
- Final review gate: before completing any module, verify (1) no
_-prefixed symbol is exported โ if it needs the export, remove the _ prefix at source; if it does not, remove the export, (2) every non-exported symbol carries _ prefix, (3) no const X = _X re-export aliasing โ fix the name at declaration site, never alias a private symbol to create a public one.
~350 LOC scrutiny threshold โ investigate for compression via polymorphism, not file splitting.
Resources
- Resource lifecycle through
Effect.acquireRelease.
- Retry, timeout, concurrency policy via
Schedule, Effect.forEach, Stream โ declarative only.
- Zero hidden global state, zero untracked ambient dependencies.
Load sequence
Foundation (always):
| Reference | Focus |
|---|
| patterns.md | Cross-boundary integration contracts |
Task-routed references:
| Reference | Load when |
|---|
| types.md | Type derivation, compression, inference |
| objects.md | Schema/Class/Model boundary work |
| effects.md | Effect pipelines, ROP, composition |
| matching.md | Exhaustive expression control flow |
| errors.md | Error construction, architecture, policy |
| transforms.md | Folds, projections, pipeline strategies |
| surface.md | Public API creation and refinement |
| composition.md | Layer and module boundary composition |
| services.md | Service topology and dependency strategy |
| persistence.md | SQL/model boundary work |
| concurrency.md | Streams, fibers, bounded concurrency |
| observability.md | Logging, tracing, metrics |
| performance.md | Hot path and allocation discipline |
Anti Patterns
Type-system violations
- SHAPE PROLIFERATION: Duplicate schema/type for one concept. Keep one runtime anchor and derive projections via
pick/omit/partial.
- TYPE PROLIFERATION: Top-level
type/interface aliases that mirror runtime shape. Derive from runtime declarations (typeof XSchema.Type).
- NULL ARCHITECTURE:
null/undefined leaking across domain boundaries. Option<T> for absence, tagged failure for errors.
Organization violations
- SECTION ORDER: Sections MUST follow canonical order: Types โ Schema โ Constants โ Errors โ Services โ Functions โ Layers โ Export.
[TYPES] before [SCHEMA] before [CONSTANTS] โ never Constants before Types, never Schema after Errors. Domain extensions insert after their parent: [TABLES] after Schema, [REPOSITORIES] after Services.
Control-flow violations
- IMPERATIVE BRANCH: Statement branching (
if/else/switch/for/while) in domain flow. Replace with Match + monadic operators.
- EARLY MATCH COLLAPSE: Calling
match/Match.exhaustive mid-pipeline and losing composition. Keep map/flatMap; match at boundaries.
- MUTABLE ACCUMULATOR:
let + loop accumulation OR new Map()/.set() mutation breaks referential transparency. Use HashMap.set (returns new map), Array.reduce, Effect.forEach, or Stream.runFold. In Stream.mapAccum, accumulator state MUST use HashMap โ never new Map() with .set() mutation.
- MATCH OVER VOCAB: Using
Match.value/Match.when chains to classify into tiers/categories when a vocabulary object already maps those tiers. If _TierVocab exists with as const satisfies Record, classification MUST use vocabulary field lookup or threshold iteration โ never Match.when chains that duplicate the vocabulary's knowledge. Match is for structural/predicate dispatch on non-keyed shapes; vocabulary lookup is for keyed domains.
Surface-area violations
- SURFACE INFLATION: Multiple entrypoints for one concern (
run, runSafe, runV2). Collapse to one polymorphic surface.
- WRAPPER REDUNDANCY: Thin wrappers around external library APIs. Call upstream primitives directly.
- MODULE CONST SPAM: One-use top-level
const values that are not semantic anchors (schemas, schedules, metrics, vocabularies). Inline into the owning rail. This includes const _Status = S.Literal(...), const _Priority = S.Literal(...), const _TenantId = S.String.pipe(S.brand("TenantId")) before a Model.Class โ inline S.Literal(...) and S.brand(...) directly in field position. Extract to a const ONLY when the same schema is consumed by 2+ sites within the file (e.g., field definition AND query predicate). A module with N schema fields should have 0-1 extracted consts, not N parallel declarations.
- STRINGLY TELEMETRY: Repeated raw telemetry keys/values (
"operation", "status_class", "obs.outcome") across spans/metrics/logs. Define one bounded vocabulary object and project through it.
- GOD FUNCTION: Giant dispatch handling all variants in one function body. DU + exhaustive
Match.valueTags makes extension additive.
- EXPORT SPRAWL: Multiple exports for the same domain. Collapse to 1โ2 exports; make everything else
_-private and integrate into the exported construct's implementation.
- ALONGSIDE EXTRACTION: Private function defined at module level consumed by a single export. Inline as closure inside the export's scoped constructor, or compose inline in the pipe chain. If a
_-prefixed function exists, it must serve 2+ call sites or be a semantic anchor (vocabulary, schedule, metric, algorithm implementation).
- PIPELINE WRAPPER LEAK: Standalone module-level function that wraps a pipeline with a span/metric/collect (e.g.,
const _observe = (stream) => stream.pipe(Stream.runCollect, Effect.withSpan(...))). Inline this as a trailing .pipe(...) composition inside the factory โ it is single-caller pipeline plumbing, not a reusable semantic anchor.
- RE-EXPORT ALIASING:
const X = _X; export { X } or exporting a _-prefixed symbol directly. If a symbol needs export, name it correctly at declaration site โ never alias a private name to create a public one. A _-prefixed export is always wrong: either remove the prefix (it is public) or remove the export (it is private).
Error-rail violations
- ERROR RAIL FRAGMENTATION: Separate error classes per method. Keep one tagged module failure rail with bounded
reason literals.
- STRINGLY POLICY DRIFT: Duplicate inline status/retry/transport literals in handlers. Project via one canonical
reason -> policy table only.
- STRINGLY SIGNATURE DRIFT: Delimiter-concatenated signatures (
${a}:${b}:${c}) for equality/routing. Project structured tuples/records and compare fields directly.
- VARIABLE REASSIGNMENT:
let value = x; value = process(value) creates temporal coupling. pipe chains make the computation graph explicit.
Validation gate
- Required during iteration:
pnpm quality.
- Required for final completion:
pnpm quality, pnpm dotnet, pnpm python.
- Reject completion when load order, contracts, or checks are not satisfied.
- Examples inside this skill are executable doctrine: no unmarked
Object.*, ternaries, undefined as never, bare Error, unbounded unknown, or one-use helper extraction in golden paths.
Skill eval prompts
- Explicit invocation: "Using coding-ts, refactor this .ts service into an Effect ROP pipeline with a single polymorphic surface."
- Implicit invocation: "Review this
.tsx boundary handler for schema, error rail, and Effect service problems."
- Noisy context: "Ignore the surrounding product notes and only fix the TypeScript persistence adapter."
- Negative control: "Design a PostgreSQL index strategy only." Expected: do not invoke TS references unless TypeScript code is involved.
- Compliance checks: output should load only relevant references, avoid command thrash, avoid new helper files, preserve functional/Effect doctrine, and run
pnpm quality or a narrower existing TS gate when code is touched.
First-class libraries
Effect packages are standard libraries โ use over stdlib equivalents.
| Package | Provides |
|---|
effect | Core runtime, types, concurrency |
@effect/platform | HTTP, filesystem, sockets, workers |
@effect/platform-browser | Browser runtime adapter |
@effect/platform-bun | Bun runtime adapter |
@effect/platform-node | Node.js runtime adapter |
@effect/platform-node-shared | Shared Node.js platform utilities |
@effect/sql | SQL client abstraction |
@effect/sql-pg | PostgreSQL adapter |
@effect/cluster | Distributed actors, sharding |
@effect/rpc | Type-safe remote procedure calls |
@effect/workflow | Durable workflow orchestration |
@effect/ai | AI provider abstraction |
@effect/ai-anthropic | Anthropic provider adapter |
@effect/ai-google | Google AI provider adapter |
@effect/ai-openai | OpenAI provider adapter |
@effect/opentelemetry | Tracing and metrics integration |
@effect/vitest | Effect-aware test runner |
@effect/cli | Type-safe CLI builder |
@effect/printer | Composable document layout |
@effect/printer-ansi | ANSI terminal rendering |
@effect/experimental | Event sourcing, state machines |