From 793d927d5984700653da338c7b427ce6371f3474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Maciejewski?= <1276139+pwlmaciejewski@users.noreply.github.com> Date: Sun, 8 Mar 2026 10:36:29 +0100 Subject: [PATCH] chore: document api decision --- .gitignore | 5 +- AGENTS.md | 97 +++++++++++++++++++ CLAUDE.md | 97 +++++++++++++++++++ decisions/0001-use-object-with-methods-api.md | 58 +++++++++++ decisions/adr-template.md | 29 ++++++ 5 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 decisions/0001-use-object-with-methods-api.md create mode 100644 decisions/adr-template.md diff --git a/.gitignore b/.gitignore index 9d9dc35..318930c 100644 --- a/.gitignore +++ b/.gitignore @@ -138,4 +138,7 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* -.vscode \ No newline at end of file +.vscode + +# Agents +.claude \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2c515a3 --- /dev/null +++ b/AGENTS.md @@ -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. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2c515a3 --- /dev/null +++ b/CLAUDE.md @@ -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. diff --git a/decisions/0001-use-object-with-methods-api.md b/decisions/0001-use-object-with-methods-api.md new file mode 100644 index 0000000..613f404 --- /dev/null +++ b/decisions/0001-use-object-with-methods-api.md @@ -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. diff --git a/decisions/adr-template.md b/decisions/adr-template.md new file mode 100644 index 0000000..8468a4f --- /dev/null +++ b/decisions/adr-template.md @@ -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} +- … + +## 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)}. + + + +### 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, …} +- …