Coding Style
High-level constraints that govern all TypeScript in the monorepo. Enforced by OXLint and OXFmt.
Import paths: massaman umbrella
The project imports match, P, isPlainObject, debounce, etc. through massaman/* subpaths — never directly from ts-pattern or es-toolkit. massaman re-exports both libraries under predictable subpaths:
Standards examples in this repo show the project import form. Upstream package names (ts-pattern, es-toolkit) appear only for discoverability.
Rules
No let — Use const Only
All bindings must be const. No reassignment, no mutation. Mutable state inside closures (factory internals) is the one accepted exception.
Correct
Incorrect
No Loops
Use map, filter, reduce, flatMap, and massaman/* utilities instead of for, while, do...while, for...in, or for...of.
Correct
Incorrect
No Classes
Use plain objects, closures, and factory functions. Classes are permitted only when wrapping an external SDK that requires instantiation.
Correct
Incorrect
No this
Never reference this. Factory closures and plain functions eliminate the need.
No throw
Return errors as values using the Result tuple type. No throw statements or throw expressions.
Correct
Incorrect
Immutable Data
Do not mutate objects or arrays after creation. Return new values from every transformation.
Correct
Incorrect
No Ternaries
Use if/else or match expressions. Ternaries are banned by the linter.
Correct
Incorrect
No Optional Chaining
Use explicit if/else or pattern matching instead of ?..
Correct
Incorrect
No any
Use unknown, generics, or proper types. Narrow with type guards when needed.
Correct
Incorrect
Prefer Point-Free Style
When passing a named function to a higher-order function, pass it directly instead of wrapping in an arrow.
Correct
Incorrect
ESM Only
Use ES module syntax with verbatimModuleSyntax. Use import type for type-only imports. Prefer the node: protocol for Node.js builtins.
Correct
Incorrect
No IIFEs
Do not use immediately invoked function expressions. Extract the logic into a named function and call it explicitly. This applies to both sync and async IIFEs.
Correct
Incorrect
Import Ordering
Organize imports into three groups separated by blank lines, sorted alphabetically within each group. Use top-level import type statements — do not use inline type specifiers.
- Node builtins —
node:protocol imports - External packages — third-party dependencies
- Internal imports — project-relative paths, ordered farthest-to-closest (
../before./), then alphabetically within the same depth
Correct
Incorrect
File Structure
Organize each source file in this order:
- Imports — ordered per import rules above
- Module-level constants —
constbindings used throughout the file - Exported declarations — the public API, each with full JSDoc
- Private helpers — each with
@privateJSDoc tag; no divider comments between sections
The export keyword and JSDoc are the separators — never add banner or divider comments. Exported functions appear first so readers see the public API without scrolling. Function declarations are hoisted, so calling order does not matter.
Correct
Incorrect
Disabling Lint Rules
Use // oxlint-disable-next-line <rule> -- <reason> to opt a single line out of a rule. Always include the -- <reason> justification — bare disables are rejected on review.
Acceptable cases:
- Stateful factory internals where
functional/no-letblocks a closure-held mutable binding. - Boundary code that must call into an external API with a banned shape (e.g., a
process.exitwrapper, raw event emitters). - Generated code where rewriting the rule violation isn't practical.
Correct
Incorrect
References
- Design Patterns -- Factories, pipelines, composition
- Errors -- Result type for error handling
- State -- Immutable state management
- Functions -- Pure functions and composition