[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/unknownleakage - 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.Classcopy-update transitions,Reffor managed mutable state, zeroletin domain code. Effect data structures (HashMap,HashSet,Chunk,List) over JS stdlib (Map,Set,Array) — structural sharing, referential transparency, andEqual/Hashintegration by default. JS stdlib collections only at system boundaries (FFI, serialization) - Typed error channels:
Data.TaggedEnumfor file-internal errors (never exported),class extends Data.TaggedErrorfor cross-cutting domain errors (polymorphic, few per system), composed viamapError/catchTag/catchTags - Exhaustive dispatch: vocabulary-driven dispatch (
Recordlookup) for keyed domains — vocabulary objects are the sole dispatch mechanism when a domain is keyed by string/enum;Matchis 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 asMatch.whenchains - Type anchoring: use
S.Class/Model.Classfor external codecs, persisted models, and domain authorities; use inferred plain objects for internal config/state when no runtime authority is needed. Derive projections viapick/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.Servicefor DI,Effect.withSpan/Effect.annotateLogsfor 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/HashSetoverMap/Set— structural equality viaEqual/Hash, persistent updates viaHashMap.setreturning new mapChunkoverArrayfor streaming contexts —Chunk.append/Chunk.concatare O(1) amortizedArraymodule (import { Array as A } from "effect") over native.map/.filter/.reduce— richer combinators (A.getSomes,A.match,A.groupBy,A.dedupeWith)Optionovernull/undefined—Option.fromNullableat boundaries,Option<T>in domainEitherover throw —Either.right/Either.leftfor pure branching without Effect overheadDurationover raw milliseconds —Duration.seconds(5)not5000Order/Equivalenceover custom comparators — composable viaOrder.struct,Order.combinePredicatemodule over inline boolean expressions —Predicate.not,Predicate.and,Predicate.orFunction.pipe/Function.flowover manual compositionSTM/TMap/TRefover mutable state for concurrent contextsRecordmodule overObject.keys/Object.entries/Object.fromEntriesStructmodule over manual object picking/omitting
Selection rules — when multiple Effect types compete:
HashMapfor accumulator state inStream.mapAccum— nevernew Map()with.set()mutationChunkforStreamoperations (Sink,mapAccumemissions, batching) —ReadonlyArrayfor small fixed collectionsOptionfor any absence in domain code —nullonly at JSON serialization boundaryDurationfor timeouts, delays, schedule intervals, vocabulary policy fields — rawnumberonly for arithmeticSTM/TMapfor shared state across fibers —HashMapfor immutable accumulator threaded throughmapAccum/reduce—Reffor single-fiber mutable state
Contracts
Type discipline
- One canonical runtime authority per boundary concept:
S.Class/Model.Classfor decoded or persisted concepts; inferred plain objects for internal config/state. Derive all projections (pick/omit/partial/extend), never parallelS.Structvariants. - Search existing shapes before creating new ones — extend or modify fields over declaring fresh schemas.
- Avoid module-level
type/interfacewhen 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/throwin domain transforms. - Expression dispatch:
Match.valueTags/Match.tagsExhaustivefor closed tagged domains, monadic combinators elsewhere. - Boundary adapters may use required statement forms with explicit marker:
// BOUNDARY ADAPTER — reason.
Error handling
Data.TaggedEnumfor file-internal errors — bounded discriminants, never exported, never crosses module boundaries.class extends Data.TaggedErrorfor 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 → policyprojection 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/runV2family inflation. - No helper files, no single-caller extracted functions, no module-level one-use
constvalues 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) noconst X = _Xre-export aliasing — fix the name at declaration site, never alias a private symbol to create a public one. ~350 LOCscrutiny 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/interfacealiases that mirror runtime shape. Derive from runtime declarations (typeof XSchema.Type). - NULL ARCHITECTURE:
null/undefinedleaking 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 withMatch+ monadic operators. - EARLY MATCH COLLAPSE: Calling
match/Match.exhaustivemid-pipeline and losing composition. Keepmap/flatMap; match at boundaries. - MUTABLE ACCUMULATOR:
let+ loop accumulation ORnew Map()/.set()mutation breaks referential transparency. UseHashMap.set(returns new map),Array.reduce,Effect.forEach, orStream.runFold. InStream.mapAccum, accumulator state MUST useHashMap— nevernew Map()with.set()mutation. - MATCH OVER VOCAB: Using
Match.value/Match.whenchains to classify into tiers/categories when a vocabulary object already maps those tiers. If_TierVocabexists withas const satisfies Record, classification MUST use vocabulary field lookup or threshold iteration — neverMatch.whenchains that duplicate the vocabulary's knowledge.Matchis 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
constvalues that are not semantic anchors (schemas, schedules, metrics, vocabularies). Inline into the owning rail. This includesconst _Status = S.Literal(...),const _Priority = S.Literal(...),const _TenantId = S.String.pipe(S.brand("TenantId"))before aModel.Class— inlineS.Literal(...)andS.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.valueTagsmakes 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). - STDLIB LEAKAGE: Using
new Map()/new Set()/Array.from()/Object.entries()in domain code when Effect providesHashMap/HashSet/Chunk/Recordmodule equivalents. JS stdlib collections lack structural equality, produce mutable state, and break referential transparency. Effect data structures are the default; JS stdlib only at FFI/serialization boundaries.
Error-rail violations
- ERROR RAIL FRAGMENTATION: Separate error classes per method. Keep one tagged module failure rail with bounded
reasonliterals. - STRINGLY POLICY DRIFT: Duplicate inline status/retry/transport literals in handlers. Project via one canonical
reason -> policytable 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.pipechains 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, bareError, unboundedunknown, 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
.tsxboundary 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 qualityor 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 |