Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,7 @@ dist
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

.vscode
.vscode

# Agents
.claude
97 changes: 97 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# OK-FP

Essential Effect Data Types for TypeScript.

## Project Structure

Single-package library (not a monorepo):

- `src/` - Source code, one directory per effect type
- `docs/` - VitePress documentation site
- `dist/` - Build output (not committed)

Each effect type follows the same layout:

- `src/{type}/` - Implementation directory
- `src/{type}.ts` - Barrel re-export
- `src/{type}/model.ts` - Type definitions
- `src/{type}/constructors.ts` - Factory functions
- `src/{type}/helpers.ts` - Utility functions
- `src/{type}/{type}.ts` - Core implementation
- `src/{type}/{type}.spec.ts` - Tests
- `src/{type}/constructors.spec.ts` - Constructor tests
- `src/{type}/helpers.spec.ts` - Helper tests

## Tech Stack

- **Runtime:** Node.js (ESM)
- **Language:** TypeScript (strict mode)
- **Build:** tsdown
- **Test:** vitest (with `@vitest/coverage-v8`)
- **Lint:** Biome
- **Docs:** VitePress

## Commands

```bash
npm run lint # Run Biome check
npm run typecheck # Run tsc --noEmit
npm run test # Run vitest
npm run build # Build with tsdown
npm run docs:dev # Dev server for docs
npm run docs:build # Build docs
```

## Key Conventions

- Tests are co-located with source files using `.spec.ts` suffix
- Imports use `.js` extensions (ESM convention for TypeScript)
- `clearMocks: true` is set globally in vitest config. No need to manually reset mocks.
- Each effect type has its own barrel export in `src/{type}.ts`
- Package exports are per-effect: `ok-fp/option`, `ok-fp/either`, `ok-fp/validation`, `ok-fp/task`, `ok-fp/taskEither`
- Test utilities for algebraic laws (functor, monad, applicative) live in `src/testUtils/`

## Agent Guidelines

### Permissions

- **Allowed without asking:** non-mutating npm scripts (`lint`, `typecheck`, `test`, `build`), reading project files, creating commits.
- **Requires permission:** `npm install` or any command that modifies project dependencies, pushing to origin.

### Quality Assurance

After a batch of code changes, verify that QA scripts pass (`lint`, `typecheck`, `test`) and that the package builds correctly before considering the work done.

### General Rules

- Read existing code before modifying it. Understand the patterns in use.
- Keep changes minimal and focused. Do not refactor surrounding code unless asked.
- Follow existing conventions. Do not introduce new patterns without discussion.

### Code Style

- TypeScript strict mode. All compiler options in `tsconfig.json` are intentional.
- Functional style. Prefer `const`, pure functions, and immutable data.
- No classes except for the effect type implementations.
- ESM imports with `.js` extensions (e.g., `import foo from "./foo.js"`).
- Use Biome for formatting and linting. Do not override Biome rules without discussion.

### Writing Style

- Be strict and concise. No filler, no fluff.
- Never use em dashes in docs, comments, or commit messages. Use commas, periods, or parentheses instead.
- Markdown files (docs, ADRs) should have lines wrapped at 120 characters max.

### Testing

- Co-locate tests with source: `foo.ts` -> `foo.spec.ts`
- Use vitest (`describe`, `it`, `expect`)
- Test behavior, not implementation. Prefer testing public API surfaces.
- Algebraic law tests (functor, monad, applicative) use shared helpers from `src/testUtils/`

### Commits & PRs

- Use conventional commits (e.g., `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`).
- One logical change per commit.
- PR descriptions should explain the "why", not just the "what".
- CI must pass: lint, typecheck, test, build.
97 changes: 97 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# OK-FP

Essential Effect Data Types for TypeScript.

## Project Structure

Single-package library (not a monorepo):

- `src/` - Source code, one directory per effect type
- `docs/` - VitePress documentation site
- `dist/` - Build output (not committed)

Each effect type follows the same layout:

- `src/{type}/` - Implementation directory
- `src/{type}.ts` - Barrel re-export
- `src/{type}/model.ts` - Type definitions
- `src/{type}/constructors.ts` - Factory functions
- `src/{type}/helpers.ts` - Utility functions
- `src/{type}/{type}.ts` - Core implementation
- `src/{type}/{type}.spec.ts` - Tests
- `src/{type}/constructors.spec.ts` - Constructor tests
- `src/{type}/helpers.spec.ts` - Helper tests

## Tech Stack

- **Runtime:** Node.js (ESM)
- **Language:** TypeScript (strict mode)
- **Build:** tsdown
- **Test:** vitest (with `@vitest/coverage-v8`)
- **Lint:** Biome
- **Docs:** VitePress

## Commands

```bash
npm run lint # Run Biome check
npm run typecheck # Run tsc --noEmit
npm run test # Run vitest
npm run build # Build with tsdown
npm run docs:dev # Dev server for docs
npm run docs:build # Build docs
```

## Key Conventions

- Tests are co-located with source files using `.spec.ts` suffix
- Imports use `.js` extensions (ESM convention for TypeScript)
- `clearMocks: true` is set globally in vitest config. No need to manually reset mocks.
- Each effect type has its own barrel export in `src/{type}.ts`
- Package exports are per-effect: `ok-fp/option`, `ok-fp/either`, `ok-fp/validation`, `ok-fp/task`, `ok-fp/taskEither`
- Test utilities for algebraic laws (functor, monad, applicative) live in `src/testUtils/`

## Agent Guidelines

### Permissions

- **Allowed without asking:** non-mutating npm scripts (`lint`, `typecheck`, `test`, `build`), reading project files, creating commits.
- **Requires permission:** `npm install` or any command that modifies project dependencies, pushing to origin.

### Quality Assurance

After a batch of code changes, verify that QA scripts pass (`lint`, `typecheck`, `test`) and that the package builds correctly before considering the work done.

### General Rules

- Read existing code before modifying it. Understand the patterns in use.
- Keep changes minimal and focused. Do not refactor surrounding code unless asked.
- Follow existing conventions. Do not introduce new patterns without discussion.

### Code Style

- TypeScript strict mode. All compiler options in `tsconfig.json` are intentional.
- Functional style. Prefer `const`, pure functions, and immutable data.
- No classes except for the effect type implementations.
- ESM imports with `.js` extensions (e.g., `import foo from "./foo.js"`).
- Use Biome for formatting and linting. Do not override Biome rules without discussion.

### Writing Style

- Be strict and concise. No filler, no fluff.
- Never use em dashes in docs, comments, or commit messages. Use commas, periods, or parentheses instead.
- Markdown files (docs, ADRs) should have lines wrapped at 120 characters max.

### Testing

- Co-locate tests with source: `foo.ts` -> `foo.spec.ts`
- Use vitest (`describe`, `it`, `expect`)
- Test behavior, not implementation. Prefer testing public API surfaces.
- Algebraic law tests (functor, monad, applicative) use shared helpers from `src/testUtils/`

### Commits & PRs

- Use conventional commits (e.g., `feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`).
- One logical change per commit.
- PR descriptions should explain the "why", not just the "what".
- CI must pass: lint, typecheck, test, build.
Comment thread
pwlmc marked this conversation as resolved.
58 changes: 58 additions & 0 deletions decisions/0001-use-object-with-methods-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
status: "accepted"
date: 2025-12-28
---

# Use object-with-methods API style

## Context and Problem Statement

Each effect data type (Option, Either, Task, etc.) exposes operations like `map`, `flatMap`, and `match`.
We need to decide how these operations are surfaced to the consumer: as standalone (free) functions
or as methods on the effect instance.

Most FP languages and libraries (e.g., Haskell, fp-ts) use free functions. This keeps each
function pure and composable, but in TypeScript it leads to deeply nested calls or requires
a `pipe` utility to read naturally:

```ts
// Free functions + pipe (fp-ts style)
pipe(
some("Alice"),
map((name) => `Hello, ${name}!`),
getOrElse(() => "User not found"),
);
```

```ts
// Object with methods
some("Alice")
.map((name) => `Hello, ${name}!`)
.getOrElse(() => "User not found");
```

## Considered Options

- **Free functions.** Each operation is a standalone function (e.g., `map(option, fn)`).
Requires a `pipe` or `flow` helper for readable chaining.
- **Object with methods.** Each effect instance exposes operations as methods
(e.g., `option.map(fn)`). Chaining is native via dot notation.

## Decision Outcome

Chosen option: **object with methods**, because:

1. Method chaining reads naturally in TypeScript and requires no extra utilities (`pipe`, `flow`).
2. IDE autocompletion shows available operations directly on the value.
3. It matches what TypeScript developers already expect from APIs like `Array`, `Promise`, and `Map`.

For additional context, read the
["Source Code Is Not Going Anywhere... I Hope"](https://pwlmc.dev/posts/ok-fp-breaks-a-core-functional-programming-rule-on-purpose/)
blog post where the reasons for the decision are discussed in more depth.

### Consequences

- All effect data types are implemented as objects with methods.
- No `pipe` or `flow` utility is needed or provided.
- Free function helpers (e.g., `map2`, `all`) are still used where an operation applies
across multiple instances rather than being called on one.
29 changes: 29 additions & 0 deletions decisions/adr-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
status: "{proposed | rejected | accepted | deprecated | … | superseded by ADR-0123}"
date: { YYYY-MM-DD when the decision was last updated }
---

# {short title, representative of solved problem and found solution}

## Context and Problem Statement

{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}

## Considered Options

- {title of option 1}
- {title of option 2}
- {title of option 3}
- … <!-- numbers of options can vary -->

## Decision Outcome

Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.

<!-- This is an optional element. Feel free to remove. -->

### Consequences

- Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
- Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
- … <!-- numbers of consequences can vary -->