Merge branch 'main' into codeflash/optimize-checkForValidAPIKey-mkwv868t

This commit is contained in:
Aseem Saxena 2026-03-18 12:10:27 -07:00 committed by GitHub
commit d44ca16d27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
917 changed files with 196719 additions and 11900 deletions

View file

@ -0,0 +1,54 @@
---
paths:
- "django/**"
---
# AIService (Django-Ninja Backend)
## Important
NEVER start, restart, or manage the dev server (uvicorn, nohup, background processes). The developer will run services manually.
## Commands
```bash
uv sync # Install dependencies
uv run uvicorn aiservice.asgi:application --reload # Dev server
uv run pytest # Run all tests
uv run pytest tests/path/test_file.py::test_name -v # Single test
uv run mypy . # Type check
uv run ty check # Type check (ty)
uv run ruff check . # Lint
uv run ruff format . # Format
```
## Architecture
- All endpoints are `async def` — runs under ASGI via uvicorn
- Django-Ninja with Pydantic schemas. APIs defined in `aiservice/urls.py`
- Use Pydantic `BaseModel` or `ninja.Schema` for all request/response types
- Use `aiservice/llm.py` for LLM calls — never call provider APIs directly
- Prompts stored as `.md` files alongside their modules
- Always use `libcst` for code transformation, never `ast` (preserves formatting)
- Use `asyncio.TaskGroup` for concurrent operations
## Multi-Language Handlers
`core/languages/` has per-language modules registering via `core/registry`. Routers in `core/shared/` dispatch by `language` field. To add a language: create `core/languages/<lang>/`, register in `__init__.py`, implement protocol methods.
## Gotchas
- No automatic exception handling — use `get_object_or_404` explicitly
- Auth: `HttpBearer` before `django_auth` to avoid CSRF errors
- Lazy imports in routers to avoid circular dependencies
## Testing
- Tests in `tests/` by feature: `optimizer/`, `testgen/`, `integration/`, `validators/`
- `@pytest.mark.asyncio` for async tests
- Factories: `create_optimizer_context()`, `create_refiner_context()`
## Style
- 120 char line length, Python 3.12+
- Minimal comments — explain "why" not "what"
- No docstrings unless requested

View file

@ -0,0 +1,15 @@
# Service Architecture
```
VSC-Extension / CLI → cf-api (Express, :3001) → aiservice (Django-Ninja, :8000)
cf-webapp (:3000) reads from the same PostgreSQL DB via Prisma
```
## Glossary
- **Optimization Candidate** — LLM-generated code that may be faster
- **Read-Write Context** — code the LLM can modify
- **Read-Only Context** — code provided as info only (not modified)
- **Tracer** — collects input args for a Python function at runtime
- **Replay Test** — reruns traced inputs to verify behavior
- **Comparator** — compares two Python objects for equality

View file

@ -0,0 +1,13 @@
---
paths:
- "js/cf-webapp/**"
---
# Next.js Patterns (cf-webapp)
- Default to server components. `"use client"` only for interactivity/state/browser APIs. Push client boundaries as low in the tree as possible
- Server actions go in files with `"use server"` at the top — never define inside client components
- Props passed from server → client must be JSON-serializable (no functions or class instances)
- Prisma queries go in server components. Client components fetch via API or receive data as props
- Path alias: `@/*``./src/*`
- Tree-sitter WASM files in `public/` built by `postinstall` — don't delete
- `@codeflash-ai/common` must be in `transpilePackages` (top-level await)

4
.claude/rules/git.md Normal file
View file

@ -0,0 +1,4 @@
# Git
- Always create a new branch from `main` — never commit directly to `main`
- Conventional commits: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:`

View file

@ -0,0 +1,27 @@
---
paths:
- "js/**"
---
# JS/TS Packages
NEVER start, restart, or manage dev servers (npm run dev, node, nohup, background processes). The developer will run services manually.
All use ESLint + Prettier. Run commands from each package directory.
## Prisma
Schema lives in `common/prisma/schema.prisma`, shared by cf-api and cf-webapp. `common` is CommonJS — use `require`-style imports when working with it directly. Published as `@codeflash-ai/common` to GitHub Packages.
## Package Gotchas
### cf-api
- Webhook routes MUST be registered before body parser (raw body needed for signature verification)
- `instrument.ts` must be imported first in entry point (Sentry init)
- Tests use dependency injection: `setXxxDependencies()` / `resetXxxDependencies()`
### VSC-Extension
- **Different prettier config**: 80 width + semicolons (vs 100/no-semi elsewhere)
- npm workspaces for local `@codeflash/*` packages
- Sidebar is a separate Vite/React app embedded via webview postMessage
- `acquireVsCodeApi()` called once per session — store and reuse
- esbuild excludes `vscode` module (provided by runtime)

View file

@ -0,0 +1,22 @@
# PR Review Guidelines
When reviewing PRs, follow these priorities:
## CRITICAL - Always comment:
- Logic errors or bugs that will cause failures
- Security vulnerabilities
- Test method names with typos (won't be discovered by test runner)
- Breaking changes without migration path
## SKIP - Don't comment on:
- Code style or formatting (we have linters for this)
- Suggestions for additional features or refactoring
- "Consider" or "might want to" suggestions
- Performance optimizations without profiling data
- Duplicate concerns (one comment per issue)
## Process:
1. Check if previous comments on same lines are now fixed - resolve those first
2. Limit to 5-7 high-signal comments per review
3. Group related issues into one comment when possible
4. If many issues exist, use a summary comment instead of 20+ inline comments

View file

@ -0,0 +1,11 @@
# Fix Formatting & Linting (prek)
When asked to fix formatting, linting, or pre-commit issues (e.g., "fix prek", "fix formatting", "run pre-k"):
1. Run `uv run prek run --all-files` to auto-fix formatting issues
2. If prek modifies files, stage them with `git add .`
3. Commit with message: `fix: auto-format with prek`
4. Push the changes with `git push`
5. Comment on the PR confirming what was fixed
Do NOT just review the code - actually run prek and push the fixes!

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-api-endpoint

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-language-support

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-optimization-failure

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-test-generation

4
.codex/config.toml Normal file
View file

@ -0,0 +1,4 @@
[mcp_servers.tessl]
type = "stdio"
command = "tessl"
args = [ "mcp", "start" ]

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-api-endpoint

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-language-support

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-optimization-failure

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-test-generation

12
.gemini/settings.json Normal file
View file

@ -0,0 +1,12 @@
{
"mcpServers": {
"tessl": {
"type": "stdio",
"command": "tessl",
"args": [
"mcp",
"start"
]
}
}
}

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-api-endpoint

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/add-language-support

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-optimization-failure

View file

@ -0,0 +1 @@
../../.tessl/tiles/codeflash/codeflash-internal-skills/skills/debug-test-generation

2
.gitattributes vendored
View file

@ -3,3 +3,5 @@
*.{bat,[bB][aA][tT]} text eol=crlf
*.png binary
*.jpg binary
.github/workflows/*.lock.yml linguist-generated=true merge=ours

View file

@ -13,33 +13,84 @@ on:
types: [submitted]
jobs:
# Automatic PR review (read-only)
# Automatic PR review (can fix linting issues and push)
pr-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
pull-requests: write
issues: read
id-token: write
actions: read
defaults:
run:
working-directory: django/aiservice
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref }}
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Install dependencies
run: |
uv venv --seed
uv sync
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
use_foundry: "true"
use_bedrock: "true"
use_sticky_comment: true
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
EVENT: ${{ github.event.action }}
IMPORTANT: This repo has Python code in `django/aiservice/`. Run uv/prek commands from that directory.
## STEP 1: Run prek and mypy checks, fix issues
First, run these checks on files changed in this PR:
1. `cd django/aiservice && uv run prek run --from-ref origin/main` - linting/formatting issues
2. `cd django/aiservice && uv run mypy <changed_files>` - type checking issues
If there are prek issues:
- For SAFE auto-fixable issues (formatting, import sorting, trailing whitespace, etc.), run `cd django/aiservice && uv run prek run --from-ref origin/main` again to auto-fix them
- For issues that prek cannot auto-fix, do NOT attempt to fix them manually — report them as remaining issues in your summary
If there are mypy issues:
- Fix type annotation issues (missing return types, Optional/None unions, import errors for type hints, incorrect types)
- Do NOT add `type: ignore` comments - always fix the root cause
After fixing issues:
- Stage the fixed files with `git add`
- Commit with message "style: auto-fix linting issues" or "fix: resolve mypy type errors" as appropriate
- Push the changes with `git push`
IMPORTANT - Verification after fixing:
- After committing fixes, run `cd django/aiservice && uv run prek run --from-ref origin/main` ONE MORE TIME to verify all issues are resolved
- If errors remain, either fix them or report them honestly as unfixed in your summary
- NEVER claim issues are fixed without verifying. If you cannot fix an issue, say so
Do NOT attempt to fix:
- Type errors that require logic changes or refactoring
- Complex generic type issues
- Anything that could change runtime behavior
## STEP 2: Review the PR
${{ github.event.action == 'synchronize' && 'This is a RE-REVIEW after new commits. First, get the list of changed files in this latest push using `gh pr diff`. Review ONLY the changed files. Check ALL existing review comments and resolve ones that are now fixed.' || 'This is the INITIAL REVIEW.' }}
Review this PR focusing ONLY on:
@ -54,14 +105,75 @@ jobs:
- Only create NEW inline comments for HIGH-PRIORITY issues found in changed files.
- Limit to 5-7 NEW comments maximum per review.
- Use CLAUDE.md for project-specific guidance.
- Use `gh pr comment` for summary-level feedback.
- Use `mcp__github_inline_comment__create_inline_comment` sparingly for critical code issues only.
claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Read,Glob,Grep"'
## STEP 3: Coverage analysis
Analyze test coverage for changed files:
1. Get the list of Python files changed in this PR (excluding tests):
`git diff --name-only origin/main...HEAD -- '*.py' | grep -v test`
2. Run tests with coverage on the PR branch (from django/aiservice):
`cd django/aiservice && uv run coverage run -m pytest -q --tb=no`
`cd django/aiservice && uv run coverage json -o coverage-pr.json`
3. Get coverage for changed files only:
`cd django/aiservice && uv run coverage report --include="<changed_files_comma_separated>"`
4. Compare with main branch coverage:
- Checkout main: `git checkout origin/main`
- Run coverage: `cd django/aiservice && uv run coverage run -m pytest -q --tb=no && uv run coverage json -o coverage-main.json`
- Checkout back: `git checkout -`
5. Analyze the diff to identify:
- NEW FILES: Files that don't exist on main (require good test coverage)
- MODIFIED FILES: Files with changes (changes must be covered by tests)
6. Report in PR comment with a markdown table:
- Coverage % for each changed file (PR vs main)
- Overall coverage change
- For NEW files: Flag if coverage is below 75%
- For MODIFIED files: Flag if the changed lines are not covered by tests
- Flag if overall coverage decreased
Coverage requirements:
- New implementations/files: Must have ≥75% test coverage
- Modified code: Changed lines should be exercised by existing or new tests
- No coverage regressions: Overall coverage should not decrease
## STEP 4: Post ONE consolidated summary comment
CRITICAL: You must post exactly ONE summary comment containing ALL results (pre-commit, review, coverage).
DO NOT post multiple separate comments. Use this format:
```
## PR Review Summary
### Prek Checks
[status and any fixes made]
### Code Review
[critical issues found, if any]
### Test Coverage
[coverage table and analysis]
---
*Last updated: <timestamp>*
```
To ensure only ONE comment exists:
1. Find existing claude[bot] comment: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | head -1`
2. If found, UPDATE it: `gh api --method PATCH repos/${{ github.repository }}/issues/comments/<ID> -f body="<content>"`
3. If not found, CREATE: `gh pr comment ${{ github.event.pull_request.number }} --body "<content>"`
4. Delete any OTHER claude[bot] comments to clean up duplicates: `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments --jq '.[] | select(.user.login == "claude[bot]") | .id' | tail -n +2 | xargs -I {} gh api --method DELETE repos/${{ github.repository }}/issues/comments/{}`
claude_args: '--model us.anthropic.claude-sonnet-4-6 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(cd django/aiservice*),Bash(uv run prek *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep,Edit"'
additional_permissions: |
actions: read
env:
ANTHROPIC_FOUNDRY_API_KEY: ${{ secrets.ANTHROPIC_FOUNDRY_API_KEY }}
ANTHROPIC_FOUNDRY_BASE_URL: ${{ secrets.ANTHROPIC_FOUNDRY_BASE_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DJANGO_SETTINGS_MODULE: aiservice.settings
# @claude mentions (can edit and push)
claude-mention:
@ -77,9 +189,13 @@ jobs:
issues: read
id-token: write
actions: read
defaults:
run:
working-directory: django/aiservice
steps:
- name: Get PR head ref
id: pr-ref
working-directory: .
env:
GH_TOKEN: ${{ github.token }}
run: |
@ -97,14 +213,25 @@ jobs:
fetch-depth: 0
ref: ${{ steps.pr-ref.outputs.ref }}
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Install dependencies
run: |
uv venv --seed
uv sync
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
use_foundry: "true"
claude_args: '--allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(uv run prek *),Bash(prek *),Bash(gh pr comment*),Bash(gh pr view*)"'
use_bedrock: "true"
claude_args: '--model us.anthropic.claude-sonnet-4-6 --allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(cd django/aiservice*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"'
additional_permissions: |
actions: read
env:
ANTHROPIC_FOUNDRY_API_KEY: ${{ secrets.ANTHROPIC_FOUNDRY_API_KEY }}
ANTHROPIC_FOUNDRY_BASE_URL: ${{ secrets.ANTHROPIC_FOUNDRY_BASE_URL }}

View file

@ -49,21 +49,8 @@ jobs:
- name: Skip optimization
run: echo "Skipping codeflash optimization - no changes in django/aiservice/"
wait-for-prek:
needs: check-changes
if: needs.check-changes.outputs.should-run == 'true'
name: Wait for prek checks
runs-on: ubuntu-latest
steps:
- uses: lewagon/wait-on-check-action@v1.3.4
with:
ref: ${{ github.event.pull_request.head.sha }}
check-name: prek
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
optimize:
needs: [check-changes, wait-for-prek]
needs: [check-changes]
if: needs.check-changes.outputs.should-run == 'true' && github.actor != 'codeflash-ai[bot]'
name: Optimize new code in this PR
runs-on: ubuntu-latest

160
.github/workflows/codeflash-js.yaml vendored Normal file
View file

@ -0,0 +1,160 @@
name: Codeflash JS/TS Optimization
on:
pull_request:
paths:
- "js/cf-api/**"
- "js/cf-webapp/**"
workflow_dispatch:
inputs:
project:
description: 'Project to optimize (cf-api, cf-webapp, or both)'
required: false
default: 'both'
type: choice
options:
- both
- cf-api
- cf-webapp
permissions:
contents: read
pull-requests: read
checks: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Check which projects have changes
check-changes:
runs-on: ubuntu-latest
outputs:
cf-api: ${{ steps.filter.outputs.cf-api }}
cf-webapp: ${{ steps.filter.outputs.cf-webapp }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
cf-api:
- 'js/cf-api/**'
cf-webapp:
- 'js/cf-webapp/**'
# Skip job when no JS changes detected (only for pull_request events)
no-js-changes:
name: No JS/TS changes detected
needs: check-changes
if: |
github.event_name == 'pull_request'
&& needs.check-changes.outputs.cf-api != 'true'
&& needs.check-changes.outputs.cf-webapp != 'true'
runs-on: ubuntu-latest
steps:
- name: Skip optimization
run: echo "Skipping codeflash optimization - no changes in js/cf-api/ or js/cf-webapp/"
# Optimize cf-api (Jest)
optimize-cf-api:
needs: [check-changes]
if: |
(
needs.check-changes.outputs.cf-api == 'true'
|| github.event_name == 'workflow_dispatch' && (github.event.inputs.project == 'cf-api' || github.event.inputs.project == 'both')
)
&& github.actor != 'codeflash-ai[bot]'
name: Optimize cf-api
runs-on: ubuntu-latest
env:
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
CODEFLASH_PR_NUMBER: ${{ github.event.number }}
COLUMNS: 110
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: js/cf-api/package-lock.json
- name: Install cf-api dependencies
working-directory: js/cf-api
run: npm ci
- name: Install jest-junit for test reporting
working-directory: js/cf-api
run: npm install --save-dev jest-junit
- name: Set up Python and install Codeflash
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install Codeflash CLI
run: |
uv tool install git+https://github.com/codeflash-ai/codeflash@main
- name: Run Codeflash optimization
working-directory: js/cf-api
run: |
export PATH="$HOME/.local/bin:$PATH"
codeflash --all --verbose --yes
# Optimize cf-webapp (Vitest)
optimize-cf-webapp:
needs: [check-changes]
if: |
(
needs.check-changes.outputs.cf-webapp == 'true'
|| github.event_name == 'workflow_dispatch' && (github.event.inputs.project == 'cf-webapp' || github.event.inputs.project == 'both')
)
&& github.actor != 'codeflash-ai[bot]'
name: Optimize cf-webapp
runs-on: ubuntu-latest
env:
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
CODEFLASH_PR_NUMBER: ${{ github.event.number }}
COLUMNS: 110
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: js/cf-webapp/package-lock.json
- name: Install cf-webapp dependencies
working-directory: js/cf-webapp
run: npm ci
- name: Set up Python and install Codeflash
uses: astral-sh/setup-uv@v7
with:
python-version: "3.12"
- name: Install Codeflash CLI
run: |
uv tool install git+https://github.com/codeflash-ai/codeflash@main
- name: Run Codeflash optimization
working-directory: js/cf-webapp
run: |
export PATH="$HOME/.local/bin:$PATH"
codeflash --all --verbose --yes

View file

@ -46,25 +46,9 @@ jobs:
- name: Skip tests
run: echo "Skipping django unit tests - no changes in django/aiservice/"
wait-for-prek:
name: Wait for prek checks
needs: check-changes
if: needs.check-changes.outputs.should-run == 'true' && github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: lewagon/wait-on-check-action@v1.3.4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
check-name: prek
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
unit-tests:
needs: [check-changes, wait-for-prek]
if: |
always() &&
needs.check-changes.outputs.should-run == 'true' &&
(needs.wait-for-prek.result == 'success' || needs.wait-for-prek.result == 'skipped')
needs: [check-changes]
if: needs.check-changes.outputs.should-run == 'true'
runs-on: ubuntu-latest
env:
@ -96,7 +80,7 @@ jobs:
django-unit-tests-status:
runs-on: ubuntu-latest
needs: [check-changes, no-aiservice-changes, wait-for-prek, unit-tests]
needs: [check-changes, no-aiservice-changes, unit-tests]
if: always()
defaults:
run:

View file

@ -0,0 +1,119 @@
name: Duplicate Code Detector
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize]
jobs:
detect-duplicates:
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref || github.ref }}
- name: Start Serena MCP server
run: |
docker pull ghcr.io/github/serena-mcp-server:latest
docker run -d --name serena \
--network host \
-v "${{ github.workspace }}:${{ github.workspace }}:rw" \
ghcr.io/github/serena-mcp-server:latest \
serena start-mcp-server --context codex --project "${{ github.workspace }}"
mkdir -p /tmp/mcp-config
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
{
"mcpServers": {
"serena": {
"command": "docker",
"args": ["exec", "-i", "serena", "serena", "start-mcp-server", "--context", "codex", "--project", "${{ github.workspace }}"]
}
}
}
EOF
- name: Configure AWS Credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
use_sticky_comment: true
allowed_bots: "claude[bot],codeflash-ai[bot]"
claude_args: '--model us.anthropic.claude-sonnet-4-6 --mcp-config /tmp/mcp-config/mcp-servers.json --allowedTools "Read,Glob,Grep,Bash(git diff:*),Bash(git log:*),Bash(git show:*),Bash(wc *),Bash(find *),mcp__serena__*"'
prompt: |
You are a duplicate code detector with access to Serena semantic code analysis.
## Setup
First activate the project in Serena:
- Use `mcp__serena__activate_project` with the workspace path `${{ github.workspace }}`
## Steps
1. Get the list of changed source files (excluding tests):
`git diff --name-only origin/main...HEAD -- '*.py' '*.ts' '*.tsx' '*.js' '*.jsx' | grep -v -E '(test_|_test\.|\.test\.|\.spec\.|/tests/|/test/|/__tests__/)'`
2. Use Serena's semantic analysis on changed files:
- `mcp__serena__get_symbols_overview` to understand file structure
- `mcp__serena__find_symbol` to search for similarly named symbols across the codebase
- `mcp__serena__find_referencing_symbols` to understand usage patterns
- `mcp__serena__search_for_pattern` to find similar code patterns
3. For each changed file, look for:
- **Exact Duplication**: Identical code blocks (>10 lines) in multiple locations
- **Structural Duplication**: Same logic with minor variations (different variable names)
- **Functional Duplication**: Different implementations of the same functionality
- **Copy-Paste Programming**: Similar blocks that could be extracted into shared utilities
4. Cross-reference against the rest of the codebase using Serena:
- Search for similar function signatures and logic patterns
- Check if new code duplicates existing utilities or helpers
- Look for repeated patterns across modules
- Check across service boundaries (django/aiservice, js/cf-api, js/cf-webapp, js/common)
## What to Report
- Identical or nearly identical functions in different files
- Repeated code blocks that could be extracted to utilities
- Similar classes or modules with overlapping functionality
- Copy-pasted code with minor modifications
- Duplicated business logic across components or services
## What to Skip
- Standard boilerplate (imports, __init__, exports, etc.)
- Test setup/teardown code
- Configuration with similar structure
- Language-specific patterns (constructors, getters/setters)
- Small snippets (<5 lines) unless highly repetitive
- Workflow files under .github/
- Prisma schema and migration files
- node_modules, .venv, build artifacts
## Output
Post a single PR comment with your findings. For each pattern found:
- Severity (High/Medium/Low)
- File locations with line numbers
- Code samples showing the duplication
- Concrete refactoring suggestion
If no significant duplication is found, say so briefly. Do not create issues — just comment on the PR.
- name: Stop Serena
if: always()
run: docker stop serena && docker rm serena || true

View file

@ -50,26 +50,9 @@ jobs:
- name: Mark as success
run: exit 0
prek-check:
name: Wait for prek checks
needs: check-changes
if: ${{ github.event_name == 'pull_request' && needs.check-changes.outputs.should_run == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
checks: read
steps:
- uses: lewagon/wait-on-check-action@v1.3.4
with:
ref: ${{ github.event.pull_request.head.sha }}
check-name: prek
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
unit-tests-check:
name: Wait for unit tests
needs: [check-changes, prek-check]
needs: [check-changes]
if: ${{ github.event_name == 'pull_request' && needs.check-changes.outputs.aiservice_changed == 'true' }}
runs-on: ubuntu-latest
permissions:
@ -209,7 +192,7 @@ jobs:
e2e-status:
name: E2E Tests Status
runs-on: ubuntu-latest
needs: [check-changes, no-changes-detected, prek-check, unit-tests-check, e2e-test]
needs: [check-changes, no-changes-detected, unit-tests-check, e2e-test]
if: always()
steps:
- name: Check all job statuses
@ -222,7 +205,6 @@ jobs:
else
echo "✗ End-to-end tests workflow failed"
echo "no-changes-detected: ${{ needs.no-changes-detected.result }}"
echo "prek-check: ${{ needs.prek-check.result }}"
echo "unit-tests-check: ${{ needs.unit-tests-check.result }}"
echo "e2e-test: ${{ needs.e2e-test.result }}"
exit 1

View file

@ -4,6 +4,7 @@ on: [pull_request]
jobs:
prek:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5
with:

4
.gitignore vendored
View file

@ -255,4 +255,8 @@ fabric.properties
*/node_modules/*
# Next.js
.next/
/cli/experiments/js-serialization-experiment/node_modules/*
/cli/packages/codeflash/.npmrc

View file

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$/django/aiservice" />
<option name="settingsModule" value="aiservice/settings.py" />
<option name="manageScript" value="manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/cli/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/django/aiservice" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/js/cf-api" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/js/cf-webapp" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/js/common" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/cli" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/django/aiservice/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.aider.ident.cache.v1" />
<excludeFolder url="file://$MODULE_DIR$/.aider.tags.cache.v1" />
<excludeFolder url="file://$MODULE_DIR$/.mypy_cache" />
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
<excludeFolder url="file://$MODULE_DIR$/cli/dist" />
<excludeFolder url="file://$MODULE_DIR$/.aider.tags.cache.v3" />
<excludeFolder url="file://$MODULE_DIR$/js/cf-api/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/js/cf-webapp/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/js/common/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/cli/.venv" />
</content>
<orderEntry type="jdk" jdkName="uv (codeflash-internal)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="langchain" />
</component>
<component name="PackageRequirementsSettings">
<option name="requirementsPath" value="" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>

12
.mcp.json Normal file
View file

@ -0,0 +1,12 @@
{
"mcpServers": {
"tessl": {
"type": "stdio",
"command": "tessl",
"args": [
"mcp",
"start"
]
}
}
}

31
.tessl/RULES.md Normal file
View file

@ -0,0 +1,31 @@
# Agent Rules
This file is updated when running `tessl install`. If a linked file is missing, make sure to run the command to download any missing tiles from the registry.
## codeflash/codeflash-internal-rules — code-style
@tiles/codeflash/codeflash-internal-rules/rules/code-style.md [code-style](tiles/codeflash/codeflash-internal-rules/rules/code-style.md)
## codeflash/codeflash-internal-rules — architecture
@tiles/codeflash/codeflash-internal-rules/rules/architecture.md [architecture](tiles/codeflash/codeflash-internal-rules/rules/architecture.md)
## codeflash/codeflash-internal-rules — optimization-patterns
@tiles/codeflash/codeflash-internal-rules/rules/optimization-patterns.md [optimization-patterns](tiles/codeflash/codeflash-internal-rules/rules/optimization-patterns.md)
## codeflash/codeflash-internal-rules — git-conventions
@tiles/codeflash/codeflash-internal-rules/rules/git-conventions.md [git-conventions](tiles/codeflash/codeflash-internal-rules/rules/git-conventions.md)
## codeflash/codeflash-internal-rules — testing-rules
@tiles/codeflash/codeflash-internal-rules/rules/testing-rules.md [testing-rules](tiles/codeflash/codeflash-internal-rules/rules/testing-rules.md)
## codeflash/codeflash-internal-rules — multi-language-handlers
@tiles/codeflash/codeflash-internal-rules/rules/multi-language-handlers.md [multi-language-handlers](tiles/codeflash/codeflash-internal-rules/rules/multi-language-handlers.md)
## tessl/cli-setup — query_library_docs
@tiles/tessl/cli-setup/steering/query_library_docs.md [query_library_docs](tiles/tessl/cli-setup/steering/query_library_docs.md)

View file

@ -0,0 +1,29 @@
# AIService Endpoints
All Django-Ninja API endpoints registered in `aiservice/urls.py`.
## Endpoint Map
| Path | API | Module | Description |
|------|-----|--------|-------------|
| `/ai/optimize` | `optimize_api` | `core.shared.optimizer_router` | Main optimization — dispatches by language |
| `/ai/optimize-line-profiler` | `optimize_line_profiler_api` | `core.languages.python.optimizer.optimizer_line_profiler` | Line profiler optimization |
| `/ai/testgen` | `testgen_api` | `core.shared.testgen_router` | Test generation — dispatches by language |
| `/ai/log_features` | `features_api` | `core.log_features.log_features` | Feature logging |
| `/ai/refinement` | `refinement_api` | `core.languages.python.optimizer.refinement` | Candidate refinement |
| `/ai/explain` | `explanations_api` | `core.languages.python.explanations.explanations` | Code explanations |
| `/ai/rank` | `ranker_api` | `core.shared.ranker.ranker` | Function ranking |
| `/ai/optimization_review` | `optimization_review_api` | `core.languages.python.optimization_review.optimization_review` | Optimization review |
| `/ai/code_repair` | `code_repair_api` | `core.languages.python.code_repair.code_repair` | Code repair for failed candidates |
| `/ai/adaptive_optimize` | `adaptive_optimize_api` | `core.languages.python.adaptive_optimizer.adaptive_optimizer` | Adaptive optimization |
| `/ai/workflow-gen` | `workflow_gen_api` | `core.shared.workflow_gen.workflow_gen` | Workflow generation |
| `/ai/rewrite_jit` | `jit_rewrite_api` | `core.languages.python.jit_rewrite.jit_rewrite` | JIT rewrite |
## Common Patterns
- All endpoints are `async def`
- Authentication via `AuthenticatedRequest` (HttpBearer + django_auth)
- Request schemas use `ninja.Schema` (Pydantic under the hood)
- Response schemas define typed success and error responses: `response={200: SuccessSchema, 400: ErrorSchema, 500: ErrorSchema}`
- Multi-language endpoints (optimize, testgen) dispatch by `data.language` field
- Python-only endpoints import directly from `core.languages.python.*`

View file

@ -0,0 +1,58 @@
# CF-API Endpoints
Express routes in `js/cf-api/routes/`. The cf-api acts as middleware between clients (VSC-Extension, CLI) and the aiservice backend.
## Route Registration Order (`routes/index.ts`)
Registration order matters — webhook routes must be before the body parser:
1. **Webhook routes** — before `express.json()` (raw body for signature verification)
2. **Body parser**`express.json({ limit: JSON_BODY_LIMIT })`
3. **Public routes** — no authentication required
4. **Protected routes** — require API key (`checkForValidAPIKey` middleware)
## Route Files
### `webhook.routes.ts`
- `POST /github/webhooks` — GitHub App webhook handler (Octokit signature verification)
- `POST /stripe/webhooks` — Stripe webhook handler
- Both need raw body access (before JSON parser)
### `optimization.routes.ts`
Protected optimization endpoints:
- `POST /suggest-pr-changes` — suggest PR changes
- `POST /create-pr` — create optimization PR
- `POST /verify-existing-optimizations` — check existing optimizations
- `POST /is-already-optimized` — check if code was already optimized
- `POST /add-code-hash` — add optimized code context hash
- `POST /mark-as-success` — mark optimization as successful
- `POST /create-staging` — create staging review
- `POST /get-staging-code` — get staged code
- `POST /commit-staging-code` — commit staged code
- `POST /test-repo` — add repository manually
### `github.routes.ts`
GitHub-related endpoints for repository management.
### `subscription.routes.ts`
Subscription management endpoints.
### `user.routes.ts`
User management endpoints.
### `public.routes.ts`
Public endpoints (no authentication): health checks, version info.
## Middleware Stack
- `checkForValidAPIKey` — API key authentication
- `trackEndpointCalls` — PostHog endpoint tracking
- `idLimiter` — rate limiting
- `logAuthEvent` / `logRequestBody` — enhanced logging (dev only)
- `trackUsage` — usage tracking for optimization endpoints

View file

@ -0,0 +1,49 @@
# Configuration & Thresholds
Key constants and configuration values used across the optimization pipeline.
## Model Distribution (`core/shared/optimizer_config.py`)
- `MAX_OPTIMIZER_CALLS = 6` — maximum parallel optimization calls
- `MAX_OPTIMIZER_LP_CALLS = 7` — maximum line profiler optimization calls
- Distribution formula: `claude_calls = (total - 1) // 2`, `gpt_calls = total - claude_calls`
- Example: 6 total → 4 OpenAI + 2 Anthropic
## Default Request Values
- `OptimizeSchema.n_candidates = 5` — default optimization candidates
- `OptimizeSchemaLP.n_candidates = 6` — default line profiler candidates
- `OptimizeSchema.language = "python"` — default language
## LLM Model Costs (USD per 1M tokens)
| Model | Input | Cached Input | Output |
|-------|-------|-------------|--------|
| GPT-4.1 | $2.00 | $0.50 | $8.00 |
| GPT-5-mini | $0.25 | $0.03 | $2.00 |
| Claude Sonnet 4.5 | $3.00 | — | $15.00 |
| Claude Haiku 4.5 | $1.00 | — | $5.00 |
## Model Assignments
| Purpose | Primary | Fallback |
|---------|---------|----------|
| Optimization | GPT-5-mini | Claude Sonnet 4.5 |
| Explanation | GPT-5-mini | Claude Sonnet 4.5 |
| Planning | GPT-5-mini | Claude Sonnet 4.5 |
| Execution | GPT-5-mini | Claude Sonnet 4.5 |
| Ranking | GPT-5-mini | Claude Sonnet 4.5 |
| Refinement | Claude Sonnet 4.5 | GPT-5-mini |
| Code Repair | Claude Sonnet 4.5 | GPT-5-mini |
| Explanations | Claude Sonnet 4.5 | GPT-5-mini |
| Optimization Review | Claude Sonnet 4.5 | GPT-5-mini |
| Adaptive Optimize | Claude Sonnet 4.5 | GPT-5-mini |
| Cost-effective (testgen) | Claude Haiku 4.5 | GPT-5-mini |
## Ruff Configuration (`pyproject.toml`)
- `line-length = 120`
- `select = ["ALL"]` with specific ignores (see `tool.ruff.lint.ignore`)
- `fix = true`, `show-fixes = true`
- Runtime-evaluated base classes: `pydantic.BaseModel`, `ninja.Schema`, `typing.TypedDict`
- Excluded paths: `core/languages/python/testgen/tests`, `core/languages/python/testgen/sqlalchemy`

View file

@ -0,0 +1,49 @@
# Context Extraction
How code context is extracted and prepared for LLM optimization prompts.
## Context Types
### Single-File Context (`SingleOptimizerContext`)
Used when the function to optimize lives in a single file:
- Extracts the function source code
- Collects helper functions and class definitions
- Formats as system prompt + user prompt
### Multi-File Context (`MultiOptimizerContext`)
Used when the function spans or depends on multiple files:
- Collects code from multiple source files
- Manages file-path-annotated code blocks
## `BaseOptimizerContext` (`optimizer_context.py`)
Abstract base class for all context types:
### Factory Method
`get_dynamic_context()` — dispatches to `SingleOptimizerContext` or `MultiOptimizerContext` based on the input.
### Prompt Construction
- `get_system_prompt(python_version_str)` — builds system prompt with language version
- `get_user_prompt(dependency_code, line_profiler_results)` — builds user prompt with code and optional profiler data
### LLM Response Parsing
- `extract_code_and_explanation_from_llm_res(content)` — parses markdown code blocks from LLM output, extracts code and explanation text
- `parse_and_generate_candidate_schema()` — converts extracted code into `OptimizeResponseItemSchema`
- `is_valid_code()` — validates the extracted code is syntactically correct
## Code Formatting
LLM responses use markdown code blocks with file path annotations:
```
\`\`\`python:path/to/file.py
# optimized code here
\`\`\`
```
The context extraction system both generates this format (for prompts) and parses it (from responses).

View file

@ -0,0 +1,59 @@
# Domain Types
Core schemas and models used across the codeflash-internal backend.
## Request/Response Schemas (`core/shared/optimizer_models.py`)
### `OptimizedCandidateSource` enum
How a candidate was generated: `OPTIMIZE`, `OPTIMIZE_LP`, `REFINE`, `REPAIR`, `ADAPTIVE`, `JIT_REWRITE`.
### `OptimizeSchema` (ninja.Schema)
Main optimization request:
- `source_code: str` — code to optimize
- `dependency_code: str | None` — read-only dependency code
- `trace_id: str` — unique request identifier
- `language: str = "python"` — language identifier (python, javascript, typescript)
- `language_version: str | None` — e.g., "ES2022", "Node 20", or Python version
- `n_candidates: int = 5` — number of optimization candidates to generate
- `is_async: bool | None = False` — whether the function is async
- `python_version: str | None` — optional, for multi-language support
- `experiment_metadata`, `codeflash_version`, `current_username`, `repo_owner`, `repo_name` — tracking fields
### `OptimizeSchemaLP` (ninja.Schema)
Line profiler variant — same as `OptimizeSchema` plus:
- `line_profiler_results: str | None` — line profiler output
- `n_candidates: int = 6` — default higher for line profiler optimization
## Domain Models (`core/log_features/models.py`)
### `OptimizationFeatures` (Django Model)
Stores per-optimization data:
- `trace_id` (PK) — unique optimization identifier
- `original_code` — original source code
- `optimizations_raw` / `optimizations_post` — raw and postprocessed candidate code
- `speedup_ratio` — best speedup achieved
- `test_results` — test execution results
- Approval workflow: `approval_status`, `approved_by`, `approved_at`
### `OptimizationEvents` (Django Model)
Event logging for optimization lifecycle:
- `pr_created`, `pr_merged`, `pr_closed` — PR events
- `llm_cost` — total LLM API cost
- `speedup_x` / `speedup_pct` — performance improvement metrics
### `Repositories` (Django Model)
GitHub repository metadata:
- `installation_id` — GitHub App installation ID
- `is_private` — whether the repo is private
- `optimizations_limit` / `optimizations_used` — usage tracking
## Response Schemas (`core/shared/optimizer_schemas.py`)
- `OptimizeResponseSchema` — successful optimization response with candidates
- `OptimizeErrorResponseSchema` — error response with message

View file

@ -0,0 +1,21 @@
# Codeflash Internal Documentation
CodeFlash's AI-powered code optimization backend. The aiservice (Django-Ninja) receives optimization requests from cf-api, dispatches to language-specific handlers, calls LLMs, postprocesses results, and returns optimized candidates.
## Service Flow
```
VSC-Extension / CLI → cf-api (Express, :3001) → aiservice (Django-Ninja, :8000)
cf-webapp (:3000) reads from the same PostgreSQL DB via Prisma
```
## Documentation Pages
- [Domain Types](domain-types.md) — Core schemas and domain models
- [Optimization Pipeline](optimization-pipeline.md) — End-to-end optimization flow
- [Test Generation Pipeline](test-generation-pipeline.md) — Test generation flow
- [Context Extraction](context-extraction.md) — How code context is extracted for LLM prompts
- [AIService Endpoints](aiservice-endpoints.md) — All Django-Ninja endpoints
- [CF-API Endpoints](cf-api-endpoints.md) — All Express routes
- [Configuration & Thresholds](configuration-thresholds.md) — Model distribution, costs, constants
- [LLM Provider Abstraction](llm-provider-abstraction.md) — llm.py usage, client creation, cost tracking

View file

@ -0,0 +1,63 @@
# LLM Provider Abstraction
Unified LLM interface in `aiservice/llm.py` — all LLM calls go through this module.
## Model Definition (`LLM` dataclass)
```python
@pydantic_dataclass
class LLM:
name: str # deployment name (e.g., "gpt-5-mini")
max_tokens: int # max context window
model_type: Literal["openai", "anthropic", "google"]
input_cost: float # USD per 1M tokens
cached_input_cost: float # USD per 1M cached tokens
output_cost: float # USD per 1M tokens
```
Concrete models: `OpenAI_GPT_4_1`, `OpenAI_GPT_5_Mini`, `Anthropic_Claude_Sonnet_4_5`, `Anthropic_Claude_Haiku_4_5`.
## Client Setup
- `_create_openai_client()` — returns `AsyncAzureOpenAI` (reads `AZURE_OPENAI_*` env vars)
- `_create_anthropic_client()` — returns `AsyncAnthropicFoundry` (reads `ANTHROPIC_FOUNDRY_API_KEY` + `ANTHROPIC_FOUNDRY_BASE_URL`)
- `get_llm_client(model_type)` — creates a fresh client per request to avoid event loop issues with Django dev server
## Calling LLMs
```python
from aiservice.llm import call_llm, LLM
response: LLMResponse = await call_llm(
llm=model, # LLM instance
messages=messages, # OpenAI-format messages
call_type="optimization", # tracking label
trace_id=trace_id, # request identifier
max_tokens=16384, # max output tokens
user_id=user_id, # optional tracking
)
# response.content: str
# response.usage: LLMUsage(input_tokens, output_tokens)
# response.raw_response: ChatCompletion | AnthropicMessage
```
### Provider Handling
- **OpenAI (Azure)**: Uses `client.chat.completions.create()`. GPT-5-mini uses `max_completion_tokens`, older models use `max_tokens`
- **Anthropic (Foundry)**: Extracts system prompt from messages list, passes separately via `system=` kwarg. Concatenates text blocks from response
### Observability
- Every call is recorded to database via `record_llm_call()` in the `finally` block
- Includes: trace_id, call_type, model, messages, result, error, cost, latency
## Cost Calculation
`calculate_llm_cost(response, llm)` accounts for cached vs non-cached input tokens:
- **Anthropic**: `cache_read_input_tokens` and `cache_creation_input_tokens` are additive to `input_tokens`
- **OpenAI**: `cached_tokens` is a subset of `prompt_tokens`
## Response Types
- `LLMResponse` — wraps `content: str`, `usage: LLMUsage`, `raw_response`
- `LLMUsage``input_tokens: int`, `output_tokens: int`

View file

@ -0,0 +1,70 @@
# Optimization Pipeline
End-to-end flow from optimization request to response.
## 1. Request Entry (`core/shared/optimizer_router.py`)
The `optimize_api` NinjaAPI router receives a POST request with `OptimizeSchema`. It dispatches by `data.language`:
- `"javascript"` / `"typescript"``core.languages.js_ts.optimizer.optimize_javascript`
- `"java"``core.languages.java.optimizer.optimize_java`
- Default → `core.languages.python.optimizer.optimizer.optimize_python`
All imports are lazy (inside the function body) to avoid circular dependencies.
## 2. Python Optimization (`core/languages/python/optimizer/optimizer.py`)
### `optimize_python(request, data) → (status, response)`
Entry point for Python optimization. Extracts user ID from request, builds context, and calls `optimize_python_code()`.
### `optimize_python_code_single()`
Single LLM optimization call:
1. Builds system + user prompts from `BaseOptimizerContext`
2. Calls `call_llm()` with the optimize model
3. Parses response via `ctx.extract_code_and_explanation_from_llm_res()`
4. Validates via `ctx.parse_and_generate_candidate_schema()`
5. Returns `(OptimizeResponseItemSchema, llm_cost, model_name)` or `(None, cost, model)`
### `optimize_python_code()`
Parallel optimization with multiple models:
1. Gets model distribution via `get_model_distribution(n_candidates, MAX_OPTIMIZER_CALLS)`
2. Runs parallel calls using `asyncio.TaskGroup`
3. Each call gets a `call_sequence` number for tracking
4. Collects results, costs, and model info from all parallel calls
## 3. Context Extraction (`optimizer_context.py`)
- `BaseOptimizerContext.get_dynamic_context()` — factory dispatching to `SingleOptimizerContext` or `MultiOptimizerContext`
- Handles prompt construction with `get_system_prompt()` / `get_user_prompt()`
- `extract_code_and_explanation_from_llm_res()` — parses markdown code blocks from LLM output
- `parse_and_generate_candidate_schema()` — converts extracted code to response schema
## 4. Postprocessing (`postprocess.py`)
- `deduplicate_optimizations()` — AST-based dedup using `ast.parse()` + `ast.dump()`
- `equality_check()` — filters out candidates identical to original code
- Uses `libcst` for all code transformations
## 5. Model Distribution (`optimizer_config.py`)
- `MAX_OPTIMIZER_CALLS = 6`, `MAX_OPTIMIZER_LP_CALLS = 7`
- `get_model_distribution()` splits between OpenAI and Anthropic
- Formula: `claude_calls = (total - 1) // 2`, `gpt_calls = total - claude_calls`
- Returns `[(OPENAI_MODEL, gpt_calls), (ANTHROPIC_MODEL, claude_calls)]`
## 6. Response
Returns `(200, OptimizeResponseSchema)` with list of `OptimizeResponseItemSchema` candidates, or `(400/500, OptimizeErrorResponseSchema)` on failure.
## Key Files
| File | Role |
|------|------|
| `core/shared/optimizer_router.py` | Language dispatch |
| `core/languages/python/optimizer/optimizer.py` | Python optimization flow |
| `core/languages/python/optimizer/context_utils/optimizer_context.py` | Context & prompt management |
| `core/languages/python/optimizer/postprocess.py` | Dedup & validation |
| `core/shared/optimizer_config.py` | Model distribution |
| `core/shared/optimizer_models.py` | Request/response schemas |

View file

@ -0,0 +1,55 @@
# Test Generation Pipeline
Flow from test generation request to instrumented test output.
## 1. Request Entry (`core/shared/testgen_router.py`)
The `testgen_api` NinjaAPI router receives a POST request and dispatches by `data.language`:
- `"javascript"` / `"typescript"``core.languages.js_ts.testgen.generate_tests_javascript`
- `"java"``core.languages.java.testgen.generate_tests_java`
- Default → `core.languages.python.testgen.testgen.generate_tests_python`
## 2. Python Testgen (`core/languages/python/testgen/testgen.py`)
### `build_prompt()`
Jinja2-based prompt builder for test generation:
- Constructs system and user prompts from context
- Handles async/sync function variants
- Includes source code, dependency code, and test context
### `instrument_tests()`
Applies instrumentation to generated tests:
- Behavior instrumentation (captures return values, stdout)
- Performance instrumentation (timing loops)
### `LLMOutputParseError`
Custom exception for LLM response parsing failures — includes raw output for debugging.
## 3. Instrumentation (`testgen/instrumentation/instrument_new_tests.py`)
### Framework Detection
- `detect_frameworks_from_code()` — parses imports to identify ML frameworks (PyTorch, TensorFlow, JAX) and their aliases
- Used to add GPU sync calls in timing blocks
### Device Sync
- `_create_device_sync_precompute_statements()` — pre-computes device sync checks (CUDA, MPS) outside timing blocks
- Ensures accurate timing for GPU-accelerated code
## 4. Postprocessing (`testgen/postprocessing/`)
- Import management: `add_missing_imports.py` adds `from __future__ import annotations`
- Test validation and cleanup
## Key Files
| File | Role |
|------|------|
| `core/shared/testgen_router.py` | Language dispatch |
| `core/languages/python/testgen/testgen.py` | Testgen flow |
| `core/languages/python/testgen/instrumentation/instrument_new_tests.py` | Test instrumentation |
| `core/languages/python/testgen/postprocessing/` | Import management, cleanup |

View file

@ -0,0 +1,7 @@
{
"name": "codeflash/codeflash-internal-docs",
"version": "0.1.0",
"summary": "Internal documentation for the codeflash-internal aiservice backend",
"private": true,
"docs": "docs/index.md"
}

View file

@ -0,0 +1,55 @@
# Architecture
## Service Flow
```
VSC-Extension / CLI → cf-api (Express, :3001) → aiservice (Django-Ninja, :8000)
cf-webapp (:3000) reads from the same PostgreSQL DB via Prisma
```
## Monorepo Layout
```
codeflash-internal/
├── django/aiservice/ # Python backend (Django-Ninja, ASGI)
│ ├── aiservice/ # Django project: settings, urls.py, llm.py
│ ├── authapp/ # Authentication (HttpBearer + django_auth)
│ ├── core/ # Business logic
│ │ ├── shared/ # Cross-language routers (optimizer_router, testgen_router, ranker)
│ │ ├── languages/ # Per-language handlers (python/, js_ts/, java/)
│ │ ├── protocols/ # Handler protocols (LanguageHandler, OptimizerProtocol, etc.)
│ │ ├── registry.py # Language handler registration
│ │ ├── dispatcher.py # Handler lookup by language + feature
│ │ └── log_features/ # Domain models (OptimizationFeatures, OptimizationEvents)
│ └── tests/ # pytest tests by feature: optimizer/, testgen/, integration/
├── js/
│ ├── cf-api/ # Express middleware layer (:3001)
│ ├── cf-webapp/ # Next.js dashboard (:3000)
│ ├── common/ # Shared Prisma schema + utilities (CommonJS)
│ └── VSC-Extension/ # VS Code extension
├── cli/ # CLI tools
└── deployment/ # Infrastructure configs
```
## Glossary
- **Optimization Candidate** — LLM-generated code that may be faster
- **Read-Write Context** — code the LLM can modify
- **Read-Only Context** — code provided as info only (not modified)
- **Tracer** — collects input args for a Python function at runtime
- **Replay Test** — reruns traced inputs to verify behavior
- **Comparator** — compares two Python objects for equality
## Key Entry Points
| Task | Start here |
|------|------------|
| Optimization dispatch | `core/shared/optimizer_router.py` |
| Testgen dispatch | `core/shared/testgen_router.py` |
| Python optimization | `core/languages/python/optimizer/optimizer.py` |
| LLM provider abstraction | `aiservice/llm.py` |
| Endpoint registration | `aiservice/urls.py` |
| Domain models | `core/log_features/models.py` |
| Request/response schemas | `core/shared/optimizer_models.py` |
| Handler registration | `core/registry.py` + `core/dispatcher.py` |
| cf-api route registration | `js/cf-api/routes/index.ts` |

View file

@ -0,0 +1,24 @@
# Code Style
## Python (aiservice)
- **Python 3.12+** — use modern syntax (type unions `X | Y`, `match` statements)
- **Line length**: 120 characters
- **Tooling**: Ruff for linting/formatting (`ruff check .`, `ruff format .`), mypy strict mode, ty for type checking, prek for pre-commit (`uv run prek run --all-files`)
- **Package management**: Always use `uv` — run commands via `uv run`
- **Comments**: Minimal — only explain "why", not "what"
- **Docstrings**: Do not add unless explicitly requested
- **Source transforms**: Use `libcst` for code modification/transformation (preserves formatting); `ast` is acceptable for read-only analysis
- **Async**: All endpoints are `async def` — runs under ASGI via uvicorn. Use `asyncio.TaskGroup` for concurrent operations
- **Schemas**: Use Pydantic `BaseModel` or `ninja.Schema` for all request/response types
- **LLM calls**: Use `aiservice/llm.py` — never call provider APIs directly
- **Prompts**: Stored as `.md` files alongside their modules, rendered with Jinja2
- **Lazy imports**: Use inside function bodies in routers to avoid circular dependencies (`# noqa: PLC0415`)
## JavaScript/TypeScript (cf-api, cf-webapp, VSC-Extension)
- All JS/TS packages use ESLint + Prettier. Run commands from each package directory
- **cf-api**: Express app. Webhook routes MUST be registered before body parser (raw body needed for signature verification). `instrument.ts` must be imported first in entry point (Sentry). Tests use dependency injection: `setXxxDependencies()` / `resetXxxDependencies()`
- **cf-webapp**: Next.js. Default to server components; `"use client"` only for interactivity. Server actions in `"use server"` files. Prisma queries in server components only. Path alias `@/*``./src/*`
- **VSC-Extension**: Different prettier config (80 width + semicolons vs 100/no-semi elsewhere). npm workspaces for local `@codeflash/*` packages. Sidebar is a separate Vite/React app embedded via webview postMessage
- **Prisma**: Schema in `common/prisma/schema.prisma`, shared by cf-api and cf-webapp. `common` is CommonJS — use `require`-style imports

View file

@ -0,0 +1,10 @@
# Git Conventions
- **Always create a new branch from `main`** — never commit directly to `main`
- Use conventional commit format: `fix:`, `feat:`, `refactor:`, `docs:`, `test:`, `chore:`
- Keep commits atomic — one logical change per commit
- Commit message body should be concise (1-2 sentences max)
- PR titles should also use conventional format
- Branch naming: `cf-#-title` (lowercase, hyphenated) where `#` is the Linear issue number
- If related to a Linear issue, include `CF-#` in the PR body
- Pre-commit: `uv run prek run --all-files` from repo root

View file

@ -0,0 +1,32 @@
# Multi-Language Handlers
## Registry Pattern
- `core/registry.py` provides `LanguageRegistry` — a dict mapping `language_id → handler_class`
- Module-level singleton: `registry = LanguageRegistry()`
- Decorator registration: `@register_handler("python")` on handler classes
- Duplicate registration raises `ValueError`
## Dispatcher
- `core/dispatcher.py` provides `get_handler_for_language()` and `get_handler_for_feature()`
- `Feature` enum maps feature names to capability flags: `TESTGEN`, `OPTIMIZER`, `CODE_REPAIR`, `JIT_REWRITE`, `OPTIMIZATION_REVIEW`, `EXPLANATIONS`
- `get_handler_for_feature()` checks `supports_*` attribute before instantiation, raises `HandlerNotImplementedError` if unsupported
## Protocols
- `core/protocols/` defines runtime-checkable protocols:
- `LanguageHandler` (base): declares `language`, `supports_*` booleans
- `OptimizerProtocol`: `optimizer_optimize(request, data)`
- `TestGenProtocol`: `testgen_generate(request, data)`
- `CodeRepairProtocol`: `code_repair_repair(user_id, optimization_id, ctx)`
## Adding a New Language
1. Create `core/languages/<lang>/` directory
2. Implement handler class with `@register_handler("<lang>")` decorator
3. Set `supports_*` flags for implemented features
4. Implement protocol methods for each supported feature
5. Import the module in `core/languages/__init__.py` so the decorator fires
6. Add language routing in `core/shared/optimizer_router.py` and `core/shared/testgen_router.py`
7. Add tests in `tests/` for the new handler

View file

@ -0,0 +1,39 @@
# Optimization Patterns
## Router Dispatch
- Shared routers in `core/shared/` dispatch by the `language` field on the request schema
- Lazy imports inside the endpoint body to avoid circular dependencies:
```python
if data.language in ("javascript", "typescript"):
from core.languages.js_ts.optimizer import optimize_javascript # noqa: PLC0415
return await optimize_javascript(request, data)
```
- Default language is Python
## Context Extraction
- `BaseOptimizerContext` in `optimizer_context.py` handles prompt management and code extraction
- Two context types: `SingleOptimizerContext` (single-file) and `MultiOptimizerContext` (multi-file)
- `extract_code_and_explanation_from_llm_res()` parses LLM markdown response into code blocks
- `parse_and_generate_candidate_schema()` converts extracted code to `OptimizeResponseItemSchema`
## Model Distribution
- `get_model_distribution()` in `optimizer_config.py` splits calls between OpenAI and Anthropic
- Formula: `claude_calls = (total - 1) // 2`, `gpt_calls = total - claude_calls`
- `MAX_OPTIMIZER_CALLS = 6`, `MAX_OPTIMIZER_LP_CALLS = 7`
- Claude gets fewer calls as it's more expensive
## Postprocessing
- `postprocess.py` handles deduplication and validation of optimization candidates
- Deduplication: normalize with `ast.parse()` + `ast.dump()`, skip duplicates with identical AST
- Equality check: compare optimized code to original to skip no-ops
- Uses `libcst` for all code transformations (preserves formatting)
## Prompt Templates
- Prompts stored as `.md` files alongside their modules
- Rendered with Jinja2 (e.g., `build_prompt()` in testgen)
- System and user prompts are constructed per-context type

View file

@ -0,0 +1,20 @@
# Testing Rules
## Python (aiservice)
- Tests in `tests/` by feature: `optimizer/`, `testgen/`, `testgen_postprocessing/`, `testgen_instrumentation/`, `integration/`, `validators/`
- `@pytest.mark.asyncio` for async tests
- `pytest.ini` sets `DJANGO_SETTINGS_MODULE = aiservice.settings`
- `conftest.py` provides `normalize_code()` helper (AST parse/unparse for quote normalization)
- Test factories: `create_optimizer_context()`, `create_refiner_context()`
- Run tests: `uv run pytest` (all), `uv run pytest tests/path/test_file.py::test_name -v` (single)
## JS/TS (cf-api)
- Tests use dependency injection: `setXxxDependencies()` / `resetXxxDependencies()`
## PR Review
- **Always comment on**: Logic errors, security vulnerabilities, test name typos, breaking changes without migration
- **Never comment on**: Code style/formatting (linters handle it), "consider" suggestions, performance without profiling data
- Limit to 5-7 high-signal comments per review

View file

@ -0,0 +1,26 @@
{
"name": "codeflash/codeflash-internal-rules",
"version": "0.1.0",
"summary": "Coding standards and conventions for the codeflash-internal monorepo",
"private": true,
"rules": {
"code-style": {
"rules": "rules/code-style.md"
},
"architecture": {
"rules": "rules/architecture.md"
},
"optimization-patterns": {
"rules": "rules/optimization-patterns.md"
},
"git-conventions": {
"rules": "rules/git-conventions.md"
},
"testing-rules": {
"rules": "rules/testing-rules.md"
},
"multi-language-handlers": {
"rules": "rules/multi-language-handlers.md"
}
}
}

View file

@ -0,0 +1,170 @@
---
name: add-api-endpoint
description: >
Guide for adding a new API endpoint to the codeflash-internal system.
Use when adding a new endpoint, creating a route, or implementing a new API.
Covers both aiservice (Django-Ninja) and cf-api (Express) endpoints
including schemas, routers, authentication, URL registration, and tests.
---
# Add API Endpoint
Use this workflow when adding a new endpoint to either the aiservice backend or the cf-api middleware. Follow the section that matches your target service.
## Part A: AIService Endpoint (Django-Ninja)
### Step 1: Define Schemas
Create request and response schemas.
1. Create or update schemas in the appropriate module (e.g., `core/shared/` for shared, `core/languages/python/` for Python-specific)
2. Use `ninja.Schema` for all request/response types:
```python
from ninja import Schema
class MyRequestSchema(Schema):
source_code: str
trace_id: str
language: str = "python"
class MyResponseSchema(Schema):
results: list[str]
class MyErrorResponseSchema(Schema):
message: str
```
3. Follow existing patterns in `core/shared/optimizer_models.py`
**Checkpoint**: Schemas should use Pydantic validation. Test with `MyRequestSchema.model_validate(data)`.
### Step 2: Create the Router
Create a NinjaAPI router for the endpoint.
1. Create a new module (e.g., `core/shared/my_feature.py` or `core/languages/python/my_feature/my_feature.py`)
2. Define the router and endpoint:
```python
from ninja import NinjaAPI
from authapp.auth import AuthenticatedRequest
my_feature_api = NinjaAPI(urls_namespace="my_feature")
@my_feature_api.post(
"/", response={200: MyResponseSchema, 400: MyErrorResponseSchema, 500: MyErrorResponseSchema}
)
async def my_endpoint(
request: AuthenticatedRequest, data: MyRequestSchema
) -> tuple[int, MyResponseSchema | MyErrorResponseSchema]:
# Implementation here
return 200, MyResponseSchema(results=[])
```
3. All endpoints must be `async def`
4. Use `AuthenticatedRequest` for authenticated endpoints
5. For multi-language endpoints, add dispatch by `data.language` with lazy imports
**Checkpoint**: The endpoint should handle success and error cases with proper status codes.
### Step 3: Register in urls.py
Add the endpoint to `aiservice/urls.py`.
1. Import the API object:
```python
from core.shared.my_feature import my_feature_api
```
2. Add to `urlpatterns`:
```python
path("ai/my-feature", my_feature_api.urls),
```
3. Follow the naming convention: `ai/<kebab-case-name>`
**Checkpoint**: The endpoint should be accessible at `/ai/my-feature/`.
### Step 4: Add Tests
Write tests for the endpoint.
1. Create test file in `tests/` matching the source structure
2. Test the handler function directly (not via HTTP):
```python
@pytest.mark.asyncio
async def test_my_endpoint():
# Mock request and data
result = await my_endpoint(mock_request, mock_data)
assert result[0] == 200
```
3. Run: `uv run pytest tests/path/test_file.py -v`
**Checkpoint**: Tests pass. Run `uv run prek run --all-files`.
## Part B: CF-API Endpoint (Express)
### Step 1: Create Endpoint Handler
Create the endpoint handler in `js/cf-api/endpoints/`.
1. Create `js/cf-api/endpoints/my-feature.ts`:
```typescript
import { Request, Response } from "express"
export async function myFeature(req: Request, res: Response) {
// Implementation
res.status(200).json({ results: [] })
}
```
2. Follow existing patterns in `endpoints/`
**Checkpoint**: Handler should return appropriate status codes and JSON responses.
### Step 2: Create Route File or Add to Existing
Add the route to the appropriate route file.
1. If it's a new domain, create `js/cf-api/routes/my-feature.routes.ts`:
```typescript
import { Router } from "express"
import { addAsync } from "@awaitjs/express"
import { myFeature } from "../endpoints/my-feature.js"
import { ROUTES } from "../constants/index.js"
const router = addAsync(Router()) as any
router.postAsync(ROUTES.MY_FEATURE, myFeature)
export default router
```
2. If it belongs to an existing domain, add to the corresponding route file
3. Add the route constant to `constants/index.ts`
**Checkpoint**: Route file exports a router with the endpoint registered.
### Step 3: Register in Route Index
Register the route in `js/cf-api/routes/index.ts`.
1. Import the route module
2. Register in the correct section:
- Before body parser: webhook routes only
- Public routes: no auth required
- Protected routes: after `checkForValidAPIKey` middleware
3. Apply middleware as needed (`trackUsage`, `idLimiter`, etc.)
**Checkpoint**: The endpoint should be accessible with proper authentication.
### Step 4: Add Tests
Write tests using the dependency injection pattern.
1. Create test file in `js/cf-api/__tests__/`
2. Use `setXxxDependencies()` / `resetXxxDependencies()` for mocking
3. Follow existing test patterns
**Checkpoint**: Tests pass with `npm test`.
## Key Files Reference
| File | What to modify |
|------|---------------|
| `core/shared/optimizer_models.py` | Schema patterns |
| `aiservice/urls.py` | AIService endpoint registration |
| `js/cf-api/routes/index.ts` | CF-API route registration |
| `js/cf-api/constants/index.ts` | Route constants |
| `authapp/auth.py` | Authentication patterns |

View file

@ -0,0 +1,135 @@
---
name: add-language-support
description: >
Guide for adding a new programming language to the multi-language system.
Use when extending the aiservice to support a new language (e.g., Rust, Go).
Covers directory creation, handler implementation, registry registration,
protocol compliance, prompt templates, router updates, and tests.
---
# Add Language Support
Use this workflow when adding a new programming language to the codeflash-internal optimization system. Follow steps in order — each builds on the previous.
## Step 1: Create Language Directory
Create the directory structure under `core/languages/`.
1. Create `core/languages/<lang>/` with `__init__.py`
2. Create subdirectories for each feature:
```
core/languages/<lang>/
├── __init__.py # Handler class + @register_handler decorator
├── optimizer/ # Optimization handler
│ ├── __init__.py
│ └── optimizer.py
└── testgen/ # Test generation handler (if supported)
├── __init__.py
└── testgen.py
```
3. Follow existing patterns from `core/languages/python/` or `core/languages/js_ts/`
**Checkpoint**: The directory structure should match existing language modules. Verify with `ls core/languages/`.
## Step 2: Implement Handler Class
Create the handler class in `core/languages/<lang>/__init__.py`.
1. Read `core/protocols/base.py` for the `LanguageHandler` protocol
2. Implement the handler:
```python
from core.registry import register_handler
@register_handler("<lang>")
class <Lang>Handler:
language = "<lang>"
supports_testgen = False # Set True if implementing testgen
supports_optimizer = True # Set True if implementing optimizer
supports_code_repair = False
supports_jit_rewrite = False
supports_optimization_review = False
supports_explanations = False
```
3. Set `supports_*` flags for each feature you implement
4. The `@register_handler` decorator registers the class with the global registry
**Checkpoint**: After this step, `registry.list_available()` should include your language ID.
## Step 3: Register the Module
Ensure the handler module is imported so the decorator fires.
1. Read `core/languages/__init__.py` — check how existing languages are imported
2. Add your language import so `@register_handler` fires on startup
3. Import should be at module level or via lazy import pattern
**Checkpoint**: Run `python -c "from core.languages import <lang>"` to verify no import errors.
## Step 4: Implement Protocol Methods
Implement the protocol methods for each supported feature.
1. Read `core/protocols/optimizer.py``OptimizerProtocol` requires `optimizer_optimize(request, data)`
2. Read `core/protocols/testgen.py``TestGenProtocol` requires `testgen_generate(request, data)`
3. Read `core/protocols/code_repair.py``CodeRepairProtocol` requires `code_repair_repair(user_id, optimization_id, ctx)`
4. Follow the pattern from Python/JS handlers:
```python
async def optimizer_optimize(self, request, data):
# 1. Build context from data.source_code
# 2. Call call_llm() with language-specific prompts
# 3. Parse response
# 4. Return (status_code, response_schema)
```
**Checkpoint**: Each method must be `async def` and return the expected response type.
## Step 5: Create Prompt Templates
Create language-specific prompt templates.
1. Create `.md` files alongside the handler modules (e.g., `optimizer/system_prompt.md`)
2. Use Jinja2 templating for dynamic content
3. Prompts should include language-specific context (version, conventions, stdlib)
4. Follow the pattern from `core/languages/python/optimizer/context_utils/`
**Checkpoint**: Prompts should produce valid, non-empty system and user messages.
## Step 6: Update Routers
Add language dispatch to the shared routers.
1. Edit `core/shared/optimizer_router.py` — add a branch for your language:
```python
if data.language == "<lang>":
from core.languages.<lang>.optimizer import optimize_<lang> # noqa: PLC0415
return await optimize_<lang>(request, data)
```
2. Edit `core/shared/testgen_router.py` — add the same pattern if testgen is supported
3. Use lazy imports (inside the function body) to avoid circular dependencies
**Checkpoint**: Both routers should dispatch correctly for the new language.
## Step 7: Add Tests
Write tests for the new language handler.
1. Create `tests/<lang>/` directory mirroring the source structure
2. Test handler registration: verify `registry.get_handler("<lang>")` returns the correct class
3. Test feature dispatch: verify `get_handler_for_feature("<lang>", "optimizer")` works
4. Test optimization flow end-to-end (mock LLM calls)
5. Use `@pytest.mark.asyncio` for async tests
6. Run: `uv run pytest tests/<lang>/ -v`
**Checkpoint**: All tests must pass. Run `uv run prek run --all-files` for formatting/lint.
## Key Files Reference
| File | What to modify |
|------|---------------|
| `core/languages/<lang>/` | New handler directory |
| `core/registry.py` | No changes needed (decorator handles it) |
| `core/dispatcher.py` | No changes needed (dynamic lookup) |
| `core/protocols/` | Reference for protocol methods |
| `core/shared/optimizer_router.py` | Add language dispatch branch |
| `core/shared/testgen_router.py` | Add language dispatch branch (if testgen) |
| `core/languages/__init__.py` | Add import for decorator registration |

View file

@ -0,0 +1,107 @@
---
name: debug-optimization-failure
description: >
Diagnose why an optimization produced no results or failed silently.
Use when an optimization request returns errors, empty results, or all
candidates are rejected. Walks through request validation, router dispatch,
context extraction, LLM calls, postprocessing, and logging stages.
---
# Debug Optimization Failure
Use this workflow when an optimization request fails or produces no results. Work through the stages sequentially — stop at the first failure found.
## Step 1: Validate the Request
Check that the incoming `OptimizeSchema` is well-formed.
1. Read `core/shared/optimizer_models.py` — verify the request matches `OptimizeSchema` fields
2. Check required fields: `source_code`, `trace_id` must be non-empty
3. Check `language` field — must be `"python"`, `"javascript"`, `"typescript"`, or `"java"`
4. Check `n_candidates` — default is 5, must be positive
**Checkpoint**: If the request schema is invalid, the error comes from Pydantic validation. Check the 400 response for field-level errors.
## Step 2: Check Router Dispatch
Verify the correct language handler is invoked.
1. Read `core/shared/optimizer_router.py` — the `optimize()` endpoint dispatches by `data.language`
2. Supported routes:
- `"javascript"` / `"typescript"``core.languages.js_ts.optimizer.optimize_javascript`
- `"java"``core.languages.java.optimizer.optimize_java`
- Default → `core.languages.python.optimizer.optimizer.optimize_python`
3. Check for import errors — lazy imports inside the function body may fail if a language module is missing
**Checkpoint**: If dispatch fails, you'll see an `ImportError`. Check that the language module exists under `core/languages/`.
## Step 3: Check Context Extraction
Verify the optimization context is built correctly.
1. Read `core/languages/python/optimizer/context_utils/optimizer_context.py`
2. `BaseOptimizerContext.get_dynamic_context()` dispatches to Single or Multi context
3. Check `get_system_prompt()` and `get_user_prompt()` — they should produce non-empty prompts
4. Check `extract_code_and_explanation_from_llm_res()` — this parses markdown code blocks from the LLM response
**Checkpoint**: If context extraction returns empty prompts, check that `source_code` in the request is valid Python/JS code.
## Step 4: Check LLM Calls
Verify the LLM is called correctly and returns valid responses.
1. Read `aiservice/llm.py``call_llm()` is the universal call handler
2. Check `get_llm_client(model_type)` returns a valid client (not `None`)
3. Environment variables required:
- OpenAI: `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, `OPENAI_API_VERSION`
- Anthropic: `ANTHROPIC_FOUNDRY_API_KEY`, `ANTHROPIC_FOUNDRY_BASE_URL`
4. Check `optimizer_config.py``get_model_distribution()` determines how many calls per model
5. Look for exceptions: `"LLM client for model type '...' is not available"`
**Checkpoint**: If LLM calls fail, check environment variables and API key validity. Network errors will raise exceptions.
## Step 5: Check Postprocessing
Verify candidates survive postprocessing.
1. Read `core/languages/python/optimizer/postprocess.py`
2. `deduplicate_optimizations()` — removes candidates with identical AST (via `ast.parse()` + `ast.dump()`)
3. `equality_check()` — removes candidates identical to the original code
4. Check if ALL candidates were deduplicated or matched the original
**Checkpoint**: If all candidates are removed by postprocessing, the LLM is generating identical or no-op code. Try increasing `n_candidates` or checking prompt quality.
## Step 6: Check Response Construction
Verify the response is properly constructed.
1. Each successful candidate produces an `OptimizeResponseItemSchema`
2. `parse_and_generate_candidate_schema()` converts extracted code to the schema
3. `is_valid_code()` validates syntax — `cst.ParserSyntaxError` or `ValidationError` means malformed output
4. If parsing fails, the candidate is dropped and a Sentry message is captured
**Checkpoint**: If candidates parse but the response is empty, check the validation step in the optimizer flow.
## Step 7: Check Logging
Verify the optimization was logged for debugging.
1. Read `core/log_features/models.py``OptimizationFeatures` stores per-trace-id data
2. Check `optimizations_raw` (before postprocessing) vs `optimizations_post` (after)
3. LLM calls are recorded via `record_llm_call()` in the `finally` block of `call_llm()`
4. PostHog events track `aiservice-optimize-openai-usage`
**Checkpoint**: If logging shows raw candidates but no post candidates, postprocessing removed them all.
## Key Files Reference
| File | What to check |
|------|---------------|
| `core/shared/optimizer_router.py` | Language dispatch |
| `core/shared/optimizer_models.py` | Request validation |
| `core/languages/python/optimizer/optimizer.py` | Optimization flow |
| `core/languages/python/optimizer/context_utils/optimizer_context.py` | Context extraction |
| `core/languages/python/optimizer/postprocess.py` | Dedup and validation |
| `aiservice/llm.py` | LLM calls and client setup |
| `core/shared/optimizer_config.py` | Model distribution |
| `core/log_features/models.py` | Logging and tracking |

View file

@ -0,0 +1,103 @@
---
name: debug-test-generation
description: >
Diagnose why test generation failed or produced invalid tests.
Use when testgen returns errors, empty results, or produces tests
that fail to compile or run. Walks through request validation,
router dispatch, context building, prompt construction, LLM calls,
postprocessing, instrumentation, and output validation.
---
# Debug Test Generation
Use this workflow when test generation fails or produces invalid tests. Work through the stages sequentially — stop at the first failure found.
## Step 1: Validate the Request
Check that the incoming testgen request is well-formed.
1. Read the testgen request schema in the relevant testgen module
2. Verify required fields: `source_code`, `trace_id` must be non-empty
3. Check `language` field — must match a supported language
4. Check for valid code — source code should parse without syntax errors
**Checkpoint**: If the request schema is invalid, the error comes from Pydantic validation. Check the 400 response.
## Step 2: Check Router Dispatch
Verify the correct language handler is invoked.
1. Read `core/shared/testgen_router.py` — the `testgen()` endpoint dispatches by `data.language`
2. Supported routes:
- `"javascript"` / `"typescript"``core.languages.js_ts.testgen.generate_tests_javascript`
- `"java"``core.languages.java.testgen.generate_tests_java`
- Default → `core.languages.python.testgen.testgen.generate_tests_python`
3. Check for `ImportError` — lazy imports may fail if a language module is broken
**Checkpoint**: If dispatch fails, check that the language module exists and imports cleanly.
## Step 3: Check Context Building
Verify the testgen context is constructed correctly.
1. Read `core/languages/python/testgen/testgen.py``build_prompt()` constructs the prompts
2. Check that source code and dependency code are passed correctly
3. Verify the Jinja2 template renders without errors
4. Check for async/sync variants — the prompt builder handles both
**Checkpoint**: If context is empty or malformed, check the input `source_code` and `dependency_code`.
## Step 4: Check Prompt Construction
Verify the LLM prompts are well-formed.
1. The `build_prompt()` function uses Jinja2 templates (`.md` files alongside the module)
2. System prompt sets the role and language context
3. User prompt includes the source code and test context
4. Check that prompts are non-empty and contain the function to test
**Checkpoint**: If prompts are empty, check the template files and Jinja2 rendering.
## Step 5: Check LLM Response
Verify the LLM returns valid test code.
1. Read `aiservice/llm.py``call_llm()` handles the API call
2. Check for network errors or API key issues (same as optimization debugging)
3. Look for `LLMOutputParseError` — this means the LLM returned unparseable output
4. Check the raw response content — it should contain markdown code blocks with test code
**Checkpoint**: If the LLM returns malformed output, check the prompt quality and model selection.
## Step 6: Check Postprocessing
Verify generated tests survive postprocessing.
1. Read `core/languages/python/testgen/postprocessing/` directory
2. `add_missing_imports.py` — adds `from __future__ import annotations` if needed
3. Check for syntax errors in generated test code — `cst.ParserSyntaxError` means malformed code
4. Verify imports are resolved correctly
**Checkpoint**: If postprocessing fails, the LLM generated syntactically invalid code. Check the raw output.
## Step 7: Check Instrumentation
Verify tests are properly instrumented.
1. Read `core/languages/python/testgen/instrumentation/instrument_new_tests.py`
2. `instrument_tests()` applies behavior and performance instrumentation
3. `detect_frameworks_from_code()` identifies ML frameworks (PyTorch, TensorFlow, JAX)
4. `_create_device_sync_precompute_statements()` adds GPU sync calls for timing accuracy
5. Check that instrumented tests still compile — instrumentation may introduce syntax errors
**Checkpoint**: If instrumented tests fail to compile, check the instrumentation transforms. The issue is usually in import handling or device sync injection.
## Key Files Reference
| File | What to check |
|------|---------------|
| `core/shared/testgen_router.py` | Language dispatch |
| `core/languages/python/testgen/testgen.py` | Testgen flow, prompt building |
| `core/languages/python/testgen/postprocessing/` | Import management, cleanup |
| `core/languages/python/testgen/instrumentation/instrument_new_tests.py` | Test instrumentation |
| `aiservice/llm.py` | LLM calls and client setup |

View file

@ -0,0 +1,20 @@
{
"name": "codeflash/codeflash-internal-skills",
"version": "0.1.0",
"summary": "Procedural workflows for developing and debugging codeflash-internal",
"private": true,
"skills": {
"debug-optimization-failure": {
"path": "skills/debug-optimization-failure/SKILL.md"
},
"add-language-support": {
"path": "skills/add-language-support/SKILL.md"
},
"add-api-endpoint": {
"path": "skills/add-api-endpoint/SKILL.md"
},
"debug-test-generation": {
"path": "skills/debug-test-generation/SKILL.md"
}
}
}

View file

@ -0,0 +1,28 @@
You are a coding agent with access to an MCP tool called `query_library_docs` provided by `tessl`. This tool allows you to query documentation that may be relevant to your task.
Before you begin working on the user request, you should use the `query_library_docs` tool to search for relevant documentation. This is especially important when:
- The request mentions specific code files, functions, classes, or services
- The request involves debugging errors or investigating issues
- The request asks about how something works or why something behaves a certain way
- The request involves making changes to existing code
- The request references specific frameworks, libraries, or systems
When in doubt, use the tool. It's better to query documentation even if it might not be relevant than to miss important context.
How to use the tool:
1. Extract key terms, file names, service names, error types, or concepts from the user's request
2. Call `query_library_docs` with relevant search terms
3. Use the returned documentation to inform your approach to the task
4. Proceed with completing the user's request using both the documentation context and your own analysis
Important rules:
- Do NOT ask for permission to use the tool - just use it
- Do NOT explain that you're going to use the tool - just use it
- If the tool fails or returns no results, simply continue with the task as you normally would
- You may call the tool multiple times with different search terms if needed
- Use the tool BEFORE reading files or making changes
After using the tool (or if the tool returns nothing useful), proceed directly to completing the user's request. Your response should focus on addressing the user's needs, incorporating any relevant documentation you found.

View file

@ -0,0 +1,11 @@
{
"name": "tessl/cli-setup",
"private": false,
"version": "0.70.0",
"summary": "Tessl CLI MCP tool usage guidelines",
"steering": {
"query_library_docs": {
"rules": "steering/query_library_docs.md"
}
}
}

View file

@ -0,0 +1,681 @@
# Message Batches
The Message Batches API allows you to send multiple message requests in a single batch, processing them asynchronously. Batches are cost-efficient for bulk operations and provide significant cost savings (50% off) compared to individual API calls.
## Overview
Message Batches enable:
- Bulk processing of multiple independent message requests
- Asynchronous result retrieval
- 50% cost reduction compared to standard API
- Processing of up to 10,000 requests per batch
- 24-hour processing window with automatic expiration
## API Reference
```typescript { .api }
class Batches extends APIResource {
create(params: MessageBatchCreateParams): APIPromise<MessageBatch>;
retrieve(messageBatchID: string): APIPromise<MessageBatch>;
list(params?: MessageBatchListParams): MessageBatchesPage;
delete(messageBatchID: string): APIPromise<DeletedMessageBatch>;
cancel(messageBatchID: string): APIPromise<MessageBatch>;
results(messageBatchID: string): AsyncIterable<MessageBatchIndividualResponse>;
}
```
Access via:
```typescript
client.messages.batches.* // Standard API
client.beta.messages.batches.* // Beta API
```
## Creating a Batch
```typescript { .api }
client.messages.batches.create(
params: MessageBatchCreateParams
): APIPromise<MessageBatch>;
interface MessageBatchCreateParams {
requests: Array<{
custom_id: string;
params: MessageCreateParams;
}>;
}
interface MessageBatch {
id: string;
type: 'message_batch';
processing_status: 'in_progress' | 'canceling' | 'ended';
request_counts: MessageBatchRequestCounts;
ended_at: string | null;
created_at: string;
expires_at: string;
archived_at: string | null;
cancel_initiated_at: string | null;
results_url: string | null;
}
interface MessageBatchRequestCounts {
processing: number;
succeeded: number;
errored: number;
canceled: number;
expired: number;
}
```
**Example:**
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const batch = await client.messages.batches.create({
requests: [
{
custom_id: 'request-1',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is the capital of France?',
},
],
},
},
{
custom_id: 'request-2',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'What is 2 + 2?',
},
],
},
},
{
custom_id: 'request-3',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Explain quantum computing in simple terms.',
},
],
},
},
],
});
console.log('Batch ID:', batch.id);
console.log('Status:', batch.processing_status);
console.log('Created at:', batch.created_at);
console.log('Expires at:', batch.expires_at);
```
### Custom IDs
Each request requires a unique `custom_id`:
```typescript
// ✅ Good: Meaningful IDs
{
custom_id: 'user-123-question-1',
params: { /* ... */ }
}
// ✅ Good: Sequential IDs
{
custom_id: 'batch-001-item-001',
params: { /* ... */ }
}
// ❌ Bad: Duplicate IDs
{
custom_id: 'request', // Used multiple times
params: { /* ... */ }
}
```
Custom IDs are used to match requests with results and must be unique within a batch.
## Checking Batch Status
```typescript { .api }
client.messages.batches.retrieve(
messageBatchID: string
): APIPromise<MessageBatch>;
```
**Example:**
```typescript
const batch = await client.messages.batches.retrieve('batch_123');
console.log('Processing status:', batch.processing_status);
console.log('Request counts:', batch.request_counts);
// {
// processing: 5,
// succeeded: 10,
// errored: 2,
// canceled: 0,
// expired: 0
// }
if (batch.processing_status === 'ended') {
console.log('Batch processing complete!');
console.log('Results URL:', batch.results_url);
}
```
### Processing Status
```typescript
type ProcessingStatus =
| 'in_progress' // Batch is being processed
| 'canceling' // Cancellation in progress
| 'ended' // Processing complete (success/error/expired)
;
```
### Polling for Completion
```typescript
async function waitForBatch(batchId: string): Promise<MessageBatch> {
while (true) {
const batch = await client.messages.batches.retrieve(batchId);
if (batch.processing_status === 'ended') {
return batch;
}
// Wait before polling again
await new Promise(resolve => setTimeout(resolve, 60000)); // 1 minute
}
}
const batch = await client.messages.batches.create({ /* ... */ });
console.log('Waiting for batch to complete...');
const completed = await waitForBatch(batch.id);
console.log('Batch completed!', completed.request_counts);
```
## Retrieving Results
```typescript { .api }
client.messages.batches.results(
messageBatchID: string
): AsyncIterable<MessageBatchIndividualResponse>;
interface MessageBatchIndividualResponse {
custom_id: string;
result: MessageBatchResult;
}
type MessageBatchResult =
| MessageBatchSucceededResult
| MessageBatchErroredResult
| MessageBatchCanceledResult
| MessageBatchExpiredResult;
interface MessageBatchSucceededResult {
type: 'succeeded';
message: Message;
}
interface MessageBatchErroredResult {
type: 'errored';
error: {
type: string;
message: string;
};
}
interface MessageBatchCanceledResult {
type: 'canceled';
}
interface MessageBatchExpiredResult {
type: 'expired';
}
```
**Example:**
```typescript
const batch = await client.messages.batches.create({ /* ... */ });
// Wait for completion
await waitForBatch(batch.id);
// Retrieve results
const results = await client.messages.batches.results(batch.id);
for await (const result of results) {
console.log('Custom ID:', result.custom_id);
if (result.result.type === 'succeeded') {
const message = result.result.message;
console.log('Success:', message.content[0].text);
} else if (result.result.type === 'errored') {
console.error('Error:', result.result.error.message);
} else if (result.result.type === 'canceled') {
console.log('Request was canceled');
} else if (result.result.type === 'expired') {
console.log('Request expired');
}
}
```
### Collecting Results
```typescript
const batch = await client.messages.batches.create({ /* ... */ });
await waitForBatch(batch.id);
const results = await client.messages.batches.results(batch.id);
const allResults: MessageBatchIndividualResponse[] = [];
for await (const result of results) {
allResults.push(result);
}
console.log(`Processed ${allResults.length} results`);
// Group by result type
const succeeded = allResults.filter(r => r.result.type === 'succeeded');
const errored = allResults.filter(r => r.result.type === 'errored');
console.log(`Succeeded: ${succeeded.length}, Errored: ${errored.length}`);
```
## Listing Batches
```typescript { .api }
client.messages.batches.list(
params?: MessageBatchListParams
): MessageBatchesPage;
interface MessageBatchListParams {
before_id?: string;
after_id?: string;
limit?: number; // Default: 20, max: 100
}
```
**Example:**
```typescript
// List recent batches
const batches = await client.messages.batches.list({
limit: 10,
});
for (const batch of batches.data) {
console.log('Batch:', batch.id);
console.log('Status:', batch.processing_status);
console.log('Counts:', batch.request_counts);
}
// Auto-pagination
for await (const batch of client.messages.batches.list({ limit: 20 })) {
console.log(batch.id);
}
```
## Canceling a Batch
```typescript { .api }
client.messages.batches.cancel(
messageBatchID: string
): APIPromise<MessageBatch>;
```
**Example:**
```typescript
const batch = await client.messages.batches.create({ /* ... */ });
// Cancel the batch
const canceled = await client.messages.batches.cancel(batch.id);
console.log('Status:', canceled.processing_status); // 'canceling'
// Wait for cancellation to complete
await waitForBatch(canceled.id);
// Check final status
const final = await client.messages.batches.retrieve(canceled.id);
console.log('Canceled requests:', final.request_counts.canceled);
```
**Note**: Cancellation may take time. In-flight requests will complete, but pending requests will be canceled.
## Deleting a Batch
```typescript { .api }
client.messages.batches.delete(
messageBatchID: string
): APIPromise<DeletedMessageBatch>;
interface DeletedMessageBatch {
id: string;
type: 'message_batch';
deleted: boolean;
}
```
**Example:**
```typescript
// Delete a batch (must be ended first)
const deleted = await client.messages.batches.delete('batch_123');
console.log('Deleted:', deleted.deleted); // true
```
**Important**: You can only delete batches that have `processing_status === 'ended'`. Archived batches are automatically deleted.
## Complete Workflow Example
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// 1. Create batch
const batch = await client.messages.batches.create({
requests: [
{
custom_id: 'analysis-1',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Analyze this data: [1, 2, 3, 4, 5]',
},
],
},
},
{
custom_id: 'analysis-2',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Summarize: The quick brown fox jumps over the lazy dog.',
},
],
},
},
],
});
console.log('Created batch:', batch.id);
// 2. Poll for completion
console.log('Waiting for completion...');
let currentBatch = batch;
while (currentBatch.processing_status !== 'ended') {
await new Promise(resolve => setTimeout(resolve, 30000)); // 30 seconds
currentBatch = await client.messages.batches.retrieve(batch.id);
console.log('Status:', currentBatch.request_counts);
}
// 3. Retrieve results
console.log('Retrieving results...');
const results = await client.messages.batches.results(batch.id);
const resultMap = new Map<string, MessageBatchIndividualResponse>();
for await (const result of results) {
resultMap.set(result.custom_id, result);
}
// 4. Process results
for (const [customId, result] of resultMap) {
console.log(`\nResult for ${customId}:`);
if (result.result.type === 'succeeded') {
console.log('Response:', result.result.message.content[0].text);
} else {
console.error('Failed:', result.result);
}
}
// 5. Cleanup (optional)
// await client.messages.batches.delete(batch.id);
```
## Use Cases
### Bulk Document Processing
```typescript
const documents = await loadDocuments(); // Load from database
const batch = await client.messages.batches.create({
requests: documents.map((doc, index) => ({
custom_id: `doc-${doc.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'base64',
media_type: 'application/pdf',
data: doc.content,
},
},
{
type: 'text',
text: 'Summarize this document.',
},
],
},
],
},
})),
});
```
### Data Analysis Pipeline
```typescript
const dataPoints = await fetchDataPoints();
const batch = await client.messages.batches.create({
requests: dataPoints.map((data) => ({
custom_id: `analysis-${data.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Analyze this data and provide insights: ${JSON.stringify(data)}`,
},
],
},
})),
});
await waitForBatch(batch.id);
const results = await client.messages.batches.results(batch.id);
for await (const result of results) {
if (result.result.type === 'succeeded') {
await saveAnalysis(result.custom_id, result.result.message);
}
}
```
### Translation Service
```typescript
const textsToTranslate = [
{ id: '1', text: 'Hello, world!', targetLang: 'Spanish' },
{ id: '2', text: 'Good morning!', targetLang: 'French' },
{ id: '3', text: 'Thank you!', targetLang: 'German' },
];
const batch = await client.messages.batches.create({
requests: textsToTranslate.map((item) => ({
custom_id: `translation-${item.id}`,
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{
role: 'user',
content: `Translate "${item.text}" to ${item.targetLang}`,
},
],
},
})),
});
```
## Limitations
- **Batch Size**: Maximum 10,000 requests per batch
- **Expiration**: Batches expire after 24 hours
- **Processing Time**: No guaranteed completion time (typically minutes to hours)
- **No Streaming**: Individual requests cannot stream results
- **Rate Limits**: Subject to account-level rate limits
## Best Practices
### Error Handling
```typescript
const results = await client.messages.batches.results(batchId);
for await (const result of results) {
if (result.result.type === 'succeeded') {
await processSuccess(result);
} else if (result.result.type === 'errored') {
await logError(result.custom_id, result.result.error);
await retryRequest(result.custom_id);
} else if (result.result.type === 'expired') {
await handleExpiredRequest(result.custom_id);
}
}
```
### Request Chunking
```typescript
// ✅ Good: Process in chunks
async function processBatches(requests: Request[]) {
const CHUNK_SIZE = 5000;
for (let i = 0; i < requests.length; i += CHUNK_SIZE) {
const chunk = requests.slice(i, i + CHUNK_SIZE);
const batch = await client.messages.batches.create({
requests: chunk.map((req, index) => ({
custom_id: `chunk-${i / CHUNK_SIZE}-item-${index}`,
params: req.params,
})),
});
await waitForBatch(batch.id);
await processResults(batch.id);
}
}
// ❌ Bad: Exceeding limits
const batch = await client.messages.batches.create({
requests: allRequests.map(/* ... */), // Could be > 10,000
});
```
### Result Processing
```typescript
// ✅ Good: Stream and process results
const results = await client.messages.batches.results(batchId);
for await (const result of results) {
// Process immediately, don't accumulate
await handleResult(result);
}
// ❌ Bad: Loading all into memory
const results = await client.messages.batches.results(batchId);
const allResults = [];
for await (const result of results) {
allResults.push(result); // Can be large
}
```
### Monitoring
```typescript
async function monitorBatch(batchId: string) {
const startTime = Date.now();
while (true) {
const batch = await client.messages.batches.retrieve(batchId);
const elapsed = (Date.now() - startTime) / 1000 / 60; // minutes
console.log(`[${elapsed.toFixed(1)}m] Status:`, batch.request_counts);
if (batch.processing_status === 'ended') {
console.log('Batch complete!');
return batch;
}
await new Promise(resolve => setTimeout(resolve, 60000)); // 1 min
}
}
```
## Cost Optimization
Batches provide 50% cost savings:
```typescript
// Standard API cost: $0.003 per 1K input tokens (example)
// Batch API cost: $0.0015 per 1K input tokens (50% off)
// For 1000 requests with 500 input tokens each:
// Standard: 1000 * 0.5K * $0.003 = $1.50
// Batch: 1000 * 0.5K * $0.0015 = $0.75
// Savings: $0.75 (50%)
```
Use batches for:
- Non-urgent bulk processing
- Offline analysis
- Data pipelines
- Scheduled tasks
Use standard API for:
- Real-time responses
- User-facing applications
- Time-sensitive requests
## See Also
- [Messages API](./messages.md) - Individual message creation
- [Tools](./tools.md) - Using tools in batch requests
- [Types](./types.md) - Batch type definitions

View file

@ -0,0 +1,678 @@
# Beta Features
The Anthropic SDK provides access to experimental and advanced features through the beta namespace. Beta features require specific beta headers and may change before general availability.
## Overview
Beta features include:
- **Code Execution**: Execute Python and bash code in sandboxed environments
- **Computer Use**: Bash, text editor, and computer control tools
- **Extended Thinking**: Access Claude's reasoning process
- **MCP (Model Context Protocol)**: External tool integration
- **Memory Tools**: Persistent memory across conversations
- **Web Search & Fetch**: Search the web and fetch content
- **Context Management**: Advanced context window management
- **Structured Outputs**: JSON output with automatic parsing
- **Files API**: File upload and management
- **Skills API**: Custom skill definitions
## Accessing Beta Features
Use the `client.beta` namespace and include appropriate beta headers:
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['code-execution-2025-05-22'], // Required beta header
messages: [{ role: 'user', content: 'Execute Python code' }],
});
```
## Available Beta Headers
```typescript { .api }
type AnthropicBeta =
| 'message-batches-2024-09-24'
| 'prompt-caching-2024-07-31'
| 'computer-use-2024-10-22'
| 'computer-use-2025-01-24'
| 'pdfs-2024-09-25'
| 'token-counting-2024-11-01'
| 'token-efficient-tools-2025-02-19'
| 'output-128k-2025-02-19'
| 'files-api-2025-04-14'
| 'mcp-client-2025-04-04'
| 'dev-full-thinking-2025-05-14'
| 'interleaved-thinking-2025-05-14'
| 'code-execution-2025-05-22'
| 'code-execution-2025-08-25'
| 'extended-cache-ttl-2025-04-11'
| 'context-1m-2025-08-07'
| 'context-management-2025-06-27'
| 'model-context-window-exceeded-2025-08-26'
| 'skills-2025-10-02'
;
```
## Code Execution
Execute Python code in a sandboxed environment:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['code-execution-2025-05-22'],
tools: [
{
name: 'code_execution',
type: 'code_execution_20250522',
},
],
messages: [
{
role: 'user',
content: 'Calculate the factorial of 10 using Python.',
},
],
});
// Claude will use code_execution tool
for (const block of message.content) {
if (block.type === 'code_execution_result') {
console.log('Output:', block.output);
}
}
```
**Versions**:
- `code_execution_20250522`: Version from 2025-05-22
- `code_execution_20250825`: Version from 2025-08-25
## Computer Use Tools
Interact with computer interfaces:
### Bash Tool
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['computer-use-2025-01-24'],
tools: [
{
name: 'bash',
type: 'bash_20250124',
},
],
messages: [
{
role: 'user',
content: 'List files in the current directory.',
},
],
});
```
### Text Editor Tool
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['computer-use-2025-01-24'],
tools: [
{
name: 'str_replace_editor',
type: 'text_editor_20250728',
},
],
messages: [
{
role: 'user',
content: 'Create a file and write "Hello World" to it.',
},
],
});
```
**Tool Versions**:
- `bash_20241022`, `bash_20250124`: Bash execution
- `text_editor_20241022`, `text_editor_20250124`, `text_editor_20250429`, `text_editor_20250728`: Text editor operations
- `computer_use_20241022`, `computer_use_20250124`: Full computer control
## Extended Thinking
Access Claude's reasoning process:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
betas: ['dev-full-thinking-2025-05-14'],
thinking: {
type: 'enabled',
budget_tokens: 2000,
},
messages: [
{
role: 'user',
content: 'Solve this complex problem: ...',
},
],
});
// Access thinking blocks
for (const block of message.content) {
if (block.type === 'thinking') {
console.log('Reasoning:', block.thinking);
console.log('Signature:', block.signature);
} else if (block.type === 'text') {
console.log('Answer:', block.text);
}
}
```
**Beta Headers**:
- `dev-full-thinking-2025-05-14`: Full thinking traces
- `interleaved-thinking-2025-05-14`: Interleaved thinking and responses
## Memory Tools
Persistent memory across conversations:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['memory-2025-08-18'],
tools: [
{
name: 'memory',
type: 'memory_20250818',
commands: {
create: {
enabled: true,
},
view: {
enabled: true,
},
delete: {
enabled: true,
},
},
},
],
messages: [
{
role: 'user',
content: 'Remember that my name is Alice.',
},
],
});
// Later conversation
const message2 = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['memory-2025-08-18'],
tools: [
{
name: 'memory',
type: 'memory_20250818',
commands: {
view: { enabled: true },
},
},
],
messages: [
{
role: 'user',
content: 'What is my name?',
},
],
});
// Claude will recall: "Alice"
```
**Memory Commands**:
- `create`: Store new memory
- `view`: Retrieve memory
- `delete`: Remove memory
- `insert`: Add content to memory
- `str_replace`: Replace content in memory
- `rename`: Rename memory
## Web Search & Fetch
Search the web and fetch content:
### Web Search
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['web-search-2025-03-05'],
tools: [
{
name: 'web_search',
type: 'web_search_20250305',
},
],
messages: [
{
role: 'user',
content: 'Search for recent AI news.',
},
],
});
// Claude will use web_search tool and return results
for (const block of message.content) {
if (block.type === 'web_search_result') {
console.log('Query:', block.query);
console.log('Results:', block.results);
}
}
```
### Web Fetch
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['web-fetch-2025-09-10'],
tools: [
{
name: 'web_fetch',
type: 'web_fetch_20250910',
},
],
messages: [
{
role: 'user',
content: 'Fetch the content from https://example.com',
},
],
});
```
## MCP (Model Context Protocol)
Integrate external tools via MCP:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['mcp-client-2025-04-04'],
mcp_servers: [
{
url: 'https://mcp-server.example.com',
tools: ['*'], // Allow all tools from server
},
],
messages: [
{
role: 'user',
content: 'Use external MCP tools.',
},
],
});
```
## Context Management
Advanced context window management:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['context-management-2025-06-27'],
context_management: {
input_tokens_clear_at_least: 50000, // Clear when over 50k tokens
tool_uses: {
keep: 10, // Keep last 10 tool uses
},
thinking_turns: {
keep: 'all', // Keep all thinking turns
},
},
messages: longConversation,
});
```
**Configuration**:
- `input_tokens_clear_at_least`: Threshold for clearing context
- `tool_uses.keep`: Number of tool interactions to retain
- `thinking_turns.keep`: How many thinking turns to keep (`'all'` or `'none'`)
## Structured Outputs
JSON output with automatic parsing:
```typescript
import { betaZodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
import { z } from 'zod';
const outputFormat = betaZodOutputFormat(
z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
})
);
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['structured-outputs-2025-02-19'],
output_format: {
type: 'json_schema',
schema: outputFormat.schema,
},
messages: [
{
role: 'user',
content: 'Extract information from: John Doe, 30, john@example.com',
},
],
});
// Parse output
const parsed = outputFormat.parse(message.content[0].text);
console.log(parsed.name); // "John Doe"
console.log(parsed.age); // 30
console.log(parsed.email); // "john@example.com"
```
Or use JSON Schema:
```typescript
import { betaJSONSchemaOutputFormat } from '@anthropic-ai/sdk/helpers/json-schema';
const outputFormat = betaJSONSchemaOutputFormat({
type: 'object',
properties: {
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
confidence: { type: 'number', minimum: 0, maximum: 1 },
},
required: ['sentiment', 'confidence'],
});
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 512,
betas: ['structured-outputs-2025-02-19'],
output_format: {
type: 'json_schema',
schema: outputFormat.schema,
},
messages: [
{
role: 'user',
content: 'Analyze sentiment: This product is amazing!',
},
],
});
const parsed = outputFormat.parse(message.content[0].text);
console.log(parsed.sentiment); // "positive"
console.log(parsed.confidence); // 0.95
```
## Prompt Caching
Cache frequently used prompts for reduced latency:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['prompt-caching-2024-07-31'],
system: [
{
type: 'text',
text: 'You are a helpful assistant.',
},
{
type: 'text',
text: largeKnowledgeBase,
cache_control: {
type: 'ephemeral',
ttl: '1h', // Cache for 1 hour
},
},
],
messages: [
{
role: 'user',
content: 'Question about the knowledge base...',
},
],
});
console.log('Cache usage:', message.usage);
// {
// input_tokens: 1000,
// cache_read_input_tokens: 50000, // Read from cache
// output_tokens: 500
// }
```
**TTL Options**:
- `5m`: 5 minutes (default)
- `1h`: 1 hour (requires `extended-cache-ttl-2025-04-11` beta)
## Message Batches
Process multiple messages asynchronously:
```typescript
const batch = await client.beta.messages.batches.create({
betas: ['message-batches-2024-09-24'],
requests: [
{
custom_id: 'request-1',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Task 1' }],
},
},
{
custom_id: 'request-2',
params: {
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Task 2' }],
},
},
],
});
console.log('Batch ID:', batch.id);
```
See [batches.md](./batches.md) for complete documentation.
## Files API
Upload and manage files:
```typescript
import { toFile } from '@anthropic-ai/sdk';
import fs from 'fs';
const file = await client.beta.files.upload({
file: await toFile(fs.createReadStream('document.pdf'), 'document.pdf', { type: 'application/pdf' }),
betas: ['files-api-2025-04-14'],
});
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['files-api-2025-04-14'],
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'file',
file_id: file.id,
},
},
{
type: 'text',
text: 'Summarize this document.',
},
],
},
],
});
```
See [files.md](./files.md) for complete documentation.
## Skills API
Create and manage custom skills:
```typescript
const skill = await client.beta.skills.create({
name: 'data_analyzer',
description: 'Analyze structured data',
definition: {
type: 'analysis',
capabilities: ['statistics', 'visualization'],
},
betas: ['skills-2025-10-02'],
});
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['skills-2025-10-02'],
skills: [
{
type: 'skill',
skill_id: skill.id,
},
],
messages: [
{
role: 'user',
content: 'Analyze this data...',
},
],
});
```
See [skills.md](./skills.md) for complete documentation.
## Combining Beta Features
Use multiple beta features together:
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
betas: [
'code-execution-2025-05-22',
'dev-full-thinking-2025-05-14',
'web-search-2025-03-05',
],
thinking: {
type: 'enabled',
budget_tokens: 2000,
},
tools: [
{
name: 'code_execution',
type: 'code_execution_20250522',
},
{
name: 'web_search',
type: 'web_search_20250305',
},
],
messages: [
{
role: 'user',
content: 'Search for recent data on topic X, then analyze it with Python.',
},
],
});
// Claude can use thinking, web search, and code execution together
```
## Beta API Namespace
All beta features are accessed through the `client.beta` namespace:
```typescript
client.beta.messages.* // Beta messages API
client.beta.messages.batches.* // Beta message batches
client.beta.models.* // Beta models API
client.beta.files.* // Files API
client.beta.skills.* // Skills API
client.beta.skills.versions.* // Skill versions
```
## Best Practices
### Feature Detection
```typescript
// ✅ Good: Check beta support
const betaHeaders: AnthropicBeta[] = ['code-execution-2025-05-22'];
try {
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: betaHeaders,
tools: [{ name: 'code_execution', type: 'code_execution_20250522' }],
messages: [{ role: 'user', content: 'Execute code' }],
});
} catch (error) {
if (error instanceof Anthropic.BadRequestError) {
console.error('Beta feature not available');
}
}
```
### Migration Planning
```typescript
// ✅ Good: Prepare for GA migration
const USE_BETA = process.env.USE_CODE_EXECUTION === 'true';
const message = await (USE_BETA ? client.beta.messages : client.messages).create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
...(USE_BETA && { betas: ['code-execution-2025-05-22'] }),
messages: [{ role: 'user', content: 'Task' }],
});
```
## See Also
- [Messages API](./messages.md) - Core message creation
- [Tools](./tools.md) - Tool use and helpers
- [Files](./files.md) - File upload and management
- [Skills](./skills.md) - Custom skill definitions
- [Batches](./batches.md) - Batch processing

View file

@ -0,0 +1,744 @@
# Client Initialization and Configuration
The Anthropic client is the primary interface for interacting with the Anthropic API. It handles authentication, configuration, and provides access to all API resource endpoints.
## Import
```typescript
import Anthropic from '@anthropic-ai/sdk';
```
CommonJS:
```typescript
const Anthropic = require('@anthropic-ai/sdk');
```
## Client Class
```typescript { .api }
class Anthropic extends BaseAnthropic {
constructor(options?: ClientOptions);
// Resource endpoints
completions: Completions;
messages: Messages;
models: Models;
beta: Beta;
// Instance methods
withOptions(options: Partial<ClientOptions>): this;
// Static constants
static HUMAN_PROMPT: string;
static AI_PROMPT: string;
static DEFAULT_TIMEOUT: number;
}
```
## ClientOptions
Complete configuration options for the Anthropic client.
```typescript { .api }
interface ClientOptions {
/**
* API key for authentication.
* Can be a static string or async function for dynamic credentials.
* Defaults to process.env['ANTHROPIC_API_KEY']
*/
apiKey?: string | ApiKeySetter | null;
/**
* Alternative authentication token.
* Defaults to process.env['ANTHROPIC_AUTH_TOKEN']
*/
authToken?: string | null;
/**
* Custom API base URL.
* Defaults to process.env['ANTHROPIC_BASE_URL'] or 'https://api.anthropic.com'
*/
baseURL?: string | null;
/**
* Request timeout in milliseconds.
* Default: 600000 (10 minutes)
*
* For non-streaming requests with large max_tokens, timeout is dynamically
* calculated: min(10 minutes, (60 * max_tokens) / 128000 hours)
*/
timeout?: number;
/**
* Maximum number of retry attempts for failed requests.
* Default: 2
*
* Retries occur for:
* - Network errors (connection failures)
* - 408 Request Timeout
* - 409 Conflict
* - 429 Rate Limit
* - 5xx Server errors
*/
maxRetries?: number;
/**
* Additional fetch options passed to all requests.
* Can include headers, signal, credentials, etc.
*/
fetchOptions?: RequestInit;
/**
* Custom fetch implementation.
* If not provided, uses global fetch.
*/
fetch?: typeof fetch;
/**
* Default headers included with every request.
* Can be overridden per-request by setting header to null.
*/
defaultHeaders?: HeadersLike;
/**
* Default query parameters included with every request.
* Can be removed per-request by setting param to undefined.
*/
defaultQuery?: Record<string, string | undefined>;
/**
* Enable client-side usage (browser environments).
* Default: false
*
* WARNING: Enabling this in browsers exposes API credentials.
* Only use with appropriate security mitigations.
*/
dangerouslyAllowBrowser?: boolean;
/**
* Logging level for SDK operations.
* Defaults to process.env['ANTHROPIC_LOG'] or 'warn'
*/
logLevel?: LogLevel;
/**
* Custom logger instance.
* Defaults to globalThis.console
*/
logger?: Logger;
}
```
### Supporting Types
```typescript { .api }
// Dynamic API key function
type ApiKeySetter = () => Promise<string>;
// Log levels (most to least verbose)
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
// Logger interface (compatible with console, pino, winston, etc.)
interface Logger {
debug(...args: any[]): void;
info(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
}
// Header types
type HeadersLike = Record<string, string | string[] | undefined> | Headers;
```
## Basic Initialization
### Default Configuration
Uses environment variables for authentication:
```typescript
const client = new Anthropic();
// Uses process.env.ANTHROPIC_API_KEY
```
### Explicit API Key
```typescript
const client = new Anthropic({
apiKey: 'your-api-key-here',
});
```
### Dynamic API Key
For rotating credentials or runtime key fetching:
```typescript
const client = new Anthropic({
apiKey: async () => {
// Fetch key from secure storage
const key = await fetchKeyFromVault();
return key;
},
});
```
The function is called before each request, allowing for:
- Credential rotation
- Token refresh
- Runtime key management
## Configuration Examples
### Custom Timeout
```typescript
const client = new Anthropic({
timeout: 30 * 1000, // 30 seconds
});
```
### Disable Retries
```typescript
const client = new Anthropic({
maxRetries: 0,
});
```
### Custom Base URL
For proxies or custom endpoints:
```typescript
const client = new Anthropic({
baseURL: 'https://proxy.example.com/anthropic',
});
```
### Custom Headers
Add headers to all requests:
```typescript
const client = new Anthropic({
defaultHeaders: {
'X-Custom-Header': 'value',
'User-Agent': 'MyApp/1.0',
},
});
```
### Browser Usage (Not Recommended)
```typescript
const client = new Anthropic({
apiKey: 'your-api-key',
dangerouslyAllowBrowser: true, // SECURITY WARNING
});
```
**WARNING**: This exposes your API key in client-side code. Only use for:
- Internal tools with trusted users
- Development/debugging with temporary keys
- Environments where key exposure is acceptable
### Logging Configuration
```typescript
// Set log level via environment
// export ANTHROPIC_LOG=debug
// Or via options
const client = new Anthropic({
logLevel: 'debug', // Shows all HTTP requests/responses
});
// Custom logger (pino example)
import pino from 'pino';
const logger = pino();
const client = new Anthropic({
logger: logger.child({ component: 'anthropic-sdk' }),
logLevel: 'debug',
});
```
Log levels:
- `debug`: All requests/responses with headers and bodies (may expose sensitive data)
- `info`: General informational messages
- `warn`: Warnings and errors (default)
- `error`: Only errors
- `off`: No logging
### Custom Fetch Implementation
For Node.js proxies or custom network handling:
```typescript
// Node.js with undici proxy
import * as undici from 'undici';
const proxyAgent = new undici.ProxyAgent('http://proxy.example.com:8080');
const client = new Anthropic({
fetchOptions: {
dispatcher: proxyAgent, // undici-specific
},
});
// Bun with built-in proxy support
const client = new Anthropic({
fetchOptions: {
proxy: 'http://proxy.example.com:8080',
},
});
// Deno with HTTP client
const httpClient = Deno.createHttpClient({
proxy: { url: 'http://proxy.example.com:8080' },
});
const client = new Anthropic({
fetchOptions: {
client: httpClient,
},
});
// Custom fetch function
const client = new Anthropic({
fetch: async (url, init) => {
// Add custom logic
console.log('Fetching:', url);
return fetch(url, init);
},
});
```
## Instance Methods
### withOptions()
Create a new client with modified options while preserving existing configuration:
```typescript { .api }
withOptions(options: Partial<ClientOptions>): Anthropic;
```
**Example:**
```typescript
const originalClient = new Anthropic({
apiKey: 'key-1',
timeout: 60000,
});
// Create new client with different timeout
const timeoutClient = originalClient.withOptions({
timeout: 120000,
});
// Create new client with different API key
const secondClient = originalClient.withOptions({
apiKey: 'key-2',
});
// Original client unchanged
console.log(originalClient.timeout); // 60000
console.log(timeoutClient.timeout); // 120000
```
Use cases:
- Multi-tenant applications with different API keys
- Different timeout requirements per use case
- Testing with different configurations
## Resource Endpoints
The client provides access to all API resources:
```typescript { .api }
client.messages // Messages API (primary conversational interface)
client.completions // Legacy completions API
client.models // Model information
client.beta // Beta features namespace
```
### Messages
```typescript
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
```
See [messages.md](./messages.md)
### Models
```typescript
const models = await client.models.list();
const model = await client.models.retrieve('claude-sonnet-4-5-20250929');
```
See [models.md](./models.md)
### Beta Features
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
betas: ['code-execution-2025-05-22'],
tools: [{ name: 'code_execution', type: 'code_execution_20250522' }],
});
```
See [beta-features.md](./beta-features.md)
## Static Constants
Legacy prompt delimiters for the old text completions API:
```typescript { .api }
Anthropic.HUMAN_PROMPT: string // '\n\nHuman:'
Anthropic.AI_PROMPT: string // '\n\nAssistant:'
Anthropic.DEFAULT_TIMEOUT: number // 600000 (10 minutes)
```
**Example:**
```typescript
import Anthropic from '@anthropic-ai/sdk';
const prompt = `${Anthropic.HUMAN_PROMPT} What is 2+2?${Anthropic.AI_PROMPT}`;
// Used with legacy completions API
const completion = await client.completions.create({
model: 'claude-2.1',
prompt,
max_tokens_to_sample: 100,
});
```
**Note**: These are for the legacy completions API. The modern Messages API uses structured message objects instead.
## Per-Request Options
Override client configuration for individual requests:
```typescript { .api }
interface RequestOptions {
timeout?: number;
maxRetries?: number;
headers?: HeadersLike;
query?: Record<string, string | undefined>;
signal?: AbortSignal;
idempotencyKey?: string;
}
```
**Example:**
```typescript
// Shorter timeout for this request
const message = await client.messages.create(
{
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Quick question' }],
},
{
timeout: 10000, // 10 seconds
maxRetries: 0,
headers: {
'X-Request-ID': 'custom-id',
},
}
);
// Manual cancellation with AbortController
const controller = new AbortController();
const promise = client.messages.create(
{ /* params */ },
{ signal: controller.signal }
);
// Cancel request after 5 seconds
setTimeout(() => controller.abort(), 5000);
```
## Environment Variables
The SDK reads configuration from environment variables:
```bash
# Authentication
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_AUTH_TOKEN=...
# Configuration
ANTHROPIC_BASE_URL=https://custom-endpoint.example.com
ANTHROPIC_LOG=debug
# Runtime-specific variables are also supported
NODE_ENV=production
```
Priority order:
1. Explicit constructor options
2. Environment variables
3. Default values
## Error Handling
The client will throw errors when:
```typescript
// No API key provided
try {
const client = new Anthropic(); // No apiKey, env var not set
} catch (error) {
// AnthropicError: Missing API Key
}
// Browser usage without explicit permission
try {
const client = new Anthropic({ apiKey: 'key' }); // In browser
} catch (error) {
// AnthropicError: Browser usage not allowed
}
// Invalid configuration
try {
const client = new Anthropic({
maxRetries: -1, // Invalid
});
} catch (error) {
// Configuration validation error
}
```
See [errors.md](./errors.md) for complete error handling documentation.
## TypeScript Support
The client is fully typed with TypeScript:
```typescript
import Anthropic from '@anthropic-ai/sdk';
// All types are exported
import type {
ClientOptions,
Message,
MessageParam,
MessageCreateParams,
} from '@anthropic-ai/sdk';
const client: Anthropic = new Anthropic();
// Type inference works automatically
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
// message is typed as Anthropic.Message
console.log(message.content[0].type); // TypeScript knows the shape
```
## Runtime Compatibility
The SDK supports multiple JavaScript runtimes:
### Node.js
```typescript
// Node.js 20+ (LTS or later)
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
```
### Deno
```typescript
// Deno v1.28.0+
import Anthropic from 'npm:@anthropic-ai/sdk';
const client = new Anthropic();
```
### Bun
```typescript
// Bun 1.0+
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
```
### Edge Runtimes
```typescript
// Cloudflare Workers
export default {
async fetch(request) {
const client = new Anthropic({
apiKey: env.ANTHROPIC_API_KEY,
});
// ...
},
};
// Vercel Edge Functions
import Anthropic from '@anthropic-ai/sdk';
export const config = { runtime: 'edge' };
export default async function handler(req) {
const client = new Anthropic();
// ...
}
```
### Browsers
```typescript
// NOT recommended - exposes API key
const client = new Anthropic({
apiKey: 'your-key', // Will be visible in browser
dangerouslyAllowBrowser: true,
});
```
**For production browser apps**: Use a backend proxy to keep credentials secure.
## Advanced Usage
### Request ID Tracking
All responses include a request ID for debugging:
```typescript
const message = await client.messages.create({ /* ... */ });
console.log(message._request_id); // 'req_018EeWyXxfu5pfWkrYcMdjWG'
// Or access via withResponse()
const { data, response, request_id } = await client.messages
.create({ /* ... */ })
.withResponse();
console.log(request_id); // Same ID
console.log(response.headers.get('request-id'));
```
### Raw Response Access
Access underlying HTTP response:
```typescript
// Get response before body is parsed
const response = await client.messages.create({ /* ... */ }).asResponse();
console.log(response.status);
console.log(response.headers.get('x-ratelimit-remaining'));
// Get both parsed data and response
const { data, response, request_id } = await client.messages
.create({ /* ... */ })
.withResponse();
console.log(data.content); // Parsed message
console.log(response.status); // 200
```
### Keep-Alive Configuration
For long-running connections, TCP keep-alive is automatically configured when supported by the fetch implementation. This reduces idle connection timeouts on some networks.
Override via custom fetch options if needed.
## Best Practices
### Credential Management
```typescript
// ✅ Good: Use environment variables
const client = new Anthropic(); // Reads from ANTHROPIC_API_KEY
// ✅ Good: Use credential manager
const client = new Anthropic({
apiKey: async () => await credentialManager.getKey(),
});
// ❌ Bad: Hardcode keys
const client = new Anthropic({
apiKey: 'sk-ant-...', // Don't commit this!
});
```
### Timeout Configuration
```typescript
// ✅ Good: Set reasonable timeouts
const client = new Anthropic({
timeout: 120000, // 2 minutes for typical requests
});
// ✅ Good: Use streaming for long operations
const stream = await client.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4000,
messages: [{ role: 'user', content: 'Long task...' }],
});
// ❌ Bad: Very short timeout without streaming
const client = new Anthropic({
timeout: 5000, // Too short for API processing
});
```
### Error Handling
```typescript
// ✅ Good: Handle specific errors
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error('API error:', error.status, error.message);
console.error('Request ID:', error.requestID);
} else if (error instanceof Anthropic.APIConnectionError) {
console.error('Network error:', error.message);
} else {
throw error;
}
}
```
### Resource Reuse
```typescript
// ✅ Good: Reuse client instance
const client = new Anthropic();
async function processMany(inputs) {
return Promise.all(
inputs.map(input =>
client.messages.create({ /* ... */ })
)
);
}
// ❌ Bad: Create new client per request
async function processBad(input) {
const client = new Anthropic(); // Wasteful
return client.messages.create({ /* ... */ });
}
```
## See Also
- [Messages API](./messages.md) - Core conversational interface
- [Streaming](./streaming.md) - Real-time response streaming
- [Error Handling](./errors.md) - Comprehensive error reference
- [Beta Features](./beta-features.md) - Experimental capabilities

View file

@ -0,0 +1,208 @@
# Text Completions API (Legacy)
The Text Completions API is a legacy API for generating text completions using Claude models. This API is maintained for backwards compatibility, but the [Messages API](./messages.md) is recommended for new implementations.
**Note:** Future models and features will not be compatible with Text Completions. See the [migration guide](https://docs.anthropic.com/en/api/migrating-from-text-completions-to-messages) for guidance on migrating from Text Completions to Messages.
## Capabilities
### Create Completion
Creates a text completion using the legacy prompt format with `\n\nHuman:` and `\n\nAssistant:` conversational turns.
```typescript { .api }
/**
* Create a Text Completion (Legacy).
*
* @param params - Completion parameters
* @param options - Optional request options
* @returns APIPromise resolving to a Completion or Stream<Completion>
*/
client.completions.create(
params: CompletionCreateParams
): APIPromise<Completion> | APIPromise<Stream<Completion>>;
```
**Usage Example:**
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Non-streaming completion
const completion = await client.completions.create({
max_tokens_to_sample: 256,
model: 'claude-sonnet-4-5-20250929',
prompt: '\n\nHuman: Hello, world!\n\nAssistant:',
});
console.log(completion.completion);
// Streaming completion
const stream = await client.completions.create({
max_tokens_to_sample: 256,
model: 'claude-sonnet-4-5-20250929',
prompt: '\n\nHuman: Tell me a story\n\nAssistant:',
stream: true,
});
for await (const chunk of stream) {
console.log(chunk.completion);
}
```
## Types
### Completion
The response object returned by the Text Completions API.
```typescript { .api }
interface Completion {
/**
* Unique object identifier.
* The format and length of IDs may change over time.
*/
id: string;
/**
* The resulting completion up to and excluding the stop sequences.
*/
completion: string;
/**
* The model that completed your prompt.
* See https://docs.anthropic.com/en/docs/models-overview for details.
*/
model: Model;
/**
* The reason that generation stopped.
*
* Possible values:
* - "stop_sequence": reached a stop sequence (provided or built-in)
* - "max_tokens": exceeded max_tokens_to_sample or model's maximum
*/
stop_reason: string | null;
/**
* Object type. For Text Completions, this is always "completion".
*/
type: 'completion';
}
```
### CompletionCreateParams
Parameters for creating a text completion.
```typescript { .api }
type CompletionCreateParams =
| CompletionCreateParamsNonStreaming
| CompletionCreateParamsStreaming;
interface CompletionCreateParamsBase {
/**
* The maximum number of tokens to generate before stopping.
*
* Note that models may stop before reaching this maximum.
* This parameter only specifies the absolute maximum number of tokens to generate.
*/
max_tokens_to_sample: number;
/**
* The model that will complete your prompt.
* See https://docs.anthropic.com/en/docs/models-overview for details.
*/
model: Model;
/**
* The prompt that you want Claude to complete.
*
* For proper response generation, format your prompt using alternating
* `\n\nHuman:` and `\n\nAssistant:` conversational turns.
*
* Example: "\n\nHuman: {userQuestion}\n\nAssistant:"
*
* See https://docs.claude.com/en/api/prompt-validation and
* https://docs.claude.com/en/docs/intro-to-prompting for more details.
*/
prompt: string;
/**
* An object describing metadata about the request.
*/
metadata?: Metadata;
/**
* Sequences that will cause the model to stop generating.
*
* Models stop on "\n\nHuman:" and may include additional built-in stop
* sequences in the future. By providing stop_sequences, you may include
* additional strings that will cause the model to stop generating.
*/
stop_sequences?: string[];
/**
* Whether to incrementally stream the response using server-sent events.
* See https://docs.claude.com/en/api/streaming for details.
*/
stream?: boolean;
/**
* Amount of randomness injected into the response.
*
* Defaults to 1.0. Ranges from 0.0 to 1.0.
* Use temperature closer to 0.0 for analytical/multiple choice tasks,
* and closer to 1.0 for creative and generative tasks.
*
* Note that even with temperature of 0.0, results will not be fully deterministic.
*/
temperature?: number;
/**
* Only sample from the top K options for each subsequent token.
*
* Used to remove "long tail" low probability responses.
* See https://towardsdatascience.com/how-to-sample-from-language-models-682bceb97277
*
* Recommended for advanced use cases only. You usually only need to use temperature.
*/
top_k?: number;
/**
* Use nucleus sampling.
*
* In nucleus sampling, we compute the cumulative distribution over all options
* for each subsequent token in decreasing probability order and cut it off once
* it reaches a particular probability specified by top_p.
*
* You should either alter temperature or top_p, but not both.
*
* Recommended for advanced use cases only. You usually only need to use temperature.
*/
top_p?: number;
/**
* Optional header to specify the beta version(s) you want to use.
*/
betas?: AnthropicBeta[];
}
interface CompletionCreateParamsNonStreaming extends CompletionCreateParamsBase {
/**
* Whether to incrementally stream the response using server-sent events.
*/
stream?: false;
}
interface CompletionCreateParamsStreaming extends CompletionCreateParamsBase {
/**
* Whether to incrementally stream the response using server-sent events.
*/
stream: true;
}
```

View file

@ -0,0 +1,697 @@
# Error Handling
The Anthropic SDK provides comprehensive error classes for different failure scenarios. All errors extend from the base `AnthropicError` class and provide detailed information for debugging and recovery.
## Error Hierarchy
```typescript { .api }
AnthropicError
├── APIError<TStatus, THeaders, TError>
│ ├── BadRequestError (400)
│ ├── AuthenticationError (401)
│ ├── PermissionDeniedError (403)
│ ├── NotFoundError (404)
│ ├── ConflictError (409)
│ ├── UnprocessableEntityError (422)
│ ├── RateLimitError (429)
│ └── InternalServerError (5xx)
├── APIConnectionError
│ └── APIConnectionTimeoutError
└── APIUserAbortError
```
## Base Error Class
```typescript { .api }
class AnthropicError extends Error {
readonly name: string;
readonly message: string;
readonly cause?: Error;
}
```
All SDK errors inherit from `AnthropicError`.
## API Errors
```typescript { .api }
class APIError<TStatus = number, THeaders = Headers, TError = any> extends AnthropicError {
readonly status: TStatus; // HTTP status code
readonly headers: THeaders; // Response headers
readonly error: TError; // Error details from API
readonly requestID: string | null; // Request ID for debugging
}
```
**Example:**
```typescript
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error('Status:', error.status);
console.error('Message:', error.message);
console.error('Request ID:', error.requestID);
console.error('Error details:', error.error);
}
}
```
## HTTP Status Errors
### BadRequestError (400)
Invalid request parameters or malformed request.
```typescript { .api }
class BadRequestError extends APIError<400> {}
```
**Common Causes**:
- Invalid parameter values
- Missing required fields
- Malformed JSON
- Invalid model name
**Example:**
```typescript
try {
const message = await client.messages.create({
model: 'invalid-model',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
} catch (error) {
if (error instanceof Anthropic.BadRequestError) {
console.error('Invalid request:', error.message);
// "model: invalid-model is not a valid model"
}
}
```
### AuthenticationError (401)
Authentication failed - invalid or missing API key.
```typescript { .api }
class AuthenticationError extends APIError<401> {}
```
**Common Causes**:
- Missing API key
- Invalid API key
- Expired API key
**Example:**
```typescript
try {
const client = new Anthropic({ apiKey: 'invalid-key' });
await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.AuthenticationError) {
console.error('Authentication failed');
// Check API key configuration
}
}
```
### PermissionDeniedError (403)
Insufficient permissions for the requested operation.
```typescript { .api }
class PermissionDeniedError extends APIError<403> {}
```
**Common Causes**:
- Account doesn't have access to requested feature
- API key lacks required permissions
**Example:**
```typescript
try {
await client.beta.messages.create({
betas: ['restricted-feature'],
/* ... */
});
} catch (error) {
if (error instanceof Anthropic.PermissionDeniedError) {
console.error('Access denied to beta feature');
}
}
```
### NotFoundError (404)
Requested resource not found.
```typescript { .api }
class NotFoundError extends APIError<404> {}
```
**Common Causes**:
- Invalid resource ID
- Resource was deleted
- Typo in endpoint or ID
**Example:**
```typescript
try {
const batch = await client.messages.batches.retrieve('nonexistent-id');
} catch (error) {
if (error instanceof Anthropic.NotFoundError) {
console.error('Batch not found');
}
}
```
### ConflictError (409)
Request conflicts with current state.
```typescript { .api }
class ConflictError extends APIError<409> {}
```
**Common Causes**:
- Duplicate resource creation
- Conflicting operations
**Example:**
```typescript
try {
await client.beta.skills.create({
name: 'existing-skill', // Already exists
/* ... */
});
} catch (error) {
if (error instanceof Anthropic.ConflictError) {
console.error('Skill already exists');
}
}
```
### UnprocessableEntityError (422)
Request is syntactically correct but semantically invalid.
```typescript { .api }
class UnprocessableEntityError extends APIError<422> {}
```
**Common Causes**:
- Invalid parameter combination
- Business logic validation failure
**Example:**
```typescript
try {
await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [
{ role: 'assistant', content: 'Cannot start with assistant' },
],
});
} catch (error) {
if (error instanceof Anthropic.UnprocessableEntityError) {
console.error('Invalid message structure');
}
}
```
### RateLimitError (429)
Rate limit exceeded.
```typescript { .api }
class RateLimitError extends APIError<429> {}
```
**Common Causes**:
- Too many requests in time window
- Exceeded tokens per minute
- Exceeded requests per day
**Example:**
```typescript
try {
await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
console.error('Rate limited');
// Get retry information from headers
const retryAfter = error.headers.get('retry-after');
const resetTime = error.headers.get('x-ratelimit-reset');
console.log(`Retry after: ${retryAfter} seconds`);
console.log(`Resets at: ${new Date(parseInt(resetTime) * 1000)}`);
// Implement backoff
await new Promise(resolve => setTimeout(resolve, parseInt(retryAfter) * 1000));
}
}
```
### InternalServerError (5xx)
Server-side error.
```typescript { .api }
class InternalServerError extends APIError<500 | 502 | 503 | 504> {}
```
**Common Causes**:
- Temporary server issues
- Service overload
- Gateway errors
**Example:**
```typescript
try {
await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.InternalServerError) {
console.error('Server error:', error.status);
// Retry with exponential backoff
await retryWithBackoff(() => client.messages.create({ /* ... */ }));
}
}
```
## Network Errors
### APIConnectionError
Failed to connect to the API.
```typescript { .api }
class APIConnectionError extends AnthropicError {
readonly cause?: Error;
}
```
**Common Causes**:
- Network connectivity issues
- DNS resolution failure
- Firewall blocking requests
**Example:**
```typescript
try {
await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.APIConnectionError) {
console.error('Network error:', error.message);
console.error('Cause:', error.cause);
// Check network connectivity
// Retry with exponential backoff
}
}
```
### APIConnectionTimeoutError
Request timed out.
```typescript { .api }
class APIConnectionTimeoutError extends APIConnectionError {}
```
**Common Causes**:
- Request exceeded timeout
- Slow network connection
- Large request/response
**Example:**
```typescript
try {
await client.messages.create(
{
model: 'claude-sonnet-4-5-20250929',
max_tokens: 10000,
messages: [{ role: 'user', content: 'Long task' }],
},
{ timeout: 30000 } // 30 seconds
);
} catch (error) {
if (error instanceof Anthropic.APIConnectionTimeoutError) {
console.error('Request timed out');
// Use streaming for long requests
const stream = client.messages.stream({ /* ... */ });
}
}
```
### APIUserAbortError
User aborted the request.
```typescript { .api }
class APIUserAbortError extends AnthropicError {}
```
**Common Causes**:
- User called `stream.abort()`
- AbortController signal triggered
- Stream break
**Example:**
```typescript
const stream = client.messages.stream({ /* ... */ });
// User decides to cancel
setTimeout(() => stream.abort(), 5000);
try {
await stream.done();
} catch (error) {
if (error instanceof Anthropic.APIUserAbortError) {
console.log('User canceled the request');
}
}
```
## Error Handling Patterns
### Basic Try-Catch
```typescript
try {
const message = await client.messages.create({ /* ... */ });
console.log(message.content);
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error(`API Error ${error.status}:`, error.message);
} else if (error instanceof Anthropic.APIConnectionError) {
console.error('Connection error:', error.message);
} else {
console.error('Unexpected error:', error);
throw error;
}
}
```
### Comprehensive Error Handling
```typescript
async function createMessage(params: Anthropic.MessageCreateParams) {
try {
return await client.messages.create(params);
} catch (error) {
// HTTP errors
if (error instanceof Anthropic.BadRequestError) {
console.error('Invalid request parameters');
throw new Error('Please check your request parameters');
}
if (error instanceof Anthropic.AuthenticationError) {
console.error('Authentication failed');
throw new Error('Please check your API key');
}
if (error instanceof Anthropic.RateLimitError) {
const retryAfter = error.headers.get('retry-after');
console.error(`Rate limited. Retry after ${retryAfter}s`);
await sleep(parseInt(retryAfter) * 1000);
return createMessage(params); // Retry
}
if (error instanceof Anthropic.InternalServerError) {
console.error('Server error, retrying...');
await sleep(5000);
return createMessage(params); // Retry
}
// Network errors
if (error instanceof Anthropic.APIConnectionTimeoutError) {
console.error('Request timed out');
throw new Error('Request took too long. Try using streaming.');
}
if (error instanceof Anthropic.APIConnectionError) {
console.error('Network error');
throw new Error('Cannot reach Anthropic API. Check your connection.');
}
// User abort
if (error instanceof Anthropic.APIUserAbortError) {
console.log('Request was canceled');
return null;
}
// Unknown error
console.error('Unexpected error:', error);
throw error;
}
}
```
### Retry with Exponential Backoff
```typescript
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
initialDelay = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;
// Don't retry on client errors (4xx except 429)
if (
error instanceof Anthropic.BadRequestError ||
error instanceof Anthropic.AuthenticationError ||
error instanceof Anthropic.PermissionDeniedError ||
error instanceof Anthropic.NotFoundError
) {
throw error;
}
// Retry on server errors, network errors, and rate limits
if (
error instanceof Anthropic.RateLimitError ||
error instanceof Anthropic.InternalServerError ||
error instanceof Anthropic.APIConnectionError
) {
const delay = initialDelay * Math.pow(2, attempt);
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// Unknown error
throw error;
}
}
throw lastError;
}
// Usage
const message = await retryWithBackoff(() =>
client.messages.create({ /* ... */ })
);
```
### Request ID Logging
```typescript
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error('Error details:', {
status: error.status,
message: error.message,
requestID: error.requestID, // Include in support requests
timestamp: new Date().toISOString(),
});
// Log for debugging
logger.error('Anthropic API error', {
requestId: error.requestID,
status: error.status,
error: error.error,
});
}
}
```
### Graceful Degradation
```typescript
async function getResponse(prompt: string): Promise<string> {
try {
// Try with Opus first
const message = await client.messages.create({
model: 'claude-opus-4-5-20250514',
max_tokens: 2048,
messages: [{ role: 'user', content: prompt }],
});
return message.content[0].text;
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
console.warn('Opus rate limited, falling back to Sonnet');
// Fall back to Sonnet
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [{ role: 'user', content: prompt }],
});
return message.content[0].text;
}
throw error;
}
}
```
## Accessing Error Details
### Error Object Structure
```typescript
catch (error) {
if (error instanceof Anthropic.APIError) {
// Status code
console.log(error.status); // 400, 401, 429, etc.
// Error message
console.log(error.message); // Human-readable message
// Request ID
console.log(error.requestID); // 'req_...'
// Response headers
console.log(error.headers.get('x-ratelimit-remaining'));
// Detailed error from API
console.log(error.error); // API error object
// Stack trace
console.log(error.stack);
// Original cause (if wrapped)
console.log(error.cause);
}
}
```
### API Error Object
```typescript
if (error instanceof Anthropic.APIError) {
const apiError = error.error;
console.log('Type:', apiError.type);
console.log('Message:', apiError.message);
// Error-specific fields
if (apiError.type === 'invalid_request_error') {
console.log('Invalid field:', apiError.field);
console.log('Invalid value:', apiError.value);
}
}
```
## Best Practices
### Specific Error Handling
```typescript
// ✅ Good: Handle specific errors
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
// Handle rate limiting
} else if (error instanceof Anthropic.AuthenticationError) {
// Handle auth issues
} else if (error instanceof Anthropic.APIError) {
// Handle other API errors
} else {
// Handle unexpected errors
}
}
// ❌ Bad: Generic catch-all
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
console.error('Error:', error); // Not helpful
}
```
### Error Logging
```typescript
// ✅ Good: Log useful context
try {
const message = await client.messages.create(params);
} catch (error) {
if (error instanceof Anthropic.APIError) {
logger.error('Anthropic API request failed', {
requestId: error.requestID,
status: error.status,
message: error.message,
params: { model: params.model, max_tokens: params.max_tokens },
userId: currentUserId,
});
}
throw error;
}
```
### User-Friendly Messages
```typescript
// ✅ Good: User-friendly error messages
try {
const message = await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
return {
error: 'Too many requests. Please try again in a few moments.',
retryAfter: error.headers.get('retry-after'),
};
} else if (error instanceof Anthropic.APIConnectionError) {
return {
error: 'Unable to connect to service. Please check your internet connection.',
};
} else {
return {
error: 'An unexpected error occurred. Please try again later.',
};
}
}
```
## See Also
- [Client Configuration](./client.md) - Timeout and retry settings
- [Messages API](./messages.md) - Message creation errors
- [Streaming](./streaming.md) - Stream error handling
- [Types](./types.md) - Error type definitions

View file

@ -0,0 +1,491 @@
# Files API (Beta)
The Files API allows you to upload, manage, and download files for use with Claude. Files can be referenced in messages for document analysis, image processing, and other file-based operations.
## Overview
The Files API enables:
- Upload files for use in conversations
- Manage uploaded files (list, retrieve metadata, delete)
- Download files when needed
- Reference files across multiple messages
- Support for images and documents (PDFs)
**Beta Feature**: Requires `betas: ['files-api-2025-04-14']`
## API Reference
```typescript { .api }
class Files extends APIResource {
upload(params: FileUploadParams): APIPromise<FileMetadata>;
list(params?: FileListParams): FileMetadataPage;
retrieveMetadata(fileID: string, params?: FileRetrieveMetadataParams): APIPromise<FileMetadata>;
download(fileID: string, params?: FileDownloadParams): APIPromise<Response>;
delete(fileID: string, params?: FileDeleteParams): APIPromise<DeletedFile>;
}
```
Access via:
```typescript
client.beta.files.*
```
## File Types
```typescript { .api }
interface FileMetadata {
id: string;
type: 'file';
filename: string;
size: number; // Bytes
created_at: string; // ISO 8601
}
interface DeletedFile {
id: string;
type: 'file';
deleted: boolean;
}
```
## Uploading Files
```typescript { .api }
client.beta.files.upload(
params: FileUploadParams
): APIPromise<FileMetadata>;
interface FileUploadParams {
file: File | Blob | ReadStream;
betas: ['files-api-2025-04-14'];
}
```
**Example - Node.js:**
```typescript
import Anthropic, { toFile } from '@anthropic-ai/sdk';
import fs from 'fs';
const client = new Anthropic();
// Upload from file system
const file = await client.beta.files.upload({
file: await toFile(
fs.createReadStream('/path/to/document.pdf'),
'document.pdf',
{ type: 'application/pdf' }
),
betas: ['files-api-2025-04-14'],
});
console.log('File ID:', file.id);
console.log('Filename:', file.filename);
console.log('Size:', file.size, 'bytes');
```
**Example - Browser:**
```typescript
// Upload from File input
const fileInput = document.querySelector('input[type="file"]');
const uploadedFile = fileInput.files[0];
const file = await client.beta.files.upload({
file: uploadedFile,
betas: ['files-api-2025-04-14'],
});
```
**Example - Buffer:**
```typescript
import { toFile } from '@anthropic-ai/sdk';
const buffer = Buffer.from('PDF content here...');
const file = await client.beta.files.upload({
file: await toFile(buffer, 'document.pdf', { type: 'application/pdf' }),
betas: ['files-api-2025-04-14'],
});
```
**Example - Fetch Response:**
```typescript
const response = await fetch('https://example.com/document.pdf');
const file = await client.beta.files.upload({
file: response,
betas: ['files-api-2025-04-14'],
});
```
## Using Uploaded Files
Reference uploaded files in messages:
```typescript
// Upload file first
const file = await client.beta.files.upload({
file: await toFile(fs.createReadStream('report.pdf'), 'report.pdf', { type: 'application/pdf' }),
betas: ['files-api-2025-04-14'],
});
// Use file in message
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['files-api-2025-04-14'],
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'file',
file_id: file.id, // Reference uploaded file
},
},
{
type: 'text',
text: 'Summarize this document.',
},
],
},
],
});
```
For images:
```typescript
const image = await client.beta.files.upload({
file: await toFile(fs.createReadStream('photo.jpg'), 'photo.jpg', { type: 'image/jpeg' }),
betas: ['files-api-2025-04-14'],
});
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['files-api-2025-04-14'],
messages: [
{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'file',
file_id: image.id,
},
},
{
type: 'text',
text: 'What is in this image?',
},
],
},
],
});
```
## Listing Files
```typescript { .api }
client.beta.files.list(
params?: FileListParams
): FileMetadataPage;
interface FileListParams {
before_id?: string;
after_id?: string;
limit?: number; // Default: 20
betas: ['files-api-2025-04-14'];
}
```
**Example:**
```typescript
const files = await client.beta.files.list({
limit: 50,
betas: ['files-api-2025-04-14'],
});
for (const file of files.data) {
console.log(`${file.filename} - ${file.size} bytes - ${file.created_at}`);
}
// Auto-pagination
for await (const file of client.beta.files.list({ betas: ['files-api-2025-04-14'] })) {
console.log(file.id);
}
```
## Retrieving File Metadata
```typescript { .api }
client.beta.files.retrieveMetadata(
fileID: string,
params?: FileRetrieveMetadataParams
): APIPromise<FileMetadata>;
interface FileRetrieveMetadataParams {
betas: ['files-api-2025-04-14'];
}
```
**Example:**
```typescript
const metadata = await client.beta.files.retrieveMetadata('file_abc123', {
betas: ['files-api-2025-04-14'],
});
console.log('Filename:', metadata.filename);
console.log('Size:', metadata.size);
console.log('Created:', metadata.created_at);
```
## Downloading Files
```typescript { .api }
client.beta.files.download(
fileID: string,
params?: FileDownloadParams
): APIPromise<Response>;
interface FileDownloadParams {
betas: ['files-api-2025-04-14'];
}
```
**Example:**
```typescript
import fs from 'fs';
import { pipeline } from 'stream/promises';
const response = await client.beta.files.download('file_abc123', {
betas: ['files-api-2025-04-14'],
});
// Save to file system (Node.js)
await pipeline(
response.body,
fs.createWriteStream('/path/to/downloaded-file.pdf')
);
// Or get as buffer
const buffer = Buffer.from(await response.arrayBuffer());
// Browser: Trigger download
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'file.pdf';
a.click();
```
## Deleting Files
```typescript { .api }
client.beta.files.delete(
fileID: string,
params?: FileDeleteParams
): APIPromise<DeletedFile>;
interface FileDeleteParams {
betas: ['files-api-2025-04-14'];
}
```
**Example:**
```typescript
const deleted = await client.beta.files.delete('file_abc123', {
betas: ['files-api-2025-04-14'],
});
console.log('Deleted:', deleted.deleted); // true
```
## Complete Workflow
```typescript
import Anthropic, { toFile } from '@anthropic-ai/sdk';
import fs from 'fs';
const client = new Anthropic();
// 1. Upload file
console.log('Uploading file...');
const file = await client.beta.files.upload({
file: await toFile(
fs.createReadStream('document.pdf'),
'document.pdf',
{ type: 'application/pdf' }
),
betas: ['files-api-2025-04-14'],
});
console.log('Uploaded:', file.id);
// 2. Use in message
console.log('Creating message...');
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['files-api-2025-04-14'],
messages: [
{
role: 'user',
content: [
{
type: 'document',
source: {
type: 'file',
file_id: file.id,
},
},
{
type: 'text',
text: 'Extract the key points from this document.',
},
],
},
],
});
console.log('Response:', message.content[0].text);
// 3. Cleanup
console.log('Cleaning up...');
await client.beta.files.delete(file.id, {
betas: ['files-api-2025-04-14'],
});
console.log('Done!');
```
## File Reuse
Files can be referenced multiple times:
```typescript
const file = await client.beta.files.upload({ /* ... */ });
// Use in multiple messages
const message1 = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['files-api-2025-04-14'],
messages: [{
role: 'user',
content: [
{ type: 'document', source: { type: 'file', file_id: file.id } },
{ type: 'text', text: 'Summarize this.' },
],
}],
});
const message2 = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['files-api-2025-04-14'],
messages: [{
role: 'user',
content: [
{ type: 'document', source: { type: 'file', file_id: file.id } },
{ type: 'text', text: 'What are the main topics?' },
],
}],
});
// Cleanup when done
await client.beta.files.delete(file.id, { betas: ['files-api-2025-04-14'] });
```
## Supported File Types
### Documents
- PDF: `application/pdf`
- Text: `text/plain`
### Images
- JPEG: `image/jpeg`
- PNG: `image/png`
- GIF: `image/gif`
- WebP: `image/webp`
## Limitations
- Maximum file size varies by type
- Files are stored temporarily
- Access requires beta header
- Automatic cleanup after expiration
## Best Practices
### File Management
```typescript
// ✅ Good: Clean up after use
const file = await client.beta.files.upload({ /* ... */ });
try {
const message = await client.beta.messages.create({ /* ... */ });
// Use message
} finally {
await client.beta.files.delete(file.id, { betas: ['files-api-2025-04-14'] });
}
// ❌ Bad: Leaving files around
const file = await client.beta.files.upload({ /* ... */ });
const message = await client.beta.messages.create({ /* ... */ });
// File never deleted
```
### Error Handling
```typescript
try {
const file = await client.beta.files.upload({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.BadRequestError) {
console.error('Invalid file:', error.message);
} else if (error instanceof Anthropic.RateLimitError) {
console.error('Rate limited, retry later');
}
throw error;
}
```
### File Validation
```typescript
// ✅ Good: Validate before upload
async function uploadFile(filePath: string) {
const stats = await fs.promises.stat(filePath);
if (stats.size > 10 * 1024 * 1024) { // 10MB
throw new Error('File too large');
}
const ext = path.extname(filePath);
if (!['.pdf', '.jpg', '.png'].includes(ext)) {
throw new Error('Unsupported file type');
}
return client.beta.files.upload({ /* ... */ });
}
```
## See Also
- [Messages API](./messages.md) - Using files in messages
- [Beta Features](./beta-features.md) - Other beta capabilities
- [Types](./types.md) - File type definitions

View file

@ -0,0 +1,434 @@
# Anthropic TypeScript SDK
The official TypeScript library for the Anthropic API, providing comprehensive access to Claude AI models. This SDK offers strongly-typed interfaces, streaming support, tool execution helpers, batch processing, and extensive beta features including code execution, computer use, and memory management.
## Package Information
- **Package Name**: @anthropic-ai/sdk
- **Package Type**: npm
- **Language**: TypeScript
- **Installation**: `npm install @anthropic-ai/sdk`
## Core Imports
```typescript
import Anthropic from '@anthropic-ai/sdk';
```
CommonJS:
```javascript
const Anthropic = require('@anthropic-ai/sdk');
```
## Basic Usage
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY, // This is the default
});
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello, Claude' }],
});
console.log(message.content);
```
## Architecture
The SDK is organized around several key components:
- **Client**: Main `Anthropic` class that initializes API access and provides resource endpoints
- **Resources**: API endpoint groups (`messages`, `models`, `beta`)
- **Streaming**: Enhanced streaming with `MessageStream` helper providing event-based API and message accumulation
- **Tools**: Helper functions for defining and automatically executing tools using Zod or JSON Schema
- **Pagination**: Automatic pagination support for list endpoints with async iteration
- **Error Handling**: Comprehensive error class hierarchy for different HTTP status codes
## Capabilities
### Client Initialization
Create and configure the Anthropic API client.
```typescript { .api }
class Anthropic {
constructor(options?: ClientOptions);
}
interface ClientOptions {
apiKey?: string | ((req: Request) => string) | null;
authToken?: string | null;
baseURL?: string | null;
timeout?: number; // default: 600000 (10 minutes)
maxRetries?: number; // default: 2
fetchOptions?: RequestInit;
fetch?: typeof fetch;
defaultHeaders?: Record<string, string>;
defaultQuery?: Record<string, string>;
dangerouslyAllowBrowser?: boolean;
logLevel?: 'debug' | 'info' | 'warn' | 'error' | 'off';
logger?: Logger;
}
```
[Client Configuration](./client.md)
### Text Completions API (Legacy)
Legacy text completion interface using the `\n\nHuman:` and `\n\nAssistant:` prompt format. This API is maintained for backwards compatibility, but the Messages API is recommended for new implementations.
```typescript { .api }
client.completions.create(
params: CompletionCreateParams
): APIPromise<Completion> | APIPromise<Stream<Completion>>;
interface Completion {
id: string;
completion: string;
model: Model;
stop_reason: string | null;
type: 'completion';
}
```
[Text Completions API](./completions.md)
### Messages API
Core conversational interface for interacting with Claude models. Supports both streaming and non-streaming requests, tool use, prompt caching, and extended thinking.
```typescript { .api }
client.messages.create(
params: MessageCreateParams
): APIPromise<Message> | APIPromise<Stream<MessageStreamEvent>>;
interface MessageCreateParams {
model: string; // e.g., 'claude-sonnet-4-5-20250929'
max_tokens: number;
messages: MessageParam[];
stream?: boolean;
system?: string | SystemBlockParam[];
temperature?: number;
top_k?: number;
top_p?: number;
stop_sequences?: string[];
metadata?: Metadata;
tools?: Tool[];
tool_choice?: ToolChoice;
}
interface MessageParam {
role: 'user' | 'assistant';
content: string | ContentBlockParam[];
}
interface Message {
id: string;
type: 'message';
role: 'assistant';
content: ContentBlock[];
model: string;
stop_reason: 'end_turn' | 'max_tokens' | 'stop_sequence' | 'tool_use' | 'server_tool_use' | null;
stop_sequence: string | null;
usage: Usage;
}
```
[Messages API](./messages.md)
### Streaming
Enhanced streaming capabilities with event-based API, automatic message accumulation, and helper methods.
```typescript { .api }
client.messages.stream(params: MessageStreamParams): MessageStream;
class MessageStream {
on(event: string, listener: Function): this;
once(event: string, listener: Function): this;
off(event: string, listener: Function): this;
emitted(event: string): Promise<any>;
done(): Promise<void>;
finalMessage(): Promise<Message>;
finalText(): Promise<string>;
abort(): void;
toReadableStream(): ReadableStream;
}
```
Events: `connect`, `streamEvent`, `text`, `inputJson`, `citation`, `thinking`, `contentBlock`, `message`, `finalMessage`, `error`, `abort`, `end`
[Streaming](./streaming.md)
### Tool Use
Define and execute tools (function calling) with automatic validation and execution loop.
```typescript { .api }
// Tool definition
interface Tool {
name: string;
description: string;
input_schema: {
type: 'object';
properties: Record<string, any>;
required?: string[];
};
}
// Tool helpers with Zod
import { betaZodTool } from '@anthropic-ai/sdk/helpers/zod';
function betaZodTool<Schema extends ZodType>(options: {
name: string;
inputSchema: Schema;
description: string;
run: (input: z.infer<Schema>) => Promise<string | BetaToolResultContentBlockParam[]> | string | BetaToolResultContentBlockParam[];
}): BetaRunnableTool<z.infer<Schema>>;
// Automatic tool execution
client.beta.messages.toolRunner(
params: BetaToolRunnerParams
): BetaToolRunner;
```
[Tool Use and Tool Helpers](./tools.md)
### Message Batches
Batch processing for multiple messages with asynchronous result retrieval.
```typescript { .api }
client.messages.batches.create(
params: MessageBatchCreateParams
): APIPromise<MessageBatch>;
client.messages.batches.list(
params?: MessageBatchListParams
): MessageBatchesPage;
client.messages.batches.results(
messageBatchID: string
): AsyncIterable<MessageBatchIndividualResponse>;
interface MessageBatch {
id: string;
type: 'message_batch';
processing_status: 'in_progress' | 'canceling' | 'ended';
request_counts: MessageBatchRequestCounts;
ended_at: string | null;
created_at: string;
expires_at: string;
archived_at: string | null;
cancel_initiated_at: string | null;
results_url: string | null;
}
```
[Message Batches](./batches.md)
### Models API
Retrieve information about available Claude models.
```typescript { .api }
client.models.retrieve(
modelID: string,
params?: ModelRetrieveParams
): APIPromise<ModelInfo>;
client.models.list(
params?: ModelListParams
): ModelInfosPage;
interface ModelInfo {
type: 'model';
id: string;
display_name: string;
created_at: string;
}
```
[Models API](./models.md)
### Files API (Beta)
Upload, manage, and download files for use with file-based tools and document processing.
```typescript { .api }
client.beta.files.upload(
params: FileUploadParams
): APIPromise<FileMetadata>;
client.beta.files.list(
params?: FileListParams
): FileMetadataPage;
client.beta.files.download(
fileID: string,
params?: FileDownloadParams
): APIPromise<Response>;
interface FileMetadata {
id: string;
type: 'file';
filename: string;
size: number;
created_at: string;
}
```
[Files API](./files.md)
### Skills API (Beta)
Create and manage custom skills for extended capabilities.
```typescript { .api }
client.beta.skills.create(
params: SkillCreateParams
): APIPromise<SkillCreateResponse>;
client.beta.skills.list(
params?: SkillListParams
): SkillListResponsesPageCursor;
client.beta.skills.retrieve(
skillID: string,
params?: SkillRetrieveParams
): APIPromise<SkillRetrieveResponse>;
```
[Skills API](./skills.md)
### Beta Features
Access experimental and advanced features through the beta namespace.
Beta features include:
- **Code Execution**: Execute Python code in sandboxed environments
- **Computer Use**: Control computer interactions (bash, text editor, computer use tools)
- **Extended Thinking**: Access full thinking traces from Claude
- **MCP (Model Context Protocol)**: External tool integration
- **Memory Tools**: Persistent memory across conversations
- **Web Search & Fetch**: Search the web and fetch content
- **Context Management**: Advanced context window management
- **Structured Outputs**: JSON output with automatic parsing
```typescript { .api }
// Access beta features
client.beta.messages.create(params: {
...MessageCreateParams,
betas: AnthropicBeta[];
});
type AnthropicBeta =
| 'message-batches-2024-09-24'
| 'prompt-caching-2024-07-31'
| 'computer-use-2025-01-24'
| 'code-execution-2025-05-22'
| 'files-api-2025-04-14'
| 'mcp-client-2025-04-04'
| 'skills-2025-10-02'
| 'context-management-2025-06-27'
// ... and more
;
```
[Beta Features](./beta-features.md)
### Error Handling
Comprehensive error classes for different failure scenarios.
```typescript { .api }
class AnthropicError extends Error {}
class APIError extends AnthropicError {
readonly status: number;
readonly headers: Headers;
readonly error: any;
readonly requestID: string | null;
}
class BadRequestError extends APIError {} // 400
class AuthenticationError extends APIError {} // 401
class PermissionDeniedError extends APIError {} // 403
class NotFoundError extends APIError {} // 404
class UnprocessableEntityError extends APIError {} // 422
class RateLimitError extends APIError {} // 429
class InternalServerError extends APIError {} // 5xx
class APIConnectionError extends AnthropicError {}
class APIConnectionTimeoutError extends APIConnectionError {}
class APIUserAbortError extends AnthropicError {}
```
[Error Handling](./errors.md)
### Pagination
Automatic pagination support for list endpoints with async iteration.
```typescript { .api }
// Auto-pagination with async iteration
for await (const item of client.messages.batches.list({ limit: 20 })) {
console.log(item);
}
// Manual pagination
let page = await client.messages.batches.list({ limit: 20 });
while (page.hasNextPage()) {
page = await page.getNextPage();
// process page.data
}
// Page interface
interface Page<Item> {
data: Item[];
has_more: boolean;
first_id: string | null;
last_id: string | null;
hasNextPage(): boolean;
getNextPage(): Promise<this>;
}
```
### File Uploads
Convert various input types to uploadable files.
```typescript { .api }
import { toFile } from '@anthropic-ai/sdk';
function toFile(
value: File | Blob | Response | ReadStream | Buffer | Uint8Array | ArrayBuffer,
name?: string,
options?: { type?: string }
): Promise<FileLike>;
```
### Request Metadata
Access response headers and request IDs for debugging.
```typescript { .api }
// Get response metadata
const { data, response, request_id } = await client.messages
.create(params)
.withResponse();
console.log(request_id); // For support requests
// Get raw response
const response = await client.messages.create(params).asResponse();
```
## Types Reference
For complete type definitions including all content blocks, tool types, streaming events, and beta-specific types, see:
[Types Reference](./types.md)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,351 @@
# Models API
The Models API provides information about available Claude models, including their capabilities, context windows, and creation dates.
## API Reference
```typescript { .api }
class Models extends APIResource {
retrieve(modelID: string, params?: ModelRetrieveParams): APIPromise<ModelInfo>;
list(params?: ModelListParams): ModelInfosPage;
}
```
Access via:
```typescript
client.models.* // Standard API
client.beta.models.* // Beta API
```
## Model Information
```typescript { .api }
interface ModelInfo {
type: 'model';
id: string;
display_name: string;
created_at: string;
}
```
## Retrieving Model Info
```typescript { .api }
client.models.retrieve(
modelID: string,
params?: ModelRetrieveParams
): APIPromise<ModelInfo>;
interface ModelRetrieveParams {
betas?: AnthropicBeta[];
}
```
**Example:**
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const model = await client.models.retrieve('claude-sonnet-4-5-20250929');
console.log('Model ID:', model.id);
console.log('Display name:', model.display_name);
console.log('Created at:', model.created_at);
```
## Listing Models
```typescript { .api }
client.models.list(
params?: ModelListParams
): ModelInfosPage;
interface ModelListParams {
before_id?: string;
after_id?: string;
limit?: number; // Default: 20, max: 1000
betas?: AnthropicBeta[];
}
```
**Example:**
```typescript
// List all available models
const models = await client.models.list();
for (const model of models.data) {
console.log(`${model.display_name} (${model.id})`);
}
// Auto-pagination
for await (const model of client.models.list({ limit: 100 })) {
console.log(model.id);
}
```
## Available Models
### Claude Opus 4.5
```typescript
model: 'claude-opus-4-5-20250514'
```
- **Capabilities**: Most capable model, excels at complex reasoning and analysis
- **Context Window**: 200K tokens
- **Use Cases**: Research, complex problem solving, detailed analysis
### Claude Sonnet 4.5
```typescript
model: 'claude-sonnet-4-5-20250929'
```
- **Capabilities**: Balance of intelligence and speed
- **Context Window**: 200K tokens
- **Use Cases**: Most applications, coding, writing, analysis
### Claude 3.5 Sonnet
```typescript
model: 'claude-3-5-sonnet-20241022'
```
- **Capabilities**: Previous generation, still highly capable
- **Context Window**: 200K tokens
- **Use Cases**: General purpose applications
### Claude 3.5 Haiku
```typescript
model: 'claude-3-5-haiku-20241022'
```
- **Capabilities**: Fast and efficient for simpler tasks
- **Context Window**: 200K tokens
- **Use Cases**: Quick responses, simple queries, high-throughput
### Claude 3 Family
```typescript
model: 'claude-3-opus-20240229' // Most capable (previous generation)
model: 'claude-3-sonnet-20240229' // Balanced (previous generation)
model: 'claude-3-haiku-20240307' // Fast (previous generation)
```
## Model Selection Guide
Choose based on your needs:
### Opus 4.5 - Maximum Capability
```typescript
const message = await client.messages.create({
model: 'claude-opus-4-5-20250514',
max_tokens: 4096,
messages: [{
role: 'user',
content: 'Provide a detailed analysis of quantum computing trends...',
}],
});
```
Use for:
- Complex reasoning and analysis
- Research and academic work
- Detailed creative writing
- Advanced coding tasks
### Sonnet 4.5 - Best All-Around
```typescript
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
messages: [{
role: 'user',
content: 'Help me debug this code...',
}],
});
```
Use for:
- Most applications
- Coding and development
- Content generation
- Data analysis
- Customer support
### Haiku 3.5 - Speed and Efficiency
```typescript
const message = await client.messages.create({
model: 'claude-3-5-haiku-20241022',
max_tokens: 1024,
messages: [{
role: 'user',
content: 'What is 2+2?',
}],
});
```
Use for:
- Simple queries
- High-throughput scenarios
- Quick responses
- Classification tasks
- Simple data extraction
## Dynamic Model Selection
```typescript
function selectModel(complexity: 'simple' | 'moderate' | 'complex'): string {
switch (complexity) {
case 'simple':
return 'claude-3-5-haiku-20241022';
case 'moderate':
return 'claude-sonnet-4-5-20250929';
case 'complex':
return 'claude-opus-4-5-20250514';
}
}
const model = selectModel('moderate');
const message = await client.messages.create({
model,
max_tokens: 1024,
messages: [{ role: 'user', content: 'Task...' }],
});
```
## Beta Models
Access beta models with beta headers:
```typescript
const model = await client.beta.models.retrieve(
'claude-sonnet-4-5-20250929',
{
betas: ['extended-thinking-2025-05-14'],
}
);
```
## Model Deprecation
Models are deprecated over time. The SDK will warn when using deprecated models:
```typescript
// Using deprecated model
const message = await client.messages.create({
model: 'claude-2.1', // Deprecated model
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
// Console warning:
// "The model 'claude-2.1' is deprecated and will reach end-of-life on 2024-12-31
// Please migrate to a newer model. Visit https://docs.anthropic.com/..."
```
## Context Windows
All current models support 200K token context windows:
```typescript
// You can use large context
const message = await client.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
system: largeSystemPrompt, // Can be very large
messages: longConversation, // Many turns
});
// Count tokens first
const count = await client.messages.countTokens({
model: 'claude-sonnet-4-5-20250929',
system: largeSystemPrompt,
messages: longConversation,
});
console.log('Input tokens:', count.input_tokens);
// Ensure: count.input_tokens + max_tokens <= 200000
```
## Best Practices
### Model Choice
```typescript
// ✅ Good: Choose based on task
async function processTask(task: Task) {
let model: string;
if (task.requiresDeepReasoning) {
model = 'claude-opus-4-5-20250514';
} else if (task.isSimple) {
model = 'claude-3-5-haiku-20241022';
} else {
model = 'claude-sonnet-4-5-20250929'; // Default
}
return client.messages.create({ model, /* ... */ });
}
// ❌ Bad: Always using most expensive model
async function processTask(task: Task) {
return client.messages.create({
model: 'claude-opus-4-5-20250514', // Overkill for simple tasks
/* ... */
});
}
```
### Cost Optimization
```typescript
// ✅ Good: Use appropriate model for workload
const simpleQueries = tasks.filter(t => t.complexity === 'simple');
const complexQueries = tasks.filter(t => t.complexity === 'complex');
// Process simple with Haiku (cheaper)
for (const task of simpleQueries) {
await client.messages.create({
model: 'claude-3-5-haiku-20241022',
/* ... */
});
}
// Process complex with Opus (necessary)
for (const task of complexQueries) {
await client.messages.create({
model: 'claude-opus-4-5-20250514',
/* ... */
});
}
```
### Model Validation
```typescript
// ✅ Good: Validate model exists
async function createMessage(model: string, content: string) {
try {
await client.models.retrieve(model);
} catch (error) {
if (error instanceof Anthropic.NotFoundError) {
console.error(`Model ${model} not found`);
model = 'claude-sonnet-4-5-20250929'; // Fallback
}
}
return client.messages.create({ model, /* ... */ });
}
```
## See Also
- [Messages API](./messages.md) - Using models for message creation
- [Batches](./batches.md) - Model selection in batches
- [Client](./client.md) - Client configuration

View file

@ -0,0 +1,464 @@
# Skills API (Beta)
The Skills API allows you to create and manage custom skills that extend Claude's capabilities with reusable, versioned components.
## Overview
Skills enable:
- Define reusable capabilities for Claude
- Version management for skills
- Organized skill libraries
- Consistent behavior across conversations
**Beta Feature**: Requires `betas: ['skills-2025-10-02']`
## API Reference
```typescript { .api }
class Skills extends APIResource {
create(params: SkillCreateParams): APIPromise<SkillCreateResponse>;
retrieve(skillID: string, params?: SkillRetrieveParams): APIPromise<SkillRetrieveResponse>;
list(params?: SkillListParams): SkillListResponsesPageCursor;
delete(skillID: string, params?: SkillDeleteParams): APIPromise<SkillDeleteResponse>;
// Sub-resource
versions: Versions;
}
class Versions extends APIResource {
create(skillID: string, params: VersionCreateParams): APIPromise<VersionCreateResponse>;
retrieve(skillID: string, versionID: string, params?: VersionRetrieveParams): APIPromise<VersionRetrieveResponse>;
list(skillID: string, params?: VersionListParams): VersionListResponsesPageCursor;
delete(skillID: string, versionID: string, params?: VersionDeleteParams): APIPromise<VersionDeleteResponse>;
}
```
Access via:
```typescript
client.beta.skills.*
client.beta.skills.versions.*
```
## Types
```typescript { .api }
interface SkillCreateResponse {
id: string;
type: 'skill';
name: string;
description: string;
created_at: string;
}
interface SkillRetrieveResponse {
id: string;
type: 'skill';
name: string;
description: string;
created_at: string;
versions: VersionListResponse[];
}
interface SkillListResponse {
id: string;
type: 'skill';
name: string;
description: string;
created_at: string;
}
interface SkillDeleteResponse {
id: string;
type: 'skill';
deleted: boolean;
}
interface VersionCreateResponse {
id: string;
skill_id: string;
version: string;
created_at: string;
}
interface VersionRetrieveResponse {
id: string;
skill_id: string;
version: string;
definition: any; // Skill definition
created_at: string;
}
```
## Creating Skills
```typescript { .api }
client.beta.skills.create(
params: SkillCreateParams
): APIPromise<SkillCreateResponse>;
interface SkillCreateParams {
name: string;
description: string;
definition: any; // Skill implementation
betas: ['skills-2025-10-02'];
}
```
**Example:**
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const skill = await client.beta.skills.create({
name: 'data_analyzer',
description: 'Analyze structured data and provide insights',
definition: {
// Skill implementation details
type: 'analysis',
capabilities: ['statistics', 'visualization', 'trend_detection'],
},
betas: ['skills-2025-10-02'],
});
console.log('Skill ID:', skill.id);
console.log('Name:', skill.name);
```
## Using Skills in Messages
```typescript
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 2048,
betas: ['skills-2025-10-02'],
skills: [
{
type: 'skill',
skill_id: skill.id,
},
],
messages: [
{
role: 'user',
content: 'Analyze this dataset and provide insights.',
},
],
});
```
## Retrieving Skills
```typescript { .api }
client.beta.skills.retrieve(
skillID: string,
params?: SkillRetrieveParams
): APIPromise<SkillRetrieveResponse>;
interface SkillRetrieveParams {
betas: ['skills-2025-10-02'];
}
```
**Example:**
```typescript
const skill = await client.beta.skills.retrieve('skill_abc123', {
betas: ['skills-2025-10-02'],
});
console.log('Skill:', skill.name);
console.log('Description:', skill.description);
console.log('Versions:', skill.versions.length);
```
## Listing Skills
```typescript { .api }
client.beta.skills.list(
params?: SkillListParams
): SkillListResponsesPageCursor;
interface SkillListParams {
limit?: number;
next_page?: string;
betas: ['skills-2025-10-02'];
}
```
**Example:**
```typescript
const skills = await client.beta.skills.list({
limit: 20,
betas: ['skills-2025-10-02'],
});
for (const skill of skills.data) {
console.log(`${skill.name}: ${skill.description}`);
}
// Auto-pagination
for await (const skill of client.beta.skills.list({ betas: ['skills-2025-10-02'] })) {
console.log(skill.id);
}
```
## Deleting Skills
```typescript { .api }
client.beta.skills.delete(
skillID: string,
params?: SkillDeleteParams
): APIPromise<SkillDeleteResponse>;
interface SkillDeleteParams {
betas: ['skills-2025-10-02'];
}
```
**Example:**
```typescript
const deleted = await client.beta.skills.delete('skill_abc123', {
betas: ['skills-2025-10-02'],
});
console.log('Deleted:', deleted.deleted); // true
```
## Version Management
Skills support versioning for controlled updates:
### Creating Versions
```typescript { .api }
client.beta.skills.versions.create(
skillID: string,
params: VersionCreateParams
): APIPromise<VersionCreateResponse>;
interface VersionCreateParams {
version: string;
definition: any;
betas: ['skills-2025-10-02'];
}
```
**Example:**
```typescript
// Create new version
const version = await client.beta.skills.versions.create('skill_abc123', {
version: '2.0.0',
definition: {
// Updated skill definition
type: 'analysis',
capabilities: ['statistics', 'visualization', 'trend_detection', 'forecasting'],
},
betas: ['skills-2025-10-02'],
});
console.log('Version created:', version.version);
```
### Retrieving Versions
```typescript { .api }
client.beta.skills.versions.retrieve(
skillID: string,
versionID: string,
params?: VersionRetrieveParams
): APIPromise<VersionRetrieveResponse>;
```
**Example:**
```typescript
const version = await client.beta.skills.versions.retrieve(
'skill_abc123',
'2.0.0',
{ betas: ['skills-2025-10-02'] }
);
console.log('Version:', version.version);
console.log('Definition:', version.definition);
```
### Listing Versions
```typescript { .api }
client.beta.skills.versions.list(
skillID: string,
params?: VersionListParams
): VersionListResponsesPageCursor;
```
**Example:**
```typescript
const versions = await client.beta.skills.versions.list('skill_abc123', {
betas: ['skills-2025-10-02'],
});
for (const version of versions.data) {
console.log(`Version ${version.version} - ${version.created_at}`);
}
```
### Deleting Versions
```typescript { .api }
client.beta.skills.versions.delete(
skillID: string,
versionID: string,
params?: VersionDeleteParams
): APIPromise<VersionDeleteResponse>;
```
**Example:**
```typescript
await client.beta.skills.versions.delete(
'skill_abc123',
'1.0.0',
{ betas: ['skills-2025-10-02'] }
);
```
## Complete Workflow
```typescript
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// 1. Create skill
const skill = await client.beta.skills.create({
name: 'sentiment_analyzer',
description: 'Analyze sentiment in text',
definition: {
type: 'text_analysis',
models: ['sentiment'],
},
betas: ['skills-2025-10-02'],
});
// 2. Use skill
const message = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['skills-2025-10-02'],
skills: [
{
type: 'skill',
skill_id: skill.id,
},
],
messages: [
{
role: 'user',
content: 'Analyze the sentiment of this review: "This product is amazing!"',
},
],
});
console.log('Analysis:', message.content[0].text);
// 3. Create new version
const version = await client.beta.skills.versions.create(skill.id, {
version: '1.1.0',
definition: {
type: 'text_analysis',
models: ['sentiment', 'emotion'], // Added emotion detection
},
betas: ['skills-2025-10-02'],
});
// 4. Use specific version
const message2 = await client.beta.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['skills-2025-10-02'],
skills: [
{
type: 'skill',
skill_id: skill.id,
version: '1.1.0', // Specific version
},
],
messages: [
{
role: 'user',
content: 'Analyze sentiment and emotions.',
},
],
});
// 5. Cleanup (optional)
// await client.beta.skills.delete(skill.id, { betas: ['skills-2025-10-02'] });
```
## Best Practices
### Skill Organization
```typescript
// ✅ Good: Descriptive names and organization
const skills = {
dataAnalysis: await client.beta.skills.create({
name: 'data_analysis',
description: 'Statistical analysis and visualization',
definition: { /* ... */ },
betas: ['skills-2025-10-02'],
}),
textSummarization: await client.beta.skills.create({
name: 'text_summarization',
description: 'Extract key points from documents',
definition: { /* ... */ },
betas: ['skills-2025-10-02'],
}),
};
```
### Version Control
```typescript
// ✅ Good: Semantic versioning
await client.beta.skills.versions.create(skillId, {
version: '2.1.0', // major.minor.patch
definition: { /* ... */ },
betas: ['skills-2025-10-02'],
});
// ❌ Bad: Unclear versioning
await client.beta.skills.versions.create(skillId, {
version: 'v2',
definition: { /* ... */ },
betas: ['skills-2025-10-02'],
});
```
### Error Handling
```typescript
try {
const skill = await client.beta.skills.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.BadRequestError) {
console.error('Invalid skill definition');
} else if (error instanceof Anthropic.ConflictError) {
console.error('Skill with this name already exists');
}
throw error;
}
```
## See Also
- [Messages API](./messages.md) - Using skills in messages
- [Tools](./tools.md) - Tool use and skills
- [Beta Features](./beta-features.md) - Other beta capabilities

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
{
"name": "tessl/npm-anthropic-ai--sdk",
"version": "0.70.0",
"docs": "docs/index.md",
"describes": "pkg:npm/%40anthropic-ai/sdk@0.70.0",
"summary": "The official TypeScript library for the Anthropic API, providing a comprehensive client for interacting with Claude AI models.",
"private": false
}

View file

@ -0,0 +1,601 @@
# Authentication Flows
MSAL Node supports multiple OAuth 2.0 authentication flows, each designed for specific application scenarios and security requirements. This document covers all supported flows with their request types, usage patterns, and security considerations.
## Capabilities
### Authorization Code Flow
The most secure and recommended flow for most applications, using OAuth 2.0 authorization code grant with PKCE.
```typescript { .api }
/**
* Request to generate authorization URL (first step)
*/
type AuthorizationUrlRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server will redirect after user authorization */
redirectUri: string;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Account to use for authentication */
account?: AccountInfo;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** PKCE code challenge for security */
codeChallenge?: string;
/** PKCE code challenge method */
codeChallengeMethod?: string;
/** State parameter for CSRF protection */
state?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Authority URL to override default */
authority?: string;
/** Response mode (query, fragment, form_post) */
responseMode?: ResponseMode;
};
/**
* Request to exchange authorization code for tokens (second step)
*/
type AuthorizationCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server redirected after user authorization */
redirectUri: string;
/** Authorization code received from authorization server */
code: string;
/** State parameter for CSRF protection validation */
state?: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** PKCE code verifier for security validation */
codeVerifier?: string;
};
/**
* Prompt values for user interaction
*/
enum PromptValue {
/** Force user to enter credentials */
LOGIN = "login",
/** Show account selection screen */
SELECT_ACCOUNT = "select_account",
/** Request user consent */
CONSENT = "consent",
/** No user interaction (silent) */
NONE = "none"
}
/**
* Response modes for authorization response
*/
enum ResponseMode {
/** Return parameters in URL query string */
QUERY = "query",
/** Return parameters in URL fragment */
FRAGMENT = "fragment",
/** Return parameters via HTTP POST */
FORM_POST = "form_post"
}
```
**Usage Example:**
```typescript
import { PublicClientApplication, PromptValue } from "@azure/msal-node";
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/common"
}
});
// Step 1: Generate authorization URL
const authCodeUrlParameters = {
scopes: ["openid", "profile", "user.read"],
redirectUri: "http://localhost:3000/redirect",
prompt: PromptValue.SELECT_ACCOUNT,
state: "random-state-value", // CSRF protection
loginHint: "user@domain.com"
};
const authUrl = await pca.getAuthCodeUrl(authCodeUrlParameters);
// Step 2: Exchange code for tokens (after user authorization)
const tokenRequest = {
code: "authorization-code-from-callback",
scopes: ["openid", "profile", "user.read"],
redirectUri: "http://localhost:3000/redirect",
state: "random-state-value"
};
const response = await pca.acquireTokenByCode(tokenRequest);
```
### Client Credentials Flow
App-only authentication flow for server applications and daemon apps that don't require user interaction.
```typescript { .api }
/**
* Request for client credentials flow (app-only authentication)
*/
type ClientCredentialRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Client assertion JWT for certificate-based authentication */
clientAssertion?: string | (() => string);
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
import { ConfidentialClientApplication } from "@azure/msal-node";
const cca = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientSecret: "your-client-secret", // Or use clientCertificate
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"],
skipCache: false // Use cache if available
};
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
// Use token for Microsoft Graph API calls
const graphData = await fetch("https://graph.microsoft.com/v1.0/users", {
headers: {
"Authorization": `Bearer ${response.accessToken}`
}
});
```
### Device Code Flow
Authentication flow for devices with limited input capabilities or no browser.
```typescript { .api }
/**
* Request for device code flow authentication
*/
type DeviceCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Callback function to display device code to user */
deviceCodeCallback: (response: DeviceCodeResponse) => void;
/** Flag to cancel the device code flow polling */
cancel?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional query parameters for the device authorization request */
extraQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Response containing device code information for user
*/
type DeviceCodeResponse = {
/** Device code for internal polling */
deviceCode: string;
/** User-friendly code to display to user */
userCode: string;
/** URL where user should navigate to enter the user code */
verificationUri: string;
/** Complete verification URL including user code (optional) */
verificationUriComplete?: string;
/** Number of seconds the device code is valid */
expiresIn: number;
/** Minimum number of seconds to wait between polling requests */
interval: number;
/** Message to display to user */
message: string;
};
```
**Usage Example:**
```typescript
const deviceCodeRequest = {
scopes: ["user.read"],
deviceCodeCallback: (response) => {
console.log("=== Device Code Authentication ===");
console.log("Please open a web browser and navigate to:");
console.log(response.verificationUri);
console.log("Enter the following code:");
console.log(response.userCode);
console.log("Or visit this URL directly:");
console.log(response.verificationUriComplete);
console.log(`Code expires in ${response.expiresIn} seconds`);
}
};
try {
const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
if (response) {
console.log("Authentication successful!");
console.log("Access token:", response.accessToken);
}
} catch (error) {
console.error("Device code flow failed:", error);
}
```
### On-Behalf-Of Flow
Flow for middle-tier services to exchange user tokens for tokens to downstream services.
```typescript { .api }
/**
* Request for on-behalf-of flow for middle-tier services
*/
type OnBehalfOfRequest = {
/** User assertion (JWT) from the upstream service */
oboAssertion: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
// Express.js middleware API that calls downstream services
app.post("/api/user-data", async (req, res) => {
const userToken = req.headers.authorization?.replace("Bearer ", "");
if (!userToken) {
return res.status(401).json({ error: "No authorization token" });
}
const oboRequest = {
oboAssertion: userToken,
scopes: ["https://graph.microsoft.com/user.read", "https://graph.microsoft.com/mail.read"]
};
try {
const response = await cca.acquireTokenOnBehalfOf(oboRequest);
if (response) {
// Call Microsoft Graph on behalf of the user
const userProfile = await fetch("https://graph.microsoft.com/v1.0/me", {
headers: {
"Authorization": `Bearer ${response.accessToken}`
}
});
const userData = await userProfile.json();
res.json(userData);
} else {
res.status(500).json({ error: "Failed to acquire OBO token" });
}
} catch (error) {
console.error("OBO flow failed:", error);
res.status(500).json({ error: "Authentication failed" });
}
});
```
### Silent Token Acquisition
Acquire tokens without user interaction using cached tokens or refresh tokens.
```typescript { .api }
/**
* Request for silent token acquisition from cache
*/
type SilentFlowRequest = {
/** Account to acquire token for */
account: AccountInfo;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Force refresh from authority instead of using cache */
forceRefresh?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
// Get cached accounts
const accounts = await pca.getAllAccounts();
if (accounts.length > 0) {
const silentRequest = {
account: accounts[0],
scopes: ["user.read"],
forceRefresh: false
};
try {
const response = await pca.acquireTokenSilent(silentRequest);
console.log("Token acquired silently:", response.accessToken);
} catch (error) {
if (error.errorCode === "interaction_required") {
// Fall back to interactive flow
console.log("Silent acquisition failed, user interaction required");
}
}
}
```
### Refresh Token Flow
Exchange refresh tokens for new access tokens.
```typescript { .api }
/**
* Request for refresh token flow
*/
type RefreshTokenRequest = {
/** Refresh token to exchange for new tokens */
refreshToken: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
/** Force cache refresh for migration scenarios */
forceCache?: boolean;
};
```
**Usage Example:**
```typescript
// Refresh token stored from previous authentication
const storedRefreshToken = getStoredRefreshToken();
const refreshRequest = {
refreshToken: storedRefreshToken,
scopes: ["user.read", "mail.read"]
};
try {
const response = await pca.acquireTokenByRefreshToken(refreshRequest);
if (response) {
console.log("Token refreshed successfully");
// Update stored tokens
storeTokens(response);
}
} catch (error) {
console.error("Token refresh failed:", error);
// May need to prompt user for re-authentication
}
```
### Interactive Flow
Browser-based interactive authentication for desktop applications.
```typescript { .api }
/**
* Request for interactive token acquisition with browser
*/
type InteractiveRequest = {
/** Function to open browser with authorization URL */
openBrowser: (url: string) => Promise<void>;
/** Array of scopes the application is requesting access to */
scopes?: string[];
/** HTML template for success page */
successTemplate?: string;
/** HTML template for error page */
errorTemplate?: string;
/** Window handle for desktop applications */
windowHandle?: Buffer;
/** Custom loopback client for handling redirects */
loopbackClient?: ILoopbackClient;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Account to use for authentication */
account?: AccountInfo;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Interface for custom loopback client implementations
*/
interface ILoopbackClient {
/** Start listening for authorization response */
listenForAuthCode(authCodeUrl: string, port?: number): Promise<string>;
/** Get the redirect URI for the loopback server */
getRedirectUri(): string;
/** Close the loopback server */
closeServer(): void;
}
```
**Usage Example:**
```typescript
import { open } from "open"; // npm package for opening URLs
const interactiveRequest = {
scopes: ["user.read", "mail.read"],
openBrowser: async (url: string) => {
console.log("Opening browser...");
await open(url);
},
successTemplate: `
<html>
<body>
<h1>Authentication Successful!</h1>
<p>You can close this window and return to the application.</p>
</body>
</html>
`,
prompt: PromptValue.SELECT_ACCOUNT
};
try {
const response = await pca.acquireTokenInteractive(interactiveRequest);
console.log("Interactive authentication successful!");
console.log("User:", response.account?.username);
} catch (error) {
console.error("Interactive authentication failed:", error);
}
```
### Username/Password Flow (Deprecated)
Resource Owner Password Credentials (ROPC) flow - deprecated for security reasons.
```typescript { .api }
/**
* Request for username/password flow (deprecated)
* @deprecated Use more secure flows like authorization code or device code
*/
type UsernamePasswordRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Username for authentication */
username: string;
/** Password for authentication */
password: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example (Not Recommended):**
```typescript
// ⚠️ This flow is deprecated and should be avoided for security reasons
const usernamePasswordRequest = {
scopes: ["user.read"],
username: "user@domain.com",
password: "user-password"
};
try {
const response = await pca.acquireTokenByUsernamePassword(usernamePasswordRequest);
// This flow bypasses multi-factor authentication and modern security features
} catch (error) {
console.error("Username/password authentication failed:", error);
}
```
## Flow Selection Guide
### Public Client Applications
- **Authorization Code Flow**: Web apps, SPAs with backend
- **Interactive Flow**: Desktop applications
- **Device Code Flow**: IoT devices, CLI tools, smart TVs
- **Silent Flow**: Token refresh for all scenarios
### Confidential Client Applications
- **Client Credentials Flow**: Daemon apps, background services
- **Authorization Code Flow**: Web applications with server-side code
- **On-Behalf-Of Flow**: Middle-tier APIs calling downstream services
- **Silent Flow**: Token refresh and cache utilization
### Security Considerations
```typescript { .api }
/**
* Security best practices for different flows
*/
type SecurityConsiderations = {
/** Always use PKCE for authorization code flow */
usePKCE: boolean;
/** Validate state parameter for CSRF protection */
validateState: boolean;
/** Use secure storage for refresh tokens */
secureTokenStorage: boolean;
/** Implement proper token caching */
implementCaching: boolean;
/** Use certificate-based authentication when possible */
useCertificates: boolean;
/** Avoid username/password flow */
avoidROPC: boolean;
};
```
**Recommended Flow Selection:**
1. **Server Applications**: Client Credentials Flow
2. **Web Applications**: Authorization Code Flow
3. **Desktop Applications**: Interactive Flow or Authorization Code Flow
4. **Mobile Applications**: Authorization Code Flow with PKCE
5. **IoT/CLI Applications**: Device Code Flow
6. **Middle-tier APIs**: On-Behalf-Of Flow
7. **Background Services**: Client Credentials Flow
**Flows to Avoid:**
- Username/Password Flow (deprecated and insecure)
- Implicit Flow (less secure than authorization code with PKCE)

View file

@ -0,0 +1,512 @@
# Confidential Client Applications
Confidential Client Applications are designed for applications that can securely store client secrets, such as server applications, web APIs, and daemon applications. These applications support advanced authentication flows including client credentials, on-behalf-of, and certificate-based authentication.
## Capabilities
### ConfidentialClientApplication Class
Main class for creating confidential client applications.
```typescript { .api }
/**
* Confidential client application class implementing IConfidentialClientApplication interface
* Suitable for applications that can securely store client secrets
*/
class ConfidentialClientApplication implements IConfidentialClientApplication {
constructor(configuration: Configuration);
/** Creates the URL of the authorization request */
getAuthCodeUrl(request: AuthorizationUrlRequest): Promise<string>;
/** Acquires a token by exchanging the authorization code received from the first step of OAuth 2.0 Authorization Code Flow */
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
/** Acquires a token silently when a user specifies the account the token is requested for */
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult | null>;
/** Acquires a token by exchanging the refresh token provided for a new set of tokens */
acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise<AuthenticationResult | null>;
/** Acquires tokens from the authority for the application (not for an end user) */
acquireTokenByClientCredential(request: ClientCredentialRequest): Promise<AuthenticationResult | null>;
/** Acquires tokens from the authority for the application */
acquireTokenOnBehalfOf(request: OnBehalfOfRequest): Promise<AuthenticationResult | null>;
/**
* Acquires tokens with password grant by exchanging client applications username and password for credentials
* @deprecated - Use a more secure flow instead
*/
acquireTokenByUsernamePassword(request: UsernamePasswordRequest): Promise<AuthenticationResult | null>;
/** Gets the token cache for the application */
getTokenCache(): TokenCache;
/** Returns the logger instance */
getLogger(): Logger;
/** Replaces the default logger set in configurations with new Logger with new configurations */
setLogger(logger: Logger): void;
/** Clear the cache */
clearCache(): void;
/** This extensibility point is meant for Azure SDK to enhance Managed Identity support */
SetAppTokenProvider(provider: IAppTokenProvider): void;
}
```
**Usage Example:**
```typescript
import { ConfidentialClientApplication } from "@azure/msal-node";
// With client secret
const cca = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
// With client certificate
const ccaWithCert = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientCertificate: {
thumbprintSha256: "cert-thumbprint-sha256",
privateKey: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
x5c: "base64-encoded-certificate"
},
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
```
### Client Credentials Flow
OAuth 2.0 client credentials flow for app-only authentication without user interaction.
```typescript { .api }
/**
* Request for client credentials flow (app-only authentication)
*/
type ClientCredentialRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Client assertion JWT for certificate-based authentication */
clientAssertion?: string | (() => string);
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
// Basic client credentials flow
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"]
};
try {
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
if (response) {
console.log("Access token:", response.accessToken);
console.log("Token expires on:", response.expiresOn);
}
} catch (error) {
console.error("Client credential authentication failed:", error);
}
// With custom client assertion for certificate rotation
const clientCredentialWithAssertion = {
scopes: ["https://graph.microsoft.com/.default"],
clientAssertion: () => {
// Generate JWT assertion dynamically
return generateClientAssertion();
},
skipCache: true // Always get fresh token
};
const response = await cca.acquireTokenByClientCredential(clientCredentialWithAssertion);
```
### On-Behalf-Of Flow
OAuth 2.0 on-behalf-of flow for middle-tier services to act on behalf of users.
```typescript { .api }
/**
* Request for on-behalf-of flow for middle-tier services
*/
type OnBehalfOfRequest = {
/** User assertion (JWT) from the upstream service */
oboAssertion: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Skip cache and force token acquisition from authority */
skipCache?: boolean;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
// Middleware service receiving user token and calling downstream API
app.post("/api/data", async (req, res) => {
const userToken = req.headers.authorization?.replace("Bearer ", "");
if (!userToken) {
return res.status(401).json({ error: "No token provided" });
}
const oboRequest = {
oboAssertion: userToken,
scopes: ["https://graph.microsoft.com/user.read"]
};
try {
const response = await cca.acquireTokenOnBehalfOf(oboRequest);
if (response) {
// Use the access token to call Microsoft Graph on behalf of user
const graphResponse = await callMicrosoftGraph(response.accessToken);
res.json(graphResponse);
} else {
res.status(500).json({ error: "Failed to acquire token" });
}
} catch (error) {
console.error("OBO flow failed:", error);
res.status(500).json({ error: "Authentication failed" });
}
});
```
### Authorization Code Flow (Server-Side)
Server-side authorization code flow for web applications.
```typescript { .api }
/**
* Same types as public client but with confidential client capabilities
*/
type AuthorizationUrlRequest = {
scopes: string[];
redirectUri: string;
prompt?: PromptValue;
state?: string;
loginHint?: string;
domainHint?: string;
extraQueryParameters?: Record<string, string>;
authority?: string;
correlationId?: string;
claims?: string;
};
type AuthorizationCodeRequest = {
scopes: string[];
redirectUri: string;
code: string;
state?: string;
authority?: string;
correlationId?: string;
claims?: string;
tokenQueryParameters?: Record<string, string>;
};
```
**Usage Example:**
```typescript
// Express.js web application example
app.get("/auth", async (req, res) => {
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: "https://your-app.com/redirect",
state: generateState(), // Generate CSRF protection state
};
try {
const authUrl = await cca.getAuthCodeUrl(authCodeUrlParameters);
res.redirect(authUrl);
} catch (error) {
console.error("Failed to generate auth URL:", error);
res.status(500).send("Authentication initiation failed");
}
});
app.get("/redirect", async (req, res) => {
const { code, state } = req.query;
// Validate state parameter for CSRF protection
if (!validateState(state)) {
return res.status(400).send("Invalid state parameter");
}
const tokenRequest = {
code: code as string,
scopes: ["user.read"],
redirectUri: "https://your-app.com/redirect",
state: state as string
};
try {
const response = await cca.acquireTokenByCode(tokenRequest);
// Store tokens securely (e.g., encrypted session)
req.session.accessToken = response.accessToken;
req.session.account = response.account;
res.redirect("/dashboard");
} catch (error) {
console.error("Token exchange failed:", error);
res.status(500).send("Authentication failed");
}
});
```
### Silent Token Acquisition
Silent token acquisition for confidential clients with refresh token support.
```typescript { .api }
/**
* Request for silent token acquisition
*/
type SilentFlowRequest = {
/** Account to acquire token for (optional for confidential clients) */
account?: AccountInfo;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Force refresh from authority instead of using cache */
forceRefresh?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Request for refresh token flow
*/
type RefreshTokenRequest = {
/** Refresh token to exchange for new tokens */
refreshToken: string;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
/** Force cache refresh for migration scenarios */
forceCache?: boolean;
};
```
**Usage Example:**
```typescript
// Silent token acquisition
const silentRequest = {
scopes: ["user.read"],
forceRefresh: false
};
try {
const response = await cca.acquireTokenSilent(silentRequest);
if (response) {
console.log("Token acquired silently:", response.accessToken);
}
} catch (error) {
console.log("Silent acquisition failed, may need user interaction");
}
// Refresh token flow
const refreshRequest = {
refreshToken: storedRefreshToken,
scopes: ["user.read", "mail.read"]
};
try {
const response = await cca.acquireTokenByRefreshToken(refreshRequest);
if (response) {
console.log("Token refreshed successfully:", response.accessToken);
// Update stored tokens
updateStoredTokens(response);
}
} catch (error) {
console.error("Token refresh failed:", error);
}
```
### Username/Password Flow (Deprecated)
Resource Owner Password Credentials (ROPC) flow - deprecated for security reasons.
```typescript { .api }
/**
* Request for username/password flow (deprecated)
* @deprecated Use more secure flows like authorization code or device code
*/
type UsernamePasswordRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Username for authentication */
username: string;
/** Password for authentication */
password: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example (Not Recommended):**
```typescript
// This flow is deprecated and should be avoided
const usernamePasswordRequest = {
scopes: ["user.read"],
username: "user@domain.com",
password: "user-password"
};
try {
const response = await cca.acquireTokenByUsernamePassword(usernamePasswordRequest);
if (response) {
console.log("Access token:", response.accessToken);
}
} catch (error) {
console.error("Username/password authentication failed:", error);
}
```
### Azure SDK Integration
App token provider integration for Azure SDK services.
```typescript { .api }
/**
* Interface for app token provider (Azure SDK integration)
*/
interface IAppTokenProvider {
getAppTokenProviderParameters(appTokenProviderParameters: AppTokenProviderParameters): AppTokenProviderResult;
}
type AppTokenProviderParameters = {
scopes: string[];
correlationId?: string;
tenantId?: string;
claims?: string;
};
type AppTokenProviderResult = {
accessToken: string;
expiresOnTimestamp: number;
};
```
**Usage Example:**
```typescript
// Custom app token provider for Azure SDK
class CustomAppTokenProvider implements IAppTokenProvider {
getAppTokenProviderParameters(params: AppTokenProviderParameters): AppTokenProviderResult {
// Custom token acquisition logic
return {
accessToken: "custom-token",
expiresOnTimestamp: Date.now() + 3600000 // 1 hour
};
}
}
// Set the app token provider
const tokenProvider = new CustomAppTokenProvider();
cca.SetAppTokenProvider(tokenProvider);
```
## Advanced Configuration
### Certificate-based Authentication
```typescript { .api }
type NodeAuthOptions = {
clientId: string;
authority?: string;
clientCertificate?: {
/** SHA-256 thumbprint of the certificate */
thumbprintSha256?: string;
/**
* @deprecated Use thumbprintSha256 instead
* SHA-1 thumbprint for backwards compatibility
*/
thumbprint?: string;
/** PEM encoded private key */
privateKey: string;
/** Base64 encoded X.509 certificate chain */
x5c?: string;
};
};
```
### Client Assertion Configuration
```typescript { .api }
type ClientAssertionCallback = () => string;
type NodeAuthOptions = {
clientId: string;
authority?: string;
/** Client assertion JWT or callback function that returns JWT */
clientAssertion?: string | ClientAssertionCallback;
};
```
**Usage Example:**
```typescript
// Dynamic client assertion generation
const ccaWithAssertion = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientAssertion: () => {
// Generate JWT assertion with current timestamp and certificate
return generateJWTAssertion({
clientId: "your-client-id",
audience: "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
certificate: loadCurrentCertificate()
});
},
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
```

View file

@ -0,0 +1,746 @@
# Configuration System
MSAL Node provides a comprehensive hierarchical configuration system that allows fine-grained control over authentication, caching, networking, logging, and telemetry. The configuration system supports both public and confidential client applications with specialized options for different deployment scenarios.
## Capabilities
### Main Configuration
Core configuration structure for MSAL applications.
```typescript { .api }
/**
* Main configuration object for MSAL applications
*/
type Configuration = {
/** Authentication configuration (required) */
auth: NodeAuthOptions;
/** Broker configuration for native authentication */
broker?: BrokerOptions;
/** Cache configuration options */
cache?: CacheOptions;
/** System-level configuration */
system?: NodeSystemOptions;
/** Telemetry configuration */
telemetry?: NodeTelemetryOptions;
};
```
**Usage Example:**
```typescript
import { PublicClientApplication, LogLevel } from "@azure/msal-node";
const config: Configuration = {
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/common"
},
cache: {
cachePlugin: // your cache plugin
},
system: {
loggerOptions: {
logLevel: LogLevel.Info,
loggerCallback: (level, message) => console.log(message)
},
networkClient: // custom network client
},
telemetry: {
application: {
appName: "MyApp",
appVersion: "1.0.0"
}
}
};
const pca = new PublicClientApplication(config);
```
### Authentication Configuration
Core authentication settings including client credentials, authority, and protocol options.
```typescript { .api }
/**
* Authentication configuration options
*/
type NodeAuthOptions = {
/** Application (client) ID from app registration */
clientId: string;
/** Authority URL - defaults to https://login.microsoftonline.com/common */
authority?: string;
/** Client secret for confidential client applications */
clientSecret?: string;
/** Client assertion JWT or callback function for certificate-based auth */
clientAssertion?: string | ClientAssertionCallback;
/** Client certificate configuration for certificate-based auth */
clientCertificate?: {
/**
* @deprecated Use thumbprintSha256 property instead
* SHA-1 thumbprint for backwards compatibility with older ADFS versions
*/
thumbprint?: string;
/** SHA-256 thumbprint of the certificate (recommended) */
thumbprintSha256?: string;
/** PEM encoded private key */
privateKey: string;
/** X.509 certificate chain (optional) */
x5c?: string;
};
/** Known authorities for B2C and ADFS scenarios */
knownAuthorities?: Array<string>;
/** Cloud discovery metadata for custom clouds */
cloudDiscoveryMetadata?: string;
/** Authority metadata for custom authorities */
authorityMetadata?: string;
/** Client capabilities for conditional access */
clientCapabilities?: Array<string>;
/** Protocol mode (AAD or OIDC) */
protocolMode?: ProtocolMode;
/** Azure cloud configuration options */
azureCloudOptions?: AzureCloudOptions;
/** Skip authority metadata cache for testing */
skipAuthorityMetadataCache?: boolean;
/**
* @deprecated This flag is deprecated and will be removed in the next major version
* All extra query params will be encoded by default
*/
encodeExtraQueryParams?: boolean;
};
/**
* Callback function type for client assertions
*/
type ClientAssertionCallback = () => string;
/**
* Protocol modes supported by MSAL
*/
enum ProtocolMode {
/** Azure Active Directory protocol */
AAD = "AAD",
/** OpenID Connect protocol */
OIDC = "OIDC"
}
```
**Usage Examples:**
```typescript
// Basic public client configuration
const publicClientAuth: NodeAuthOptions = {
clientId: "12345678-1234-1234-1234-123456789012",
authority: "https://login.microsoftonline.com/common"
};
// Confidential client with client secret
const confidentialClientAuth: NodeAuthOptions = {
clientId: "12345678-1234-1234-1234-123456789012",
clientSecret: "your-client-secret",
authority: "https://login.microsoftonline.com/your-tenant-id"
};
// Certificate-based authentication
const certificateAuth: NodeAuthOptions = {
clientId: "12345678-1234-1234-1234-123456789012",
clientCertificate: {
thumbprintSha256: "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99",
privateKey: `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
-----END PRIVATE KEY-----`,
x5c: "base64-encoded-certificate-chain"
},
authority: "https://login.microsoftonline.com/your-tenant-id"
};
// Dynamic client assertion
const dynamicAssertionAuth: NodeAuthOptions = {
clientId: "12345678-1234-1234-1234-123456789012",
clientAssertion: () => {
// Generate JWT assertion dynamically
return generateJWTAssertion();
},
authority: "https://login.microsoftonline.com/your-tenant-id"
};
// B2C configuration
const b2cAuth: NodeAuthOptions = {
clientId: "12345678-1234-1234-1234-123456789012",
authority: "https://yourtenant.b2clogin.com/yourtenant.onmicrosoft.com/B2C_1_signupsignin1",
knownAuthorities: ["yourtenant.b2clogin.com"],
protocolMode: ProtocolMode.OIDC
};
```
### Azure Cloud Configuration
Configuration for different Azure cloud environments.
```typescript { .api }
/**
* Azure cloud instance enumeration
*/
enum AzureCloudInstance {
/** No specific cloud (use authority as-is) */
None = "",
/** Azure Public Cloud */
AzurePublic = "https://login.microsoftonline.com",
/** Azure US Government Cloud */
AzurePpope = "https://login.microsoftonline.us",
/** Azure China Cloud */
AzureChina = "https://login.chinacloudapi.cn",
/** Azure Germany Cloud */
AzureGermany = "https://login.microsoftonline.de"
}
/**
* Azure cloud configuration options
*/
type AzureCloudOptions = {
/** Azure cloud instance */
azureCloudInstance: AzureCloudInstance;
/** Tenant ID or domain */
tenant: string;
};
```
**Usage Example:**
```typescript
// Azure US Government configuration
const govCloudAuth: NodeAuthOptions = {
clientId: "your-client-id",
azureCloudOptions: {
azureCloudInstance: AzureCloudInstance.AzurePpope,
tenant: "your-tenant-id"
}
};
// Azure China configuration
const chinaCloudAuth: NodeAuthOptions = {
clientId: "your-client-id",
azureCloudOptions: {
azureCloudInstance: AzureCloudInstance.AzureChina,
tenant: "your-tenant-id"
}
};
```
### System Configuration
System-level configuration for networking, logging, and operational settings.
```typescript { .api }
/**
* System configuration options
*/
type NodeSystemOptions = {
/** Logger configuration */
loggerOptions?: LoggerOptions;
/** Custom network client implementation */
networkClient?: INetworkModule;
/** Proxy URL for network requests */
proxyUrl?: string;
/** Custom HTTP agent options */
customAgentOptions?: http.AgentOptions | https.AgentOptions;
/** Disable internal request retries */
disableInternalRetries?: boolean;
};
/**
* Logger configuration options
*/
type LoggerOptions = {
/** Callback function for log messages */
loggerCallback?: (level: LogLevel, message: string, containsPii: boolean) => void;
/** Enable PII (personally identifiable information) logging */
piiLoggingEnabled?: boolean;
/** Minimum log level to output */
logLevel?: LogLevel;
/** Correlation ID for log correlation */
correlationId?: string;
};
/**
* Log levels
*/
enum LogLevel {
Error = 0,
Warning = 1,
Info = 2,
Verbose = 3,
Trace = 4
}
/**
* Network module interface for custom HTTP clients
*/
interface INetworkModule {
/** Send HTTP request */
sendGetRequestAsync<T>(
url: string,
options?: NetworkRequestOptions,
cancellationToken?: number
): Promise<NetworkResponse<T>>;
/** Send HTTP POST request */
sendPostRequestAsync<T>(
url: string,
options?: NetworkRequestOptions
): Promise<NetworkResponse<T>>;
}
/**
* Network request options
*/
type NetworkRequestOptions = {
/** Request headers */
headers?: Record<string, string>;
/** Request body */
body?: string;
/** Request timeout in milliseconds */
timeout?: number;
/** Proxy URL */
proxyUrl?: string;
};
/**
* Network response structure
*/
type NetworkResponse<T> = {
/** Response headers */
headers: Record<string, string>;
/** Response body */
body: T;
/** HTTP status code */
status: number;
};
```
**Usage Examples:**
```typescript
import https from "https";
// Custom logger configuration
const systemWithLogger: NodeSystemOptions = {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (!containsPii) {
const timestamp = new Date().toISOString();
const levelName = LogLevel[level];
console.log(`[${timestamp}] ${levelName}: ${message}`);
}
},
logLevel: LogLevel.Verbose,
piiLoggingEnabled: false
}
};
// Proxy configuration
const systemWithProxy: NodeSystemOptions = {
proxyUrl: "http://proxy.company.com:8080",
customAgentOptions: {
timeout: 30000,
keepAlive: true
}
};
// Custom HTTPS agent with certificate validation
const systemWithCustomAgent: NodeSystemOptions = {
customAgentOptions: {
ca: [fs.readFileSync("./ca-cert.pem")],
rejectUnauthorized: true,
timeout: 10000
} as https.AgentOptions
};
// Disable retries for testing
const systemWithoutRetries: NodeSystemOptions = {
disableInternalRetries: true,
loggerOptions: {
logLevel: LogLevel.Trace,
loggerCallback: (level, message) => console.log(message)
}
};
```
### Cache Configuration
Configuration options for token caching behavior.
```typescript { .api }
/**
* Cache configuration options
*/
type CacheOptions = {
/** Cache plugin for custom storage backends */
cachePlugin?: ICachePlugin;
/**
* @deprecated claims-based-caching functionality will be removed in the next version
*/
claimsBasedCachingEnabled?: boolean;
};
/**
* Cache plugin interface
*/
interface ICachePlugin {
/** Called before cache access - load cache data */
beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void>;
/** Called after cache access - persist cache data if changed */
afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void>;
}
```
**Usage Example:**
```typescript
// File-based cache plugin
class FileCachePlugin implements ICachePlugin {
private cacheFilePath: string;
constructor(cacheFilePath: string) {
this.cacheFilePath = cacheFilePath;
}
async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
try {
const cacheData = await fs.readFile(this.cacheFilePath, "utf8");
await cacheContext.tokenCache.deserialize(cacheData);
} catch (error) {
// Cache file doesn't exist - start with empty cache
}
}
async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
if (cacheContext.hasChanged) {
const serializedCache = await cacheContext.tokenCache.serialize();
await fs.writeFile(this.cacheFilePath, serializedCache);
}
}
}
const cacheConfig: CacheOptions = {
cachePlugin: new FileCachePlugin("./token-cache.json")
};
```
### Broker Configuration
Configuration for native broker integration (Windows, macOS, Linux).
```typescript { .api }
/**
* Broker configuration options
* Note: These options are only available for PublicClientApplications using Authorization Code Flow
*/
type BrokerOptions = {
/** Native broker plugin implementation */
nativeBrokerPlugin?: INativeBrokerPlugin;
};
/**
* Native broker plugin interface
*/
interface INativeBrokerPlugin {
/** Check if broker is available on current platform */
isBrokerAvailable(): Promise<boolean>;
/** Acquire token using native broker */
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult>;
/** Acquire token interactively using native broker */
acquireTokenInteractive(request: InteractiveRequest): Promise<AuthenticationResult>;
/** Get accounts from native broker */
getAllAccounts(): Promise<AccountInfo[]>;
/** Remove account from native broker */
removeAccount(account: AccountInfo): Promise<void>;
}
```
**Usage Example:**
```typescript
// Broker configuration (requires msal-node-extensions package)
import { NativeBrokerPlugin } from "msal-node-extensions";
const brokerConfig: BrokerOptions = {
nativeBrokerPlugin: new NativeBrokerPlugin()
};
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id"
},
broker: brokerConfig
});
```
### Telemetry Configuration
Configuration for application telemetry and usage analytics.
```typescript { .api }
/**
* Telemetry configuration options
*/
type NodeTelemetryOptions = {
/** Application telemetry information */
application?: ApplicationTelemetry;
};
/**
* Application telemetry information
*/
type ApplicationTelemetry = {
/** Application name */
appName: string;
/** Application version */
appVersion: string;
};
```
**Usage Example:**
```typescript
const telemetryConfig: NodeTelemetryOptions = {
application: {
appName: "MyNodeApp",
appVersion: "2.1.0"
}
};
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id"
},
telemetry: telemetryConfig
});
```
### Managed Identity Configuration
Specialized configuration for Azure Managed Identity applications.
```typescript { .api }
/**
* Configuration for managed identity applications
*/
type ManagedIdentityConfiguration = {
/** Client capabilities for conditional access */
clientCapabilities?: Array<string>;
/** Parameters for user-assigned managed identity */
managedIdentityIdParams?: ManagedIdentityIdParams;
/** System configuration options */
system?: NodeSystemOptions;
};
/**
* Parameters for user-assigned managed identity
*/
type ManagedIdentityIdParams = {
/** Client ID of the user-assigned managed identity */
userAssignedClientId?: string;
/** Resource ID of the user-assigned managed identity */
userAssignedResourceId?: string;
/** Object ID of the user-assigned managed identity */
userAssignedObjectId?: string;
};
```
**Usage Example:**
```typescript
import { ManagedIdentityApplication } from "@azure/msal-node";
// System-assigned managed identity
const systemAssignedConfig: ManagedIdentityConfiguration = {};
// User-assigned managed identity by client ID
const userAssignedConfig: ManagedIdentityConfiguration = {
managedIdentityIdParams: {
userAssignedClientId: "12345678-1234-1234-1234-123456789012"
},
clientCapabilities: ["CP1"], // Conditional access capability
system: {
loggerOptions: {
logLevel: LogLevel.Info,
loggerCallback: (level, message) => console.log(message)
}
}
};
const mia = new ManagedIdentityApplication(userAssignedConfig);
```
## Configuration Validation and Defaults
### Default Values
```typescript { .api }
/**
* Default configuration values applied automatically
*/
const DEFAULT_AUTH_OPTIONS: Required<NodeAuthOptions> = {
clientId: "", // Must be provided
authority: "https://login.microsoftonline.com/common",
clientSecret: "",
clientAssertion: "",
clientCertificate: {
thumbprint: "",
thumbprintSha256: "",
privateKey: "",
x5c: ""
},
knownAuthorities: [],
cloudDiscoveryMetadata: "",
authorityMetadata: "",
clientCapabilities: [],
protocolMode: ProtocolMode.AAD,
azureCloudOptions: {
azureCloudInstance: AzureCloudInstance.None,
tenant: ""
},
skipAuthorityMetadataCache: false,
encodeExtraQueryParams: false
};
const DEFAULT_SYSTEM_OPTIONS: Required<NodeSystemOptions> = {
loggerOptions: {
loggerCallback: () => {}, // No-op by default
piiLoggingEnabled: false,
logLevel: LogLevel.Info
},
networkClient: new HttpClient(),
proxyUrl: "",
customAgentOptions: {},
disableInternalRetries: false
};
```
### Configuration Validation
```typescript { .api }
/**
* Configuration validation function (internal)
*/
function buildAppConfiguration(config: Configuration): NodeConfiguration;
/**
* Validation rules applied during configuration
*/
type ConfigurationValidation = {
/** Client ID is required and must be valid GUID */
clientIdRequired: boolean;
/** Certificate thumbprint validation (SHA-1 or SHA-256 required) */
certificateThumbprintRequired: boolean;
/** Known authorities format validation */
knownAuthoritiesFormat: boolean;
/** Protocol mode compatibility */
protocolModeCompatibility: boolean;
};
```
**Usage Example:**
```typescript
// Configuration with validation
try {
const pca = new PublicClientApplication({
auth: {
clientId: "invalid-client-id" // This will cause validation error
}
});
} catch (error) {
console.error("Configuration validation failed:", error.message);
}
// Valid configuration
const validConfig: Configuration = {
auth: {
clientId: "12345678-1234-1234-1234-123456789012",
authority: "https://login.microsoftonline.com/common",
clientCapabilities: ["CP1", "CP2"]
},
system: {
loggerOptions: {
logLevel: LogLevel.Warning,
loggerCallback: (level, message, containsPii) => {
if (!containsPii && level <= LogLevel.Warning) {
console.warn(message);
}
}
}
}
};
const pca = new PublicClientApplication(validConfig);
```
## Environment-Specific Configurations
### Development Configuration
```typescript
const developmentConfig: Configuration = {
auth: {
clientId: "dev-client-id",
authority: "https://login.microsoftonline.com/common"
},
system: {
loggerOptions: {
logLevel: LogLevel.Verbose,
loggerCallback: (level, message) => console.log(message),
piiLoggingEnabled: false
},
disableInternalRetries: false
}
};
```
### Production Configuration
```typescript
const productionConfig: Configuration = {
auth: {
clientId: process.env.MSAL_CLIENT_ID!,
clientSecret: process.env.MSAL_CLIENT_SECRET,
authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`
},
system: {
loggerOptions: {
logLevel: LogLevel.Error,
loggerCallback: (level, message, containsPii) => {
if (!containsPii) {
// Log to production logging system
logger.log(level, message);
}
}
},
proxyUrl: process.env.HTTPS_PROXY,
disableInternalRetries: false
},
cache: {
cachePlugin: new ProductionCachePlugin()
}
};
```
### Testing Configuration
```typescript
const testConfig: Configuration = {
auth: {
clientId: "test-client-id",
authority: "https://login.microsoftonline.com/common",
skipAuthorityMetadataCache: true
},
system: {
disableInternalRetries: true,
loggerOptions: {
logLevel: LogLevel.Error,
loggerCallback: () => {} // Silent in tests
}
}
};
```

View file

@ -0,0 +1,653 @@
# Error Handling
MSAL Node provides comprehensive error handling with specific error classes, detailed error codes, and structured error information. The error system helps developers diagnose authentication issues and implement appropriate error recovery strategies.
## Capabilities
### Error Class Hierarchy
MSAL Node uses a hierarchical error system with specific error classes for different scenarios.
```typescript { .api }
/**
* Base authentication error class (from @azure/msal-common)
*/
class AuthError extends Error {
/** Error code identifier */
errorCode: string;
/** Detailed error message */
errorMessage: string;
/** Sub-error code for additional context */
subError: string;
/** Correlation ID for tracking requests */
correlationId: string;
constructor(errorCode: string, errorMessage?: string, suberror?: string);
/** Create error message with error codes */
createPostRequestFailed(endpoint: string, serverErrorMsg: string): string;
/** Create error message for GET request failures */
createGetRequestFailed(endpoint: string, serverErrorMsg: string): string;
}
/**
* Client-side authentication errors (from @azure/msal-common)
*/
class ClientAuthError extends AuthError {
constructor(errorCode: string, errorMessage?: string);
}
/**
* Client configuration errors (from @azure/msal-common)
*/
class ClientConfigurationError extends AuthError {
constructor(errorCode: string, errorMessage?: string);
}
/**
* Server-side authentication errors (from @azure/msal-common)
*/
class ServerError extends AuthError {
constructor(errorCode: string, errorMessage?: string, subError?: string);
}
/**
* Interaction required errors (from @azure/msal-common)
*/
class InteractionRequiredAuthError extends AuthError {
constructor(errorCode: string, errorMessage?: string, subError?: string);
}
```
### Node-Specific Errors
MSAL Node provides additional error classes for Node.js-specific scenarios.
```typescript { .api }
/**
* Node.js specific authentication errors
*/
class NodeAuthError extends AuthError {
constructor(errorCode: string, errorMessage?: string);
/** Create error for invalid loopback server address type */
static createInvalidLoopbackAddressTypeError(): NodeAuthError;
/** Create error when unable to load redirect URL */
static createUnableToLoadRedirectUrlError(): NodeAuthError;
/** Create error when no auth code is found in response */
static createNoAuthCodeInResponseError(): NodeAuthError;
/** Create error when no loopback server exists */
static createNoLoopbackServerExistsError(): NodeAuthError;
/** Create error when loopback server already exists */
static createLoopbackServerAlreadyExistsError(): NodeAuthError;
/** Create error for loopback server timeout */
static createLoopbackServerTimeoutError(): NodeAuthError;
/** Create error for invalid state parameter */
static createStateNotFoundError(): NodeAuthError;
/** Create error when client certificate thumbprint is missing */
static createThumbprintMissingError(): NodeAuthError;
/** Create error when redirect URI is not supported */
static createRedirectUriNotSupportedError(): NodeAuthError;
}
/**
* Managed Identity specific errors
*/
class ManagedIdentityError extends AuthError {
constructor(errorCode: string);
}
/**
* Helper function to create managed identity errors
*/
function createManagedIdentityError(errorCode: string): ManagedIdentityError;
```
### Error Codes
Comprehensive error codes for different error scenarios.
```typescript { .api }
/**
* Base authentication error codes
*/
const AuthErrorCodes = {
/** Unexpected error occurred */
unexpectedError: "unexpected_error",
/** POST request failed */
postRequestFailed: "post_request_failed",
/** GET request failed */
get_request_failed: "get_request_failed",
/** Network request failed */
networkError: "network_error",
/** Request timeout */
requestTimeout: "request_timeout",
/** Invalid JSON in response */
jsonParseError: "json_parse_error",
/** Invalid authority */
invalidAuthority: "invalid_authority"
} as const;
/**
* Client authentication error codes
*/
const ClientAuthErrorCodes = {
/** Multiple matching accounts found */
multipleMatchingAccounts: "multiple_matching_accounts",
/** Multiple matching tokens found */
multipleMatchingTokens: "multiple_matching_tokens",
/** No matching account found */
noAccountFound: "no_account_found",
/** No matching token found */
noTokenFound: "no_token_found",
/** Token request cannot be made */
tokenRequestCannotBeMade: "token_request_cannot_be_made",
/** Silent token request failed */
silentTokenRequestFailure: "silent_token_request_failure"
} as const;
/**
* Client configuration error codes
*/
const ClientConfigurationErrorCodes = {
/** Invalid client ID */
invalidClientId: "invalid_client_id",
/** Invalid authority */
invalidAuthority: "invalid_authority",
/** Invalid redirect URI */
invalidRedirectUri: "invalid_redirect_uri",
/** Invalid request */
invalidRequest: "invalid_request",
/** Invalid scope */
invalidScope: "invalid_scope",
/** Invalid claims */
invalidClaims: "invalid_claims"
} as const;
/**
* Interaction required error codes
*/
const InteractionRequiredAuthErrorCodes = {
/** User interaction required */
interactionRequired: "interaction_required",
/** User consent required */
consentRequired: "consent_required",
/** User login required */
loginRequired: "login_required",
/** Account selection required */
accountSelectionRequired: "account_selection_required",
/** Basic action required */
basicActionRequired: "basic_action_required",
/** Additional action required */
additionalActionRequired: "additional_action_required"
} as const;
/**
* Node-specific error codes
*/
const NodeAuthErrorCodes = {
/** Invalid loopback server address type */
invalidLoopbackAddressType: "invalid_loopback_server_address_type",
/** Unable to load redirect URL */
unableToLoadRedirectUrl: "unable_to_load_redirectUrl",
/** No auth code in response */
noAuthCodeInResponse: "no_auth_code_in_response",
/** No loopback server exists */
noLoopbackServerExists: "no_loopback_server_exists",
/** Loopback server already exists */
loopbackServerAlreadyExists: "loopback_server_already_exists",
/** Loopback server timeout */
loopbackServerTimeout: "loopback_server_timeout",
/** State parameter not found */
stateNotFound: "state_not_found",
/** Thumbprint missing from client certificate */
thumbprintMissing: "thumbprint_missing_from_client_certificate",
/** Redirect URI not supported */
redirectUriNotSupported: "redirect_uri_not_supported"
} as const;
/**
* Managed Identity error codes
*/
const ManagedIdentityErrorCodes = {
/** Invalid file extension */
invalidFileExtension: "invalid_file_extension",
/** Invalid file path */
invalidFilePath: "invalid_file_path",
/** Invalid managed identity ID type */
invalidManagedIdentityIdType: "invalid_managed_identity_id_type",
/** Invalid secret */
invalidSecret: "invalid_secret",
/** Missing client ID */
missingId: "missing_client_id",
/** Network unavailable */
networkUnavailable: "network_unavailable",
/** Platform not supported */
platformNotSupported: "platform_not_supported",
/** Unable to create Azure Arc */
unableToCreateAzureArc: "unable_to_create_azure_arc",
/** Unable to create Cloud Shell */
unableToCreateCloudShell: "unable_to_create_cloud_shell",
/** Unable to create source */
unableToCreateSource: "unable_to_create_source",
/** Unable to read secret file */
unableToReadSecretFile: "unable_to_read_secret_file",
/** URL parse error */
urlParseError: "url_parse_error",
/** User assigned not available at runtime */
userAssignedNotAvailableAtRuntime: "user_assigned_not_available_at_runtime",
/** WWW-Authenticate header missing */
wwwAuthenticateHeaderMissing: "www_authenticate_header_missing",
/** WWW-Authenticate header unsupported format */
wwwAuthenticateHeaderUnsupportedFormat: "www_authenticate_header_unsupported_format",
/** Environment variable URL malformed - Azure Pod Identity Authority Host */
azurePodIdentityAuthorityHostUrlMalformed: "azure_pod_identity_authority_host_url_malformed",
/** Environment variable URL malformed - Identity Endpoint */
identityEndpointUrlMalformed: "identity_endpoint_url_malformed",
/** Environment variable URL malformed - IMDS Endpoint */
imdsEndpointUrlMalformed: "imds_endpoint_url_malformed",
/** Environment variable URL malformed - MSI Endpoint */
msiEndpointUrlMalformed: "msi_endpoint_url_malformed"
} as const;
```
### Error Handling Patterns
Common error handling patterns and best practices.
```typescript { .api }
/**
* Error handling utility functions
*/
type ErrorHandlingUtils = {
/** Check if error is retryable */
isRetryableError(error: AuthError): boolean;
/** Check if error requires user interaction */
requiresInteraction(error: AuthError): boolean;
/** Extract user-friendly error message */
getUserFriendlyMessage(error: AuthError): string;
/** Check if error is due to network issues */
isNetworkError(error: AuthError): boolean;
};
```
**Usage Examples:**
```typescript
import {
AuthError,
ClientAuthError,
InteractionRequiredAuthError,
NodeAuthError,
ManagedIdentityError
} from "@azure/msal-node";
// Basic error handling
try {
const response = await pca.acquireTokenSilent(silentRequest);
} catch (error) {
if (error instanceof AuthError) {
console.error("Authentication error:", error.errorCode, error.errorMessage);
console.error("Correlation ID:", error.correlationId);
} else {
console.error("Unexpected error:", error);
}
}
// Comprehensive error handling
async function handleAuthenticationError(error: unknown): Promise<void> {
if (error instanceof InteractionRequiredAuthError) {
console.log("User interaction required, falling back to interactive flow");
// Implement interactive flow fallback
} else if (error instanceof ClientAuthError) {
switch (error.errorCode) {
case "no_account_found":
console.log("No cached accounts found, prompting for login");
break;
case "multiple_matching_accounts":
console.log("Multiple accounts found, prompting for account selection");
break;
default:
console.error("Client authentication error:", error.errorMessage);
}
} else if (error instanceof NodeAuthError) {
switch (error.errorCode) {
case "loopback_server_timeout":
console.error("Interactive authentication timed out");
break;
case "state_not_found":
console.error("Invalid state parameter - possible CSRF attack");
break;
default:
console.error("Node authentication error:", error.errorMessage);
}
} else if (error instanceof ManagedIdentityError) {
switch (error.errorCode) {
case "platform_not_supported":
console.error("Managed Identity not available on this platform");
break;
case "network_request_failed":
console.error("Failed to connect to managed identity endpoint");
break;
default:
console.error("Managed Identity error:", error.errorMessage);
}
} else if (error instanceof AuthError) {
console.error("General authentication error:", error.errorCode, error.errorMessage);
} else {
console.error("Unexpected error:", error);
}
}
```
### Silent Flow Error Handling
Specific error handling for silent token acquisition scenarios.
```typescript { .api }
/**
* Silent flow error handling pattern
*/
async function acquireTokenWithFallback(
silentRequest: SilentFlowRequest,
interactiveRequest: InteractiveRequest
): Promise<AuthenticationResult> {
try {
// Try silent acquisition first
return await pca.acquireTokenSilent(silentRequest);
} catch (error) {
if (error instanceof InteractionRequiredAuthError) {
// Silent acquisition failed, user interaction required
console.log("Silent acquisition failed, using interactive flow");
return await pca.acquireTokenInteractive(interactiveRequest);
} else if (error instanceof ClientAuthError && error.errorCode === "no_account_found") {
// No cached accounts, need to authenticate
console.log("No cached accounts found, using interactive flow");
return await pca.acquireTokenInteractive(interactiveRequest);
} else {
// Other errors should be handled or re-thrown
throw error;
}
}
}
```
### Network Error Handling
Handling network-related errors and implementing retry logic.
```typescript { .api }
/**
* Network error handling with retry logic
*/
async function acquireTokenWithRetry(
request: ClientCredentialRequest,
maxRetries: number = 3
): Promise<AuthenticationResult | null> {
let lastError: AuthError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await cca.acquireTokenByClientCredential(request);
} catch (error) {
lastError = error as AuthError;
if (error instanceof AuthError) {
// Check if error is retryable
if (isRetryableError(error) && attempt < maxRetries) {
const delayMs = Math.pow(2, attempt) * 1000; // Exponential backoff
console.log(`Attempt ${attempt} failed, retrying in ${delayMs}ms`);
await new Promise(resolve => setTimeout(resolve, delayMs));
continue;
}
}
// Non-retryable error or max retries reached
throw error;
}
}
throw lastError;
}
function isRetryableError(error: AuthError): boolean {
const retryableErrors = [
"network_error",
"request_timeout",
"post_request_failed",
"get_request_failed"
];
return retryableErrors.includes(error.errorCode);
}
```
### Device Code Flow Error Handling
Specific error handling for device code flow scenarios.
```typescript { .api }
/**
* Device code flow error handling
*/
async function deviceCodeFlowWithErrorHandling(): Promise<AuthenticationResult | null> {
const deviceCodeRequest = {
scopes: ["user.read"],
deviceCodeCallback: (response) => {
console.log("Please visit:", response.verificationUri);
console.log("And enter code:", response.userCode);
}
};
try {
const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
return response;
} catch (error) {
if (error instanceof AuthError) {
switch (error.errorCode) {
case "authorization_pending":
console.log("User has not completed authentication yet");
break;
case "slow_down":
console.log("Polling too frequently, slowing down");
break;
case "expired_token":
console.log("Device code has expired, please try again");
break;
case "access_denied":
console.log("User denied authorization");
break;
default:
console.error("Device code flow error:", error.errorMessage);
}
}
throw error;
}
}
```
### Error Recovery Strategies
Implementation patterns for error recovery and user experience.
```typescript { .api }
/**
* Comprehensive error recovery strategy
*/
class AuthenticationService {
private pca: PublicClientApplication;
constructor(config: Configuration) {
this.pca = new PublicClientApplication(config);
}
async acquireToken(scopes: string[]): Promise<AuthenticationResult> {
const accounts = await this.pca.getAllAccounts();
if (accounts.length > 0) {
// Try silent acquisition first
try {
return await this.pca.acquireTokenSilent({
account: accounts[0],
scopes: scopes
});
} catch (error) {
return await this.handleSilentFlowError(error, scopes);
}
} else {
// No accounts cached, use interactive flow
return await this.acquireTokenInteractively(scopes);
}
}
private async handleSilentFlowError(
error: unknown,
scopes: string[]
): Promise<AuthenticationResult> {
if (error instanceof InteractionRequiredAuthError) {
// Token expired or consent required
return await this.acquireTokenInteractively(scopes);
} else if (error instanceof ClientAuthError) {
if (error.errorCode === "no_token_found") {
// No cached token, use interactive flow
return await this.acquireTokenInteractively(scopes);
}
} else if (error instanceof AuthError && this.isNetworkError(error)) {
// Network error, implement retry with backoff
await this.delay(1000);
return await this.acquireToken(scopes);
}
// Re-throw unhandled errors
throw error;
}
private async acquireTokenInteractively(scopes: string[]): Promise<AuthenticationResult> {
return await this.pca.acquireTokenInteractive({
scopes: scopes,
openBrowser: async (url: string) => {
const { open } = await import("open");
await open(url);
}
});
}
private isNetworkError(error: AuthError): boolean {
const networkErrorCodes = [
"network_error",
"request_timeout",
"post_request_failed",
"get_request_failed"
];
return networkErrorCodes.includes(error.errorCode);
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
```
### Logging and Monitoring
Error logging and monitoring best practices.
```typescript { .api }
/**
* Error logging and monitoring integration
*/
class ErrorLogger {
static logAuthError(error: AuthError, context: string): void {
const errorData = {
timestamp: new Date().toISOString(),
context: context,
errorCode: error.errorCode,
errorMessage: error.errorMessage,
correlationId: error.correlationId,
subError: error.subError,
stack: error.stack
};
// Log to monitoring system
console.error("Authentication Error:", JSON.stringify(errorData, null, 2));
// Send to monitoring service (e.g., Application Insights, DataDog)
// this.sendToMonitoring(errorData);
}
static logNetworkError(error: AuthError, endpoint: string): void {
const networkErrorData = {
timestamp: new Date().toISOString(),
endpoint: endpoint,
errorCode: error.errorCode,
errorMessage: error.errorMessage,
correlationId: error.correlationId
};
console.error("Network Error:", JSON.stringify(networkErrorData, null, 2));
}
}
// Usage in error handling
try {
const response = await pca.acquireTokenSilent(request);
} catch (error) {
if (error instanceof AuthError) {
ErrorLogger.logAuthError(error, "silent_token_acquisition");
}
throw error;
}
```
## Common Error Scenarios
### Token Expiration
```typescript
// Handle expired tokens
if (error.errorCode === "token_expired" || error.errorCode === "interaction_required") {
// Token has expired, need to refresh or re-authenticate
console.log("Token expired, attempting refresh");
}
```
### Account Selection
```typescript
// Handle multiple accounts
if (error.errorCode === "multiple_matching_accounts") {
const accounts = await pca.getAllAccounts();
// Present account selection UI to user
console.log("Multiple accounts found:", accounts.map(a => a.username));
}
```
### Network Connectivity
```typescript
// Handle network issues
if (error.errorCode === "network_error" || error.errorCode === "request_timeout") {
console.log("Network connectivity issue, check internet connection");
// Implement offline mode or retry logic
}
```
### Configuration Issues
```typescript
// Handle configuration errors
if (error instanceof ClientConfigurationError) {
console.error("Configuration error - check client ID, authority, and other settings");
console.error("Error details:", error.errorMessage);
}
```
### Platform-Specific Issues
```typescript
// Handle managed identity platform issues
if (error instanceof ManagedIdentityError && error.errorCode === "platform_not_supported") {
console.error("Managed Identity not available - running outside Azure environment");
// Fallback to other authentication methods
}
```

View file

@ -0,0 +1,475 @@
# MSAL Node
Microsoft Authentication Library for Node (MSAL Node) is a comprehensive TypeScript library that enables Node.js applications to authenticate users and acquire tokens using the Microsoft Identity platform. It supports multiple OAuth 2.0 authentication flows for both public and confidential client applications, enabling authentication with Azure AD work and school accounts, Microsoft personal accounts, and social identity providers through Azure AD B2C.
## Package Information
- **Package Name**: @azure/msal-node
- **Package Type**: npm
- **Language**: TypeScript
- **Installation**: `npm install @azure/msal-node`
## Core Imports
```typescript
import {
PublicClientApplication,
ConfidentialClientApplication,
ManagedIdentityApplication,
type Configuration,
type AuthenticationResult
} from "@azure/msal-node";
```
For CommonJS:
```javascript
const {
PublicClientApplication,
ConfidentialClientApplication,
ManagedIdentityApplication
} = require("@azure/msal-node");
```
## Basic Usage
### Public Client (Desktop/Mobile Apps)
```typescript
import { PublicClientApplication } from "@azure/msal-node";
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/common"
}
});
// Authorization code flow
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: "http://localhost:3000/redirect"
};
const authUrl = await pca.getAuthCodeUrl(authCodeUrlParameters);
// Redirect user to authUrl, get code from callback
const tokenRequest = {
code: "authorization-code-from-callback",
scopes: ["user.read"],
redirectUri: "http://localhost:3000/redirect"
};
const response = await pca.acquireTokenByCode(tokenRequest);
console.log(response.accessToken);
```
### Confidential Client (Server Applications)
```typescript
import { ConfidentialClientApplication } from "@azure/msal-node";
const cca = new ConfidentialClientApplication({
auth: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
authority: "https://login.microsoftonline.com/your-tenant-id"
}
});
// Client credentials flow
const clientCredentialRequest = {
scopes: ["https://graph.microsoft.com/.default"]
};
const response = await cca.acquireTokenByClientCredential(clientCredentialRequest);
console.log(response.accessToken);
```
### Managed Identity (Azure Resources)
```typescript
import { ManagedIdentityApplication } from "@azure/msal-node";
const mia = new ManagedIdentityApplication({});
const tokenRequest = {
resource: "https://graph.microsoft.com/"
};
const response = await mia.acquireToken(tokenRequest);
console.log(response.accessToken);
```
## Architecture
MSAL Node is built around several key components:
- **Client Applications**: Different application types (Public, Confidential, Managed Identity) providing specialized authentication flows
- **Request/Response System**: Strongly-typed request objects for each OAuth 2.0 flow with comprehensive response handling
- **Token Cache**: In-memory and distributed cache system for storing tokens with serialization support
- **Configuration System**: Hierarchical configuration with authentication, system, cache, and telemetry options
- **Error Handling**: Comprehensive error classes with specific error codes for different scenarios
- **Cryptographic Provider**: Secure cryptographic operations including PKCE, JWT signatures, and certificate handling
## Capabilities
### Public Client Applications
Core functionality for applications that cannot securely store client secrets, including desktop apps, mobile apps, and single-page applications.
```typescript { .api }
interface IPublicClientApplication {
getAuthCodeUrl(request: AuthorizationUrlRequest): Promise<string>;
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
acquireTokenInteractive(request: InteractiveRequest): Promise<AuthenticationResult>;
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult>;
acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise<AuthenticationResult | null>;
acquireTokenByDeviceCode(request: DeviceCodeRequest): Promise<AuthenticationResult | null>;
acquireTokenByUsernamePassword(request: UsernamePasswordRequest): Promise<AuthenticationResult | null>;
getTokenCache(): TokenCache;
getLogger(): Logger;
setLogger(logger: Logger): void;
clearCache(): void;
getAllAccounts(): Promise<AccountInfo[]>;
signOut(request: SignOutRequest): Promise<void>;
}
class PublicClientApplication implements IPublicClientApplication {
constructor(configuration: Configuration);
}
```
[Public Client Applications](./public-client.md)
### Confidential Client Applications
Advanced functionality for applications that can securely store client secrets, including server applications, web APIs, and daemon applications.
```typescript { .api }
interface IConfidentialClientApplication {
getAuthCodeUrl(request: AuthorizationUrlRequest): Promise<string>;
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult | null>;
acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise<AuthenticationResult | null>;
acquireTokenByClientCredential(request: ClientCredentialRequest): Promise<AuthenticationResult | null>;
acquireTokenOnBehalfOf(request: OnBehalfOfRequest): Promise<AuthenticationResult | null>;
acquireTokenByUsernamePassword(request: UsernamePasswordRequest): Promise<AuthenticationResult | null>;
getTokenCache(): TokenCache;
getLogger(): Logger;
setLogger(logger: Logger): void;
clearCache(): void;
SetAppTokenProvider(provider: IAppTokenProvider): void;
}
class ConfidentialClientApplication implements IConfidentialClientApplication {
constructor(configuration: Configuration);
}
```
[Confidential Client Applications](./confidential-client.md)
### Managed Identity Applications
Azure Managed Identity authentication for applications running on Azure resources like VMs, App Service, and Function Apps.
```typescript { .api }
class ManagedIdentityApplication {
constructor(configuration?: ManagedIdentityConfiguration);
acquireToken(request: ManagedIdentityRequestParams): Promise<AuthenticationResult | null>;
getManagedIdentitySource(): ManagedIdentitySourceNames;
}
type ManagedIdentityConfiguration = {
clientCapabilities?: Array<string>;
managedIdentityIdParams?: ManagedIdentityIdParams;
system?: NodeSystemOptions;
};
type ManagedIdentityIdParams = {
userAssignedClientId?: string;
userAssignedResourceId?: string;
userAssignedObjectId?: string;
};
```
[Managed Identity Applications](./managed-identity.md)
### Authentication Flows
Complete set of OAuth 2.0 authentication flows with strongly-typed request objects and comprehensive configuration options.
```typescript { .api }
type AuthorizationUrlRequest = {
scopes: string[];
redirectUri: string;
prompt?: PromptValue;
account?: AccountInfo;
loginHint?: string;
domainHint?: string;
extraQueryParameters?: Record<string, string>;
};
type AuthorizationCodeRequest = {
scopes: string[];
redirectUri: string;
code: string;
state?: string;
authority?: string;
correlationId?: string;
claims?: string;
};
type ClientCredentialRequest = {
scopes: string[];
authority?: string;
clientAssertion?: string | (() => string);
skipCache?: boolean;
};
```
[Authentication Flows](./authentication-flows.md)
### Token Cache Management
Comprehensive token caching system with in-memory storage, serialization support, and distributed cache capabilities.
```typescript { .api }
interface ITokenCache {
getAllAccounts(): Promise<AccountInfo[]>;
getAccountByHomeId(homeAccountId: string): Promise<AccountInfo | null>;
getAccountByLocalId(localAccountId: string): Promise<AccountInfo | null>;
removeAccount(account: AccountInfo): Promise<void>;
}
class TokenCache implements ITokenCache, ISerializableTokenCache {
// Cache management methods
}
type CacheOptions = {
cachePlugin?: ICachePlugin;
claimsBasedCachingEnabled?: boolean;
};
```
[Token Cache Management](./token-cache.md)
### Configuration System
Hierarchical configuration system supporting authentication options, system settings, cache configuration, and telemetry.
```typescript { .api }
type Configuration = {
auth: NodeAuthOptions;
broker?: BrokerOptions;
cache?: CacheOptions;
system?: NodeSystemOptions;
telemetry?: NodeTelemetryOptions;
};
type NodeAuthOptions = {
clientId: string;
authority?: string;
clientSecret?: string;
clientAssertion?: string | ClientAssertionCallback;
clientCertificate?: {
thumbprintSha256?: string;
privateKey: string;
x5c?: string;
};
knownAuthorities?: Array<string>;
protocolMode?: ProtocolMode;
};
```
[Configuration System](./configuration.md)
### Error Handling
Comprehensive error handling with specific error classes and detailed error codes for different authentication scenarios.
```typescript { .api }
class NodeAuthError extends AuthError {
static createStateNotFoundError(): NodeAuthError;
static createLoopbackServerTimeoutError(): NodeAuthError;
}
class ManagedIdentityError extends AuthError {
// Managed Identity specific errors
}
type AuthErrorCodes = {
unexpectedError: string;
postRequestFailed: string;
getRequestFailed: string;
// Additional error codes...
};
```
[Error Handling](./error-handling.md)
### Specialized Client Classes
Low-level client classes for specific OAuth 2.0 flows, providing direct access to individual authentication methods.
```typescript { .api }
/**
* OAuth 2.0 client credential flow client
*/
class ClientCredentialClient extends BaseClient {
constructor(configuration: ClientConfiguration, appTokenProvider?: IAppTokenProvider);
acquireToken(request: CommonClientCredentialRequest): Promise<AuthenticationResult | null>;
}
/**
* OAuth 2.0 device code flow client
*/
class DeviceCodeClient extends BaseClient {
constructor(configuration: ClientConfiguration);
acquireToken(request: CommonDeviceCodeRequest): Promise<AuthenticationResult | null>;
}
/**
* OAuth 2.0 on-behalf-of flow client
*/
class OnBehalfOfClient extends BaseClient {
constructor(configuration: ClientConfiguration);
acquireToken(request: CommonOnBehalfOfRequest): Promise<AuthenticationResult | null>;
}
/**
* OAuth 2.0 username/password flow client
*/
class UsernamePasswordClient extends BaseClient {
constructor(configuration: ClientConfiguration);
acquireToken(request: CommonUsernamePasswordRequest): Promise<AuthenticationResult | null>;
}
/**
* Base client application class
*/
abstract class ClientApplication {
protected constructor(configuration: Configuration);
getTokenCache(): TokenCache;
getLogger(): Logger;
setLogger(logger: Logger): void;
clearCache(): void;
}
```
### Cryptographic Provider
Cryptographic operations for authentication flows including PKCE, JWT signatures, and certificate handling.
```typescript { .api }
/**
* Cryptographic provider for Node.js environments
*/
class CryptoProvider {
/** Generate UUID v4 */
createNewGuid(): string;
/** Generate PKCE code verifier */
generatePkceCodes(): PkceCodes;
/** Generate SHA256 hash */
sha256Digest(inputString: string): Promise<string>;
/** Sign JWT with private key */
signJwt(payload: object, privateKey: string): Promise<string>;
/** Verify JWT signature */
verifyJwt(token: string, publicKey: string): Promise<object>;
}
type PkceCodes = {
verifier: string;
challenge: string;
};
```
### Client Assertion
Certificate-based authentication support for confidential client applications.
```typescript { .api }
/**
* Client assertion for certificate-based authentication
*/
class ClientAssertion {
constructor(assertion: string);
getJwt(): string;
static fromCertificate(privateKey: string, thumbprint: string, audience: string): ClientAssertion;
}
```
## Common Types
### Authentication Results
```typescript { .api }
type AuthenticationResult = {
authority: string;
uniqueId: string;
tenantId: string;
scopes: string[];
account: AccountInfo | null;
idToken: string;
idTokenClaims: IdTokenClaims;
accessToken: string;
fromCache: boolean;
expiresOn: Date | null;
correlationId: string;
requestId?: string;
};
type AccountInfo = {
homeAccountId: string;
environment: string;
tenantId: string;
username: string;
localAccountId: string;
name?: string;
idTokenClaims?: IdTokenClaims;
};
```
### Protocol Types
```typescript { .api }
enum PromptValue {
LOGIN = "login",
SELECT_ACCOUNT = "select_account",
CONSENT = "consent",
NONE = "none"
}
enum ResponseMode {
QUERY = "query",
FRAGMENT = "fragment",
FORM_POST = "form_post"
}
enum ProtocolMode {
AAD = "AAD",
OIDC = "OIDC"
}
enum AzureCloudInstance {
None = "",
AzurePublic = "https://login.microsoftonline.com",
AzurePpope = "https://login.microsoftonline.us",
AzureChina = "https://login.chinacloudapi.cn",
AzureGermany = "https://login.microsoftonline.de"
}
type ClientAssertionCallback = () => string;
const ManagedIdentitySourceNames = {
IMDS: "IMDS",
APP_SERVICE: "APP_SERVICE",
AZURE_ARC: "AZURE_ARC",
CLOUD_SHELL: "CLOUD_SHELL",
SERVICE_FABRIC: "SERVICE_FABRIC",
MACHINE_LEARNING: "MACHINE_LEARNING",
DEFAULT_TO_IMDS: "DEFAULT_TO_IMDS"
} as const;
type ManagedIdentitySourceNames = typeof ManagedIdentitySourceNames[keyof typeof ManagedIdentitySourceNames];
```

View file

@ -0,0 +1,438 @@
# Managed Identity Applications
Managed Identity Applications provide authentication for applications running on Azure resources like Virtual Machines, App Service, Function Apps, and other Azure services. This eliminates the need to store credentials in code by leveraging Azure's managed identity infrastructure.
## Capabilities
### ManagedIdentityApplication Class
Main class for Azure Managed Identity authentication.
```typescript { .api }
/**
* Managed Identity application class for Azure resources
* Automatically detects and uses the appropriate managed identity source
*/
class ManagedIdentityApplication {
constructor(configuration: ManagedIdentityConfiguration);
/**
* Acquires a token using Azure Managed Identity
* Automatically detects the managed identity source (IMDS, App Service, etc.)
*/
acquireToken(request: ManagedIdentityRequestParams): Promise<AuthenticationResult | null>;
}
/**
* Configuration for managed identity applications
*/
type ManagedIdentityConfiguration = {
/** Client capabilities for conditional access */
clientCapabilities?: Array<string>;
/** Parameters for user-assigned managed identity */
managedIdentityIdParams?: ManagedIdentityIdParams;
/** System configuration options */
system?: NodeSystemOptions;
};
/**
* Parameters for user-assigned managed identity
*/
type ManagedIdentityIdParams = {
/** Client ID of the user-assigned managed identity */
userAssignedClientId?: string;
/** Resource ID of the user-assigned managed identity */
userAssignedResourceId?: string;
/** Object ID of the user-assigned managed identity */
userAssignedObjectId?: string;
};
/**
* Request parameters for managed identity token acquisition
*/
type ManagedIdentityRequestParams = {
/** Target resource URI for the token */
resource: string;
/** Claims for conditional access */
claims?: string;
/** Force refresh instead of using cached token */
forceRefresh?: boolean;
};
```
**Usage Example:**
```typescript
import { ManagedIdentityApplication } from "@azure/msal-node";
// System-assigned managed identity (default)
const mia = new ManagedIdentityApplication({});
const tokenRequest = {
resource: "https://graph.microsoft.com/"
};
try {
const response = await mia.acquireToken(tokenRequest);
if (response) {
console.log("Access token:", response.accessToken);
console.log("Token expires on:", response.expiresOn);
}
} catch (error) {
console.error("Managed identity authentication failed:", error);
}
```
### System-Assigned Managed Identity
Default configuration that uses the system-assigned managed identity of the Azure resource.
```typescript { .api }
/**
* Basic configuration for system-assigned managed identity
* No additional parameters needed - automatically uses the system-assigned identity
*/
type SystemAssignedConfig = ManagedIdentityConfiguration;
```
**Usage Example:**
```typescript
// System-assigned managed identity (simplest configuration)
const mia = new ManagedIdentityApplication({});
// Acquire token for Microsoft Graph
const graphToken = await mia.acquireToken({
resource: "https://graph.microsoft.com/"
});
// Acquire token for Azure Resource Manager
const armToken = await mia.acquireToken({
resource: "https://management.azure.com/"
});
// Acquire token for Key Vault
const keyVaultToken = await mia.acquireToken({
resource: "https://vault.azure.net/"
});
```
### User-Assigned Managed Identity
Configuration for using a specific user-assigned managed identity.
```typescript { .api }
/**
* Configuration for user-assigned managed identity
* Specify identity using one of: clientId, resourceId, or objectId
*/
type UserAssignedConfig = {
managedIdentityIdParams: ManagedIdentityIdParams;
system?: NodeSystemOptions;
};
```
**Usage Examples:**
```typescript
// User-assigned managed identity by client ID
const miaByClientId = new ManagedIdentityApplication({
managedIdentityIdParams: {
userAssignedClientId: "12345678-1234-1234-1234-123456789012"
}
});
// User-assigned managed identity by resource ID
const miaByResourceId = new ManagedIdentityApplication({
managedIdentityIdParams: {
userAssignedResourceId: "/subscriptions/sub-id/resourceGroups/rg-name/providers/Microsoft.ManagedIdentity/userAssignedIdentities/identity-name"
}
});
// User-assigned managed identity by object ID
const miaByObjectId = new ManagedIdentityApplication({
managedIdentityIdParams: {
userAssignedObjectId: "87654321-4321-4321-4321-210987654321"
}
});
const tokenRequest = {
resource: "https://graph.microsoft.com/"
};
const response = await miaByClientId.acquireToken(tokenRequest);
```
### Supported Azure Services
Managed Identity automatically detects and works with various Azure services:
```typescript { .api }
/**
* Managed Identity source names (for reference)
* These are automatically detected - no manual configuration needed
*/
const ManagedIdentitySourceNames = {
/** Azure Instance Metadata Service (VMs, VMSS) */
IMDS: "IMDS",
/** Azure App Service and Function Apps */
APP_SERVICE: "APP_SERVICE",
/** Azure Arc enabled servers */
AZURE_ARC: "AZURE_ARC",
/** Azure Cloud Shell */
CLOUD_SHELL: "CLOUD_SHELL",
/** Azure Service Fabric */
SERVICE_FABRIC: "SERVICE_FABRIC",
/** Azure Machine Learning */
MACHINE_LEARNING: "MACHINE_LEARNING",
/** Default fallback to IMDS */
DEFAULT_TO_IMDS: "DEFAULT_TO_IMDS"
} as const;
```
**Supported Azure Services:**
- **Azure Virtual Machines**: Uses IMDS endpoint
- **Azure App Service**: Uses environment variables and MSI endpoint
- **Azure Function Apps**: Uses environment variables and MSI endpoint
- **Azure Container Instances**: Uses IMDS endpoint
- **Azure Kubernetes Service**: Uses IMDS endpoint
- **Azure Service Fabric**: Uses Service Fabric endpoint
- **Azure Arc enabled servers**: Uses Arc-specific endpoint
- **Azure Cloud Shell**: Uses Cloud Shell environment
- **Azure Machine Learning**: Uses ML-specific endpoint
### Token Caching and Refresh
```typescript { .api }
/**
* Request with cache control options
*/
type ManagedIdentityRequestWithCaching = {
/** Target resource URI for the token */
resource: string;
/** Force refresh instead of using cached token */
forceRefresh?: boolean;
/** Claims for conditional access scenarios */
claims?: string;
};
```
**Usage Example:**
```typescript
const mia = new ManagedIdentityApplication({});
// Use cached token if available and valid
const cachedResponse = await mia.acquireToken({
resource: "https://graph.microsoft.com/",
forceRefresh: false
});
// Force refresh from managed identity endpoint
const freshResponse = await mia.acquireToken({
resource: "https://graph.microsoft.com/",
forceRefresh: true
});
// With conditional access claims
const claimsResponse = await mia.acquireToken({
resource: "https://graph.microsoft.com/",
claims: JSON.stringify({
"access_token": {
"acrs": {
"essential": true,
"values": ["urn:microsoft:req1"]
}
}
})
});
```
### Error Handling
```typescript { .api }
/**
* Managed Identity specific error class
*/
class ManagedIdentityError extends AuthError {
constructor(errorCode: string, errorMessage: string);
}
/**
* Common managed identity error codes
*/
const ManagedIdentityErrorCodes = {
/** Network request failed */
NETWORK_REQUEST_FAILED: "network_request_failed",
/** Invalid resource format */
INVALID_RESOURCE: "invalid_resource",
/** Managed identity not available on current platform */
PLATFORM_NOT_SUPPORTED: "platform_not_supported",
/** Environment variables malformed */
MALFORMED_ENVIRONMENT_VARIABLE: "malformed_environment_variable",
/** Timeout waiting for response */
REQUEST_TIMEOUT: "request_timeout",
/** File system access error */
FILE_NOT_FOUND: "file_not_found"
} as const;
```
**Usage Example:**
```typescript
import { ManagedIdentityError } from "@azure/msal-node";
try {
const response = await mia.acquireToken({
resource: "https://graph.microsoft.com/"
});
} catch (error) {
if (error instanceof ManagedIdentityError) {
switch (error.errorCode) {
case "platform_not_supported":
console.error("Managed Identity not supported on this platform");
break;
case "network_request_failed":
console.error("Network error accessing managed identity endpoint");
break;
case "invalid_resource":
console.error("Invalid resource URI provided");
break;
default:
console.error("Managed Identity error:", error.message);
}
} else {
console.error("Unexpected error:", error);
}
}
```
### Advanced Configuration
```typescript { .api }
/**
* Advanced managed identity configuration with system options
*/
type AdvancedManagedIdentityConfiguration = {
/** Client capabilities for conditional access */
clientCapabilities?: Array<string>;
/** User-assigned identity parameters */
managedIdentityIdParams?: ManagedIdentityIdParams;
/** System configuration */
system?: {
/** Logger configuration */
loggerOptions?: LoggerOptions;
/** Custom network client */
networkClient?: INetworkModule;
/** Proxy URL for network requests */
proxyUrl?: string;
/** Custom HTTP agent options */
customAgentOptions?: http.AgentOptions | https.AgentOptions;
/** Disable internal request retries */
disableInternalRetries?: boolean;
};
};
```
**Usage Example:**
```typescript
import { ManagedIdentityApplication, LogLevel } from "@azure/msal-node";
const mia = new ManagedIdentityApplication({
managedIdentityIdParams: {
userAssignedClientId: "12345678-1234-1234-1234-123456789012"
},
clientCapabilities: ["CP1"], // Conditional access capability
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (!containsPii) {
console.log(`[${level}] ${message}`);
}
},
logLevel: LogLevel.Verbose,
piiLoggingEnabled: false
},
proxyUrl: "http://proxy.company.com:8080",
disableInternalRetries: false
}
});
const response = await mia.acquireToken({
resource: "https://graph.microsoft.com/",
claims: JSON.stringify({
"access_token": {
"xms_cc": {
"values": ["CP1"]
}
}
})
});
```
## Common Use Cases
### Accessing Microsoft Graph
```typescript
const mia = new ManagedIdentityApplication({});
const graphToken = await mia.acquireToken({
resource: "https://graph.microsoft.com/"
});
// Use token to call Microsoft Graph
const response = await fetch("https://graph.microsoft.com/v1.0/me", {
headers: {
"Authorization": `Bearer ${graphToken.accessToken}`
}
});
```
### Accessing Azure Resource Manager
```typescript
const armToken = await mia.acquireToken({
resource: "https://management.azure.com/"
});
// Use token for Azure Resource Manager APIs
const subscriptions = await fetch("https://management.azure.com/subscriptions?api-version=2020-01-01", {
headers: {
"Authorization": `Bearer ${armToken.accessToken}`
}
});
```
### Accessing Azure Key Vault
```typescript
const keyVaultToken = await mia.acquireToken({
resource: "https://vault.azure.net/"
});
// Use token for Key Vault operations
const secret = await fetch("https://my-vault.vault.azure.net/secrets/my-secret?api-version=7.2", {
headers: {
"Authorization": `Bearer ${keyVaultToken.accessToken}`
}
});
```
### Accessing Azure Storage
```typescript
const storageToken = await mia.acquireToken({
resource: "https://storage.azure.com/"
});
// Use token for Azure Storage operations
const blobs = await fetch("https://myaccount.blob.core.windows.net/mycontainer?restype=container&comp=list", {
headers: {
"Authorization": `Bearer ${storageToken.accessToken}`,
"x-ms-version": "2020-04-08"
}
});
```

View file

@ -0,0 +1,421 @@
# Public Client Applications
Public Client Applications are designed for applications that cannot securely store client secrets, such as desktop applications, mobile apps, and single-page applications. These applications rely on user interaction for authentication and use flows like authorization code with PKCE, device code, and interactive authentication.
## Capabilities
### PublicClientApplication Class
Main class for creating public client applications.
```typescript { .api }
/**
* Public client application class implementing IPublicClientApplication interface
* Suitable for applications that cannot securely store client secrets
*/
class PublicClientApplication implements IPublicClientApplication {
constructor(configuration: Configuration);
/** Creates the URL of the authorization request */
getAuthCodeUrl(request: AuthorizationUrlRequest): Promise<string>;
/** Acquires a token by exchanging the authorization code received from the first step of OAuth 2.0 Authorization Code Flow */
acquireTokenByCode(request: AuthorizationCodeRequest): Promise<AuthenticationResult>;
/** Acquires a token interactively */
acquireTokenInteractive(request: InteractiveRequest): Promise<AuthenticationResult>;
/** Acquires a token silently when a user specifies the account the token is requested for */
acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult>;
/** Acquires a token by exchanging the refresh token provided for a new set of tokens */
acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise<AuthenticationResult | null>;
/** Acquires a token from the authority using OAuth2.0 device code flow */
acquireTokenByDeviceCode(request: DeviceCodeRequest): Promise<AuthenticationResult | null>;
/**
* Acquires tokens with password grant by exchanging client applications username and password for credentials
* @deprecated - Use a more secure flow instead
*/
acquireTokenByUsernamePassword(request: UsernamePasswordRequest): Promise<AuthenticationResult | null>;
/** Gets the token cache for the application */
getTokenCache(): TokenCache;
/** Returns the logger instance */
getLogger(): Logger;
/** Replaces the default logger set in configurations with new Logger with new configurations */
setLogger(logger: Logger): void;
/** Clear the cache */
clearCache(): void;
/** Gets all cached accounts */
getAllAccounts(): Promise<AccountInfo[]>;
/** Removes cache artifacts associated with the given account */
signOut(request: SignOutRequest): Promise<void>;
}
```
**Usage Example:**
```typescript
import { PublicClientApplication } from "@azure/msal-node";
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/common"
},
cache: {
cachePlugin: // optional cache plugin
},
system: {
loggerOptions: {
loggerCallback: (level, message) => {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: LogLevel.Info
}
}
});
```
### Authorization Code Flow
Standard OAuth 2.0 authorization code flow with PKCE for secure authentication.
```typescript { .api }
/**
* Request to generate authorization URL (first step of authorization code flow)
*/
type AuthorizationUrlRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server will redirect after user authorization */
redirectUri: string;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Account to use for authentication */
account?: AccountInfo;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** PKCE code challenge for security */
codeChallenge?: string;
/** PKCE code challenge method */
codeChallengeMethod?: string;
/** State parameter for CSRF protection */
state?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Authority URL to override default */
authority?: string;
};
/**
* Request to exchange authorization code for tokens (second step of authorization code flow)
*/
type AuthorizationCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** URI where the authorization server redirected after user authorization */
redirectUri: string;
/** Authorization code received from authorization server */
code: string;
/** State parameter for CSRF protection validation */
state?: string;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
/** Additional parameters for token request */
tokenQueryParameters?: Record<string, string>;
/** PKCE code verifier for security validation */
codeVerifier?: string;
};
```
**Usage Example:**
```typescript
// Step 1: Generate authorization URL
const authCodeUrlParameters = {
scopes: ["user.read", "mail.read"],
redirectUri: "http://localhost:3000/redirect",
prompt: "select_account" as PromptValue
};
const authUrl = await pca.getAuthCodeUrl(authCodeUrlParameters);
console.log("Navigate to:", authUrl);
// Step 2: Exchange authorization code for tokens
const tokenRequest = {
code: "authorization-code-from-callback", // Extract from callback URL
scopes: ["user.read", "mail.read"],
redirectUri: "http://localhost:3000/redirect"
};
try {
const response = await pca.acquireTokenByCode(tokenRequest);
console.log("Access token:", response.accessToken);
console.log("Account:", response.account);
} catch (error) {
console.error("Token acquisition failed:", error);
}
```
### Interactive Authentication
Browser-based interactive authentication flow for desktop applications.
```typescript { .api }
/**
* Request for interactive token acquisition with browser
*/
type InteractiveRequest = {
/** Function to open browser with authorization URL */
openBrowser: (url: string) => Promise<void>;
/** Array of scopes the application is requesting access to */
scopes?: string[];
/** HTML template for success page */
successTemplate?: string;
/** HTML template for error page */
errorTemplate?: string;
/** Window handle for desktop applications */
windowHandle?: Buffer;
/** Custom loopback client for handling redirects */
loopbackClient?: ILoopbackClient;
/** Additional query parameters for the authorization request */
extraQueryParameters?: Record<string, string>;
/** Prompt behavior for user interaction */
prompt?: PromptValue;
/** Login hint for pre-filling username */
loginHint?: string;
/** Domain hint for federated authentication */
domainHint?: string;
/** Account to use for authentication */
account?: AccountInfo;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
import { open } from "open"; // For opening browser
const interactiveRequest = {
scopes: ["user.read"],
openBrowser: async (url: string) => {
await open(url);
},
successTemplate: "<h1>Authentication successful! You can close this window.</h1>",
errorTemplate: "<h1>Authentication failed. Please try again.</h1>"
};
try {
const response = await pca.acquireTokenInteractive(interactiveRequest);
console.log("Access token:", response.accessToken);
} catch (error) {
console.error("Interactive authentication failed:", error);
}
```
### Device Code Flow
OAuth 2.0 device code flow for devices with limited input capabilities.
```typescript { .api }
/**
* Request for device code flow authentication
*/
type DeviceCodeRequest = {
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Callback function to display device code to user */
deviceCodeCallback: (response: DeviceCodeResponse) => void;
/** Flag to cancel the device code flow polling */
cancel?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Additional query parameters for the device authorization request */
extraQueryParameters?: Record<string, string>;
/** Claims request for additional token claims */
claims?: string;
};
/**
* Response containing device code information for user
*/
type DeviceCodeResponse = {
/** Device code for internal polling */
deviceCode: string;
/** User-friendly code to display to user */
userCode: string;
/** URL where user should navigate to enter the user code */
verificationUri: string;
/** Complete verification URL including user code (optional) */
verificationUriComplete?: string;
/** Number of seconds the device code is valid */
expiresIn: number;
/** Minimum number of seconds to wait between polling requests */
interval: number;
/** Message to display to user */
message: string;
};
```
**Usage Example:**
```typescript
const deviceCodeRequest = {
scopes: ["user.read"],
deviceCodeCallback: (response) => {
console.log("Please visit:", response.verificationUri);
console.log("And enter code:", response.userCode);
console.log("Or visit:", response.verificationUriComplete);
}
};
try {
const response = await pca.acquireTokenByDeviceCode(deviceCodeRequest);
if (response) {
console.log("Access token:", response.accessToken);
}
} catch (error) {
console.error("Device code authentication failed:", error);
}
```
### Silent Token Acquisition
Silent token acquisition from cache or using refresh tokens.
```typescript { .api }
/**
* Request for silent token acquisition from cache
*/
type SilentFlowRequest = {
/** Account to acquire token for */
account: AccountInfo;
/** Array of scopes the application is requesting access to */
scopes: string[];
/** Force refresh from authority instead of using cache */
forceRefresh?: boolean;
/** Authority URL to override default */
authority?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
/** Claims request for additional token claims */
claims?: string;
};
```
**Usage Example:**
```typescript
// Get accounts from cache
const accounts = await pca.getAllAccounts();
if (accounts.length > 0) {
const silentRequest = {
account: accounts[0],
scopes: ["user.read"],
forceRefresh: false // Use cache if available
};
try {
const response = await pca.acquireTokenSilent(silentRequest);
console.log("Access token from cache:", response.accessToken);
} catch (error) {
// Silent acquisition failed, fall back to interactive
console.log("Silent acquisition failed, using interactive flow");
}
}
```
### Sign Out
Sign out functionality to clear user session and cache.
```typescript { .api }
/**
* Request to sign out user
*/
type SignOutRequest = {
/** Account to sign out */
account: AccountInfo;
/** Post logout redirect URI */
postLogoutRedirectUri?: string;
/** Correlation ID for tracking requests */
correlationId?: string;
};
```
**Usage Example:**
```typescript
const accounts = await pca.getAllAccounts();
if (accounts.length > 0) {
const signOutRequest = {
account: accounts[0],
postLogoutRedirectUri: "http://localhost:3000/signedout"
};
try {
await pca.signOut(signOutRequest);
console.log("User signed out successfully");
} catch (error) {
console.error("Sign out failed:", error);
}
}
```
## Additional Features
### Account Management
```typescript { .api }
/** Get all cached accounts */
getAllAccounts(): Promise<AccountInfo[]>;
/** Clear all cache entries */
clearCache(): void;
```
### Logger Configuration
```typescript { .api }
/** Get current logger instance */
getLogger(): Logger;
/** Set new logger instance */
setLogger(logger: Logger): void;
```
### Token Cache Access
```typescript { .api }
/** Get token cache instance for advanced operations */
getTokenCache(): TokenCache;
```

View file

@ -0,0 +1,681 @@
# Token Cache Management
MSAL Node provides a comprehensive token caching system that stores authentication artifacts in memory with support for serialization, distributed caching, and custom cache plugins. The cache system improves performance by avoiding unnecessary network requests and enables silent token acquisition.
## Capabilities
### TokenCache Class
Main token cache implementation providing in-memory storage with serialization support.
```typescript { .api }
/**
* Token cache interface for basic cache operations
*/
interface ITokenCache {
/** Get all cached accounts */
getAllAccounts(): Promise<AccountInfo[]>;
/** Get account by home account ID */
getAccountByHomeId(homeAccountId: string): Promise<AccountInfo | null>;
/** Get account by local account ID */
getAccountByLocalId(localAccountId: string): Promise<AccountInfo | null>;
/** Remove account and associated tokens from cache */
removeAccount(account: AccountInfo): Promise<void>;
}
/**
* Main token cache class with serialization support
*/
class TokenCache implements ITokenCache, ISerializableTokenCache {
/** Get all cached accounts */
getAllAccounts(): Promise<AccountInfo[]>;
/** Get account by home account ID */
getAccountByHomeId(homeAccountId: string): Promise<AccountInfo | null>;
/** Get account by local account ID */
getAccountByLocalId(localAccountId: string): Promise<AccountInfo | null>;
/** Remove account and associated tokens from cache */
removeAccount(account: AccountInfo): Promise<void>;
/** Serialize cache to JSON string */
serialize(): Promise<string>;
/** Deserialize cache from JSON string */
deserialize(cache: string): Promise<void>;
/** Get all cached access tokens */
getAllAccessTokens(): Promise<AccessTokenEntity[]>;
/** Get all cached refresh tokens */
getAllRefreshTokens(): Promise<RefreshTokenEntity[]>;
/** Get all cached ID tokens */
getAllIdTokens(): Promise<IdTokenEntity[]>;
/** Clear all cache entries */
clear(): Promise<void>;
}
```
**Usage Example:**
```typescript
import { PublicClientApplication } from "@azure/msal-node";
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id"
}
});
// Get token cache instance
const cache = pca.getTokenCache();
// Get all accounts
const accounts = await cache.getAllAccounts();
console.log("Cached accounts:", accounts.length);
// Get specific account
if (accounts.length > 0) {
const account = await cache.getAccountByHomeId(accounts[0].homeAccountId);
console.log("Account:", account?.username);
}
// Remove account from cache
if (accounts.length > 0) {
await cache.removeAccount(accounts[0]);
console.log("Account removed from cache");
}
```
### Cache Serialization
Serialize and deserialize cache for persistent storage.
```typescript { .api }
/**
* Interface for serializable token cache
*/
interface ISerializableTokenCache {
/** Serialize cache to JSON string */
serialize(): Promise<string>;
/** Deserialize cache from JSON string */
deserialize(cache: string): Promise<void>;
}
/**
* Cache serialization types
*/
type JsonCache = {
/** Account entities indexed by key */
Account: Record<string, SerializedAccountEntity>;
/** ID token entities indexed by key */
IdToken: Record<string, SerializedIdTokenEntity>;
/** Access token entities indexed by key */
AccessToken: Record<string, SerializedAccessTokenEntity>;
/** Refresh token entities indexed by key */
RefreshToken: Record<string, SerializedRefreshTokenEntity>;
/** App metadata entities indexed by key */
AppMetadata: Record<string, SerializedAppMetadataEntity>;
};
/**
* In-memory cache representation
*/
type InMemoryCache = {
/** Account entities */
accounts: Record<string, AccountEntity>;
/** ID token entities */
idTokens: Record<string, IdTokenEntity>;
/** Access token entities */
accessTokens: Record<string, AccessTokenEntity>;
/** Refresh token entities */
refreshTokens: Record<string, RefreshTokenEntity>;
/** App metadata entities */
appMetadata: Record<string, AppMetadataEntity>;
};
/**
* Key-value store for cache operations
*/
type CacheKVStore = Record<string, ValidCacheType>;
/**
* Valid cache value types
*/
type ValidCacheType =
| AccountEntity
| IdTokenEntity
| AccessTokenEntity
| RefreshTokenEntity
| AppMetadataEntity;
```
**Usage Example:**
```typescript
import fs from "fs/promises";
const cache = pca.getTokenCache();
// Serialize cache to file
const serializedCache = await cache.serialize();
await fs.writeFile("./token-cache.json", serializedCache);
console.log("Cache saved to file");
// Load cache from file
try {
const cacheData = await fs.readFile("./token-cache.json", "utf8");
await cache.deserialize(cacheData);
console.log("Cache loaded from file");
} catch (error) {
console.log("No existing cache file found");
}
// Clear cache
await cache.clear();
console.log("Cache cleared");
```
### Serialized Entity Types
Detailed structure of cached entities for serialization.
```typescript { .api }
/**
* Serialized account entity
*/
type SerializedAccountEntity = {
/** Home account ID (user identifier across tenants) */
home_account_id: string;
/** Environment (authority host) */
environment: string;
/** Realm (tenant ID) */
realm: string;
/** Local account ID (user identifier within tenant) */
local_account_id: string;
/** Username (UPN or email) */
username: string;
/** Account type */
authority_type: string;
/** Display name */
name?: string;
/** Last modification timestamp */
last_modification_time?: string;
/** Additional client info */
client_info?: string;
};
/**
* Serialized ID token entity
*/
type SerializedIdTokenEntity = {
/** Home account ID */
home_account_id: string;
/** Environment */
environment: string;
/** Credential type */
credential_type: string;
/** Client ID */
client_id: string;
/** ID token JWT */
secret: string;
/** Realm */
realm: string;
};
/**
* Serialized access token entity
*/
type SerializedAccessTokenEntity = {
/** Home account ID */
home_account_id: string;
/** Environment */
environment: string;
/** Credential type */
credential_type: string;
/** Client ID */
client_id: string;
/** Access token */
secret: string;
/** Realm */
realm: string;
/** Target scopes */
target: string;
/** Expiration timestamp */
expires_on: string;
/** Extended expiration timestamp */
extended_expires_on?: string;
/** Cached at timestamp */
cached_at: string;
/** Token type (Bearer) */
token_type?: string;
/** Key ID for key-based tokens */
key_id?: string;
};
/**
* Serialized refresh token entity
*/
type SerializedRefreshTokenEntity = {
/** Home account ID */
home_account_id: string;
/** Environment */
environment: string;
/** Credential type */
credential_type: string;
/** Client ID */
client_id: string;
/** Refresh token */
secret: string;
/** Family ID for family of client IDs */
family_id?: string;
/** Target scopes */
target?: string;
/** Realm */
realm?: string;
};
/**
* Serialized app metadata entity
*/
type SerializedAppMetadataEntity = {
/** Client ID */
client_id: string;
/** Environment */
environment: string;
/** Family ID */
family_id?: string;
};
```
### Cache Plugin System
Pluggable cache system for custom storage backends.
```typescript { .api }
/**
* Cache plugin interface for custom cache implementations
*/
interface ICachePlugin {
/** Called before cache access */
beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void>;
/** Called after cache access */
afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void>;
}
/**
* Token cache context provided to cache plugins
*/
type TokenCacheContext = {
/** Token cache instance */
tokenCache: ISerializableTokenCache;
/** Whether cache has changed */
hasChanged: boolean;
/** Client ID of the application */
clientId: string;
/** Account information if available */
account?: AccountInfo;
/** Requested scopes */
scopes?: string[];
};
/**
* Cache options for configuring cache behavior
*/
type CacheOptions = {
/** Cache plugin for custom storage */
cachePlugin?: ICachePlugin;
/**
* @deprecated claims-based-caching functionality will be removed in the next version
*/
claimsBasedCachingEnabled?: boolean;
};
```
**Usage Example:**
```typescript
import fs from "fs/promises";
// Custom file-based cache plugin
class FileCachePlugin implements ICachePlugin {
private cacheFilePath = "./msal-cache.json";
async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
try {
const cacheData = await fs.readFile(this.cacheFilePath, "utf8");
await cacheContext.tokenCache.deserialize(cacheData);
} catch (error) {
// Cache file doesn't exist yet
console.log("No existing cache file found");
}
}
async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
if (cacheContext.hasChanged) {
const serializedCache = await cacheContext.tokenCache.serialize();
await fs.writeFile(this.cacheFilePath, serializedCache);
console.log("Cache updated and saved to file");
}
}
}
// Use custom cache plugin
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id"
},
cache: {
cachePlugin: new FileCachePlugin()
}
});
```
### Distributed Cache Support
Distributed cache plugin for Redis and other external cache systems.
```typescript { .api }
/**
* Interface for external cache clients (Redis, etc.)
*/
interface ICacheClient {
/** Retrieve value from cache using key */
get(key: string): Promise<string>;
/** Save value to cache using key */
set(key: string, value: string): Promise<string>;
}
/**
* Interface for cache partitioning strategies
*/
interface IPartitionManager {
/** Get partition key for current context */
getKey(): Promise<string>;
/** Extract partition key from account entity */
extractKey(accountEntity: AccountEntity): Promise<string>;
}
/**
* Distributed cache plugin for external cache systems like Redis, DynamoDB, etc.
*/
class DistributedCachePlugin implements ICachePlugin {
constructor(client: ICacheClient, partitionManager: IPartitionManager);
/** Called before cache access to deserialize from external cache */
beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void>;
/** Called after cache access to serialize to external cache */
afterCacheAccess(cacheContext: TokenCacheContext): Promise<void>;
}
```
**Usage Example:**
```typescript
import Redis from "ioredis";
// Redis cache client implementation
class RedisCacheClient implements ICacheClient {
private redis: Redis;
constructor(redisUrl: string) {
this.redis = new Redis(redisUrl);
}
async get(key: string): Promise<string | null> {
return await this.redis.get(key);
}
async set(key: string, value: string): Promise<void> {
await this.redis.set(key, value, "EX", 3600); // 1 hour expiration
}
}
// Simple partition manager
class SimplePartitionManager implements IPartitionManager {
private clientId: string;
constructor(clientId: string) {
this.clientId = clientId;
}
getKey(): string {
return `msal-cache:${this.clientId}`;
}
extractKey(accountEntity: AccountEntity): string {
return `msal-cache:${this.clientId}:${accountEntity.homeAccountId}`;
}
}
// Use distributed cache
const redisClient = new RedisCacheClient("redis://localhost:6379");
const partitionManager = new SimplePartitionManager("your-client-id");
const distributedCachePlugin = new DistributedCachePlugin(redisClient, partitionManager);
const pca = new PublicClientApplication({
auth: {
clientId: "your-client-id"
},
cache: {
cachePlugin: distributedCachePlugin
}
});
```
### Account Management
Advanced account management operations.
```typescript { .api }
/**
* Account information structure
*/
type AccountInfo = {
/** Home account ID (unique across tenants) */
homeAccountId: string;
/** Environment (authority host) */
environment: string;
/** Tenant ID */
tenantId: string;
/** Username (UPN or email) */
username: string;
/** Local account ID (unique within tenant) */
localAccountId: string;
/** Display name */
name?: string;
/** ID token claims */
idTokenClaims?: IdTokenClaims;
};
/**
* ID token claims structure
*/
type IdTokenClaims = {
/** Issuer */
iss?: string;
/** Subject (user ID) */
sub?: string;
/** Audience */
aud?: string;
/** Expiration time */
exp?: number;
/** Issued at time */
iat?: number;
/** Authentication time */
auth_time?: number;
/** Nonce */
nonce?: string;
/** Preferred username */
preferred_username?: string;
/** Name */
name?: string;
/** Email */
email?: string;
/** Object ID */
oid?: string;
/** Tenant ID */
tid?: string;
/** Version */
ver?: string;
};
```
**Usage Example:**
```typescript
// Account management operations
const accounts = await cache.getAllAccounts();
// Filter accounts by tenant
const tenantAccounts = accounts.filter(account =>
account.tenantId === "specific-tenant-id"
);
// Find account by username
const userAccount = accounts.find(account =>
account.username === "user@domain.com"
);
// Get detailed account information
if (userAccount) {
console.log("Account details:");
console.log("- Home Account ID:", userAccount.homeAccountId);
console.log("- Tenant ID:", userAccount.tenantId);
console.log("- Username:", userAccount.username);
console.log("- Display Name:", userAccount.name);
if (userAccount.idTokenClaims) {
console.log("- Email:", userAccount.idTokenClaims.email);
console.log("- Object ID:", userAccount.idTokenClaims.oid);
}
}
// Remove specific account
if (userAccount) {
await cache.removeAccount(userAccount);
console.log("Account removed from cache");
}
```
### Cache Performance Optimization
Best practices for cache performance and management.
```typescript { .api }
/**
* Cache performance considerations
*/
type CachePerformanceOptions = {
/** Enable cache compression */
enableCompression?: boolean;
/** Cache size limits */
maxCacheSize?: number;
/** Token expiration buffer */
expirationBuffer?: number;
/** Cleanup interval */
cleanupInterval?: number;
};
```
**Usage Example:**
```typescript
// High-performance cache plugin with compression and cleanup
class OptimizedCachePlugin implements ICachePlugin {
private cacheFile = "./optimized-cache.json";
private lastCleanup = Date.now();
private cleanupInterval = 60000; // 1 minute
async beforeCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
// Periodic cleanup of expired tokens
if (Date.now() - this.lastCleanup > this.cleanupInterval) {
await this.cleanupExpiredTokens(cacheContext);
this.lastCleanup = Date.now();
}
// Load cache from file
try {
const cacheData = await fs.readFile(this.cacheFile, "utf8");
await cacheContext.tokenCache.deserialize(cacheData);
} catch (error) {
// No cache file exists
}
}
async afterCacheAccess(cacheContext: TokenCacheContext): Promise<void> {
if (cacheContext.hasChanged) {
const serializedCache = await cacheContext.tokenCache.serialize();
// Compress cache data (optional)
const compressedCache = this.compressCache(serializedCache);
await fs.writeFile(this.cacheFile, compressedCache);
}
}
private async cleanupExpiredTokens(cacheContext: TokenCacheContext): Promise<void> {
// Implementation to remove expired access tokens
const accessTokens = await cacheContext.tokenCache.getAllAccessTokens();
const now = Date.now() / 1000;
for (const token of accessTokens) {
if (token.expiresOn && parseInt(token.expiresOn) < now) {
// Remove expired token
console.log("Removing expired token");
}
}
}
private compressCache(cache: string): string {
// Optional: implement compression
return cache;
}
}
```
## Cache Storage Patterns
### In-Memory Only
```typescript
// Default behavior - cache only exists in memory
const pca = new PublicClientApplication({
auth: { clientId: "your-client-id" }
// No cache plugin = in-memory only
});
```
### File-Based Persistence
```typescript
// File-based cache for desktop applications
const pca = new PublicClientApplication({
auth: { clientId: "your-client-id" },
cache: { cachePlugin: new FileCachePlugin() }
});
```
### Distributed Cache
```typescript
// Redis-based cache for scalable web applications
const pca = new PublicClientApplication({
auth: { clientId: "your-client-id" },
cache: { cachePlugin: new DistributedCachePlugin(redisClient, partitionManager) }
});
```
### Encrypted Cache
```typescript
// Encrypted file cache for sensitive environments
const pca = new PublicClientApplication({
auth: { clientId: "your-client-id" },
cache: { cachePlugin: new EncryptedFileCachePlugin(encryptionKey) }
});
```

View file

@ -0,0 +1,7 @@
{
"name": "tessl/npm-azure--msal-node",
"version": "3.7.0",
"docs": "docs/index.md",
"describes": "pkg:npm/@azure/msal-node@3.7.3",
"summary": "Microsoft Authentication Library for Node - A comprehensive Node.js authentication library for Microsoft identity platform supporting multiple OAuth 2.0 flows"
}

View file

@ -0,0 +1,346 @@
# Chart Management
Core Chart class functionality for creating, updating, and managing chart instances.
## Capabilities
### Chart Constructor
Creates a new chart instance on a canvas element.
```javascript { .api }
/**
* Creates a new chart instance
* @param item - Canvas element, context, string ID, or other chart item
* @param config - Chart configuration object
*/
constructor(
item: ChartItem,
config: ChartConfiguration
): Chart;
```
**Usage Example:**
```javascript
import { Chart } from "chart.js";
const ctx = document.getElementById('myChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['A', 'B', 'C'],
datasets: [{
label: 'Dataset 1',
data: [1, 2, 3]
}]
},
options: {
responsive: true
}
});
```
### Update Chart
Updates the chart with new data or configuration changes.
```javascript { .api }
/**
* Updates the chart
* @param mode - Animation mode for the update
* @returns void
*/
update(mode?: UpdateMode): void;
```
**Usage Example:**
```javascript
// Update chart data
chart.data.datasets[0].data = [4, 5, 6];
chart.update('active'); // Animates the update
// Update without animation
chart.update('none');
```
### Render Chart
Triggers a re-render of the chart.
```javascript { .api }
/**
* Renders the chart
* @returns void
*/
render(): void;
```
### Draw Chart
Draws the chart without animation.
```javascript { .api }
/**
* Draws the chart without animation
* @returns void
*/
draw(): void;
```
### Resize Chart
Resizes the chart to fit its container or specified dimensions.
```javascript { .api }
/**
* Resizes the chart
* @param width - Optional new width
* @param height - Optional new height
* @returns void
*/
resize(width?: number, height?: number): void;
```
### Clear Chart
Clears the chart canvas.
```javascript { .api }
/**
* Clears the chart canvas
* @returns Chart instance for chaining
*/
clear(): this;
```
### Reset Chart
Resets the chart to its initial state.
```javascript { .api }
/**
* Resets the chart to initial state
* @returns void
*/
reset(): void;
```
### Stop Animations
Stops all current animations.
```javascript { .api }
/**
* Stops all chart animations
* @returns Chart instance for chaining
*/
stop(): this;
```
### Destroy Chart
Destroys the chart instance and cleans up resources.
```javascript { .api }
/**
* Destroys the chart instance
* @returns void
*/
destroy(): void;
```
**Usage Example:**
```javascript
// Clean up when done
chart.destroy();
```
### Export Image
Exports the chart as a base64 encoded image.
```javascript { .api }
/**
* Exports chart as base64 image
* @param type - Image MIME type (default: 'image/png')
* @param quality - Image quality for lossy formats
* @returns Base64 encoded image string
*/
toBase64Image(type?: string, quality?: unknown): string;
```
**Usage Example:**
```javascript
// Export as PNG
const pngImage = chart.toBase64Image();
// Export as JPEG with quality
const jpegImage = chart.toBase64Image('image/jpeg', 0.8);
```
### Element Visibility
Control visibility of chart elements.
```javascript { .api }
/**
* Hides chart elements
* @param datasetIndex - Index of dataset
* @param dataIndex - Optional index of specific data point
* @returns void
*/
hide(datasetIndex: number, dataIndex?: number): void;
/**
* Shows chart elements
* @param datasetIndex - Index of dataset
* @param dataIndex - Optional index of specific data point
* @returns void
*/
show(datasetIndex: number, dataIndex?: number): void;
```
**Usage Example:**
```javascript
// Hide entire dataset
chart.hide(0);
// Hide specific data point
chart.hide(0, 2);
// Show dataset
chart.show(0);
```
### Active Elements
Get and set active (highlighted) chart elements.
```javascript { .api }
/**
* Gets currently active elements
* @returns Array of active elements
*/
getActiveElements(): ActiveElement[];
/**
* Sets active elements
* @param active - Array of data points to make active
* @returns void
*/
setActiveElements(active: ActiveDataPoint[]): void;
```
### Static Methods
Class-level methods for chart management.
```javascript { .api }
/**
* Gets chart instance by key
* @param key - Chart ID, canvas element, or context
* @returns Chart instance or undefined
*/
static getChart(key: string | CanvasRenderingContext2D | HTMLCanvasElement): Chart | undefined;
/**
* Registers chart components
* @param items - Components to register
* @returns void
*/
static register(...items: ChartComponentLike[]): void;
/**
* Unregisters chart components
* @param items - Components to unregister
* @returns void
*/
static unregister(...items: ChartComponentLike[]): void;
```
**Usage Example:**
```javascript
import { Chart, LinearScale, CategoryScale } from 'chart.js';
// Register components
Chart.register(LinearScale, CategoryScale);
// Get chart by canvas ID
const chart = Chart.getChart('myCanvas');
```
### Instance Properties
Key properties available on chart instances.
```javascript { .api }
interface Chart {
// Read-only properties
readonly id: string;
readonly canvas: HTMLCanvasElement;
readonly ctx: CanvasRenderingContext2D;
readonly platform: BasePlatform;
readonly config: ChartConfiguration;
readonly scales: { [key: string]: Scale };
// Mutable properties
data: ChartData;
options: ChartOptions;
}
```
### Static Properties
Class-level properties and registries.
```javascript { .api }
interface ChartStatic {
defaults: Defaults;
overrides: Overrides;
version: string;
instances: { [key: string]: Chart };
registry: Registry;
}
```
## Types
```javascript { .api }
type UpdateMode = 'resize' | 'reset' | 'none' | 'hide' | 'show' | 'default' | 'active';
interface ActiveElement {
datasetIndex: number;
index: number;
element: Element;
}
interface ActiveDataPoint {
datasetIndex: number;
index: number;
}
interface ChartConfiguration<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> {
type: TType;
data: ChartData<TType, TData, TLabel>;
options?: ChartOptions<TType>;
plugins?: Plugin[];
}
interface ChartData<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> {
labels?: TLabel[];
datasets: ChartDataset<TType, TData>[];
}
type ChartItem =
| string
| CanvasRenderingContext2D
| HTMLCanvasElement
| { canvas: HTMLCanvasElement }
| ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>;
```

View file

@ -0,0 +1,461 @@
# Chart Types
Built-in chart type controllers for different visualization patterns. Each controller handles dataset-specific data processing, element creation, and interaction handling.
## Capabilities
### Bar Charts
Creates bar and column charts for categorical data comparison.
```javascript { .api }
class BarController extends DatasetController {
static id: 'bar';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface BarChartConfiguration {
type: 'bar';
data: {
labels: string[];
datasets: BarDataset[];
};
options?: BarChartOptions;
}
interface BarDataset {
label?: string;
data: number[];
backgroundColor?: Color | Color[];
borderColor?: Color | Color[];
borderWidth?: number | number[];
borderRadius?: number | BorderRadius | (number | BorderRadius)[];
borderSkipped?: string | string[];
base?: number | number[];
grouped?: boolean;
indexAxis?: 'x' | 'y';
maxBarThickness?: number;
minBarLength?: number;
order?: number;
stack?: string;
}
```
**Usage Example:**
```javascript
const config = {
type: 'bar',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June'],
datasets: [{
label: 'Sales',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
};
```
### Line Charts
Creates line charts for showing trends over continuous data.
```javascript { .api }
class LineController extends DatasetController {
static id: 'line';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface LineChartConfiguration {
type: 'line';
data: {
labels: string[];
datasets: LineDataset[];
};
options?: LineChartOptions;
}
interface LineDataset {
label?: string;
data: (number | Point)[];
backgroundColor?: Color;
borderColor?: Color;
borderWidth?: number;
borderDash?: number[];
borderDashOffset?: number;
borderCapStyle?: CanvasLineCap;
borderJoinStyle?: CanvasLineJoin;
cubicInterpolationMode?: 'default' | 'monotone';
fill?: boolean | string | number | object;
lineTension?: number;
pointBackgroundColor?: Color | Color[];
pointBorderColor?: Color | Color[];
pointBorderWidth?: number | number[];
pointRadius?: number | number[];
pointStyle?: PointStyle | PointStyle[];
showLine?: boolean;
spanGaps?: boolean | number;
stepped?: boolean | 'before' | 'after' | 'middle';
tension?: number;
}
```
**Usage Example:**
```javascript
const config = {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Temperature',
data: [20, 25, 22, 28, 24, 30],
borderColor: 'rgba(255, 99, 132, 1)',
backgroundColor: 'rgba(255, 99, 132, 0.2)',
tension: 0.1,
fill: true
}]
}
};
```
### Radar Charts
Creates radar charts for multivariate data comparison.
```javascript { .api }
class RadarController extends DatasetController {
static id: 'radar';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface RadarChartConfiguration {
type: 'radar';
data: {
labels: string[];
datasets: RadarDataset[];
};
options?: RadarChartOptions;
}
interface RadarDataset {
label?: string;
data: number[];
backgroundColor?: Color;
borderColor?: Color;
borderWidth?: number;
borderDash?: number[];
borderDashOffset?: number;
borderCapStyle?: CanvasLineCap;
borderJoinStyle?: CanvasLineJoin;
fill?: boolean | string | number;
lineTension?: number;
pointBackgroundColor?: Color | Color[];
pointBorderColor?: Color | Color[];
pointBorderWidth?: number | number[];
pointRadius?: number | number[];
pointStyle?: PointStyle | PointStyle[];
tension?: number;
}
```
### Doughnut Charts
Creates doughnut charts for proportional data visualization.
```javascript { .api }
class DoughnutController extends DatasetController {
static id: 'doughnut';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface DoughnutChartConfiguration {
type: 'doughnut';
data: {
labels: string[];
datasets: DoughnutDataset[];
};
options?: DoughnutChartOptions;
}
interface DoughnutDataset {
label?: string;
data: number[];
backgroundColor?: Color | Color[];
borderColor?: Color | Color[];
borderWidth?: number | number[];
borderAlign?: 'center' | 'inner';
borderRadius?: number | number[];
circumference?: number;
clip?: number | object;
hoverBackgroundColor?: Color | Color[];
hoverBorderColor?: Color | Color[];
hoverBorderWidth?: number | number[];
hoverOffset?: number | number[];
offset?: number | number[];
rotation?: number;
spacing?: number;
weight?: number;
}
```
### Pie Charts
Creates pie charts, extends doughnut charts with 100% circumference.
```javascript { .api }
class PieController extends DoughnutController {
static id: 'pie';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface PieChartConfiguration {
type: 'pie';
data: {
labels: string[];
datasets: PieDataset[];
};
options?: PieChartOptions;
}
// PieDataset extends DoughnutDataset
type PieDataset = DoughnutDataset;
```
### Polar Area Charts
Creates polar area charts for proportional data with equal angles.
```javascript { .api }
class PolarAreaController extends DatasetController {
static id: 'polarArea';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface PolarAreaChartConfiguration {
type: 'polarArea';
data: {
labels: string[];
datasets: PolarAreaDataset[];
};
options?: PolarAreaChartOptions;
}
interface PolarAreaDataset {
label?: string;
data: number[];
backgroundColor?: Color | Color[];
borderColor?: Color | Color[];
borderWidth?: number | number[];
borderAlign?: 'center' | 'inner';
hoverBackgroundColor?: Color | Color[];
hoverBorderColor?: Color | Color[];
hoverBorderWidth?: number | number[];
}
```
### Bubble Charts
Creates bubble charts for three-dimensional data visualization.
```javascript { .api }
class BubbleController extends DatasetController {
static id: 'bubble';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface BubbleChartConfiguration {
type: 'bubble';
data: {
datasets: BubbleDataset[];
};
options?: BubbleChartOptions;
}
interface BubbleDataset {
label?: string;
data: BubbleDataPoint[];
backgroundColor?: Color | Color[];
borderColor?: Color | Color[];
borderWidth?: number | number[];
hoverBackgroundColor?: Color | Color[];
hoverBorderColor?: Color | Color[];
hoverBorderWidth?: number | number[];
hoverRadius?: number | number[];
pointRadius?: number | number[];
pointStyle?: PointStyle | PointStyle[];
rotation?: number | number[];
}
interface BubbleDataPoint {
x: number;
y: number;
r: number;
}
```
### Scatter Charts
Creates scatter plots, extends line charts without connecting lines.
```javascript { .api }
class ScatterController extends LineController {
static id: 'scatter';
static defaults: object;
static overrides: object;
}
// Chart configuration
interface ScatterChartConfiguration {
type: 'scatter';
data: {
datasets: ScatterDataset[];
};
options?: ScatterChartOptions;
}
// ScatterDataset extends LineDataset
interface ScatterDataset extends LineDataset {
data: ScatterDataPoint[];
showLine?: boolean; // Typically false for scatter
}
interface ScatterDataPoint {
x: number;
y: number;
}
```
**Usage Example:**
```javascript
const config = {
type: 'scatter',
data: {
datasets: [{
label: 'Scatter Dataset',
data: [{
x: -10,
y: 0
}, {
x: 0,
y: 10
}, {
x: 10,
y: 5
}],
backgroundColor: 'rgba(255, 99, 132, 1)'
}]
},
options: {
scales: {
x: {
type: 'linear',
position: 'bottom'
}
}
}
};
```
## Types
```javascript { .api }
type ChartType = 'line' | 'bar' | 'radar' | 'doughnut' | 'pie' | 'polarArea' | 'bubble' | 'scatter';
interface DatasetController {
static id: string;
static defaults: object;
static overrides: object;
constructor(chart: Chart, datasetIndex: number);
initialize(): void;
update(mode: UpdateMode): void;
draw(): void;
reset(): void;
destroy(): void;
}
type Color = string | CanvasGradient | CanvasPattern;
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle' | HTMLImageElement | HTMLCanvasElement;
interface BorderRadius {
topLeft: number;
topRight: number;
bottomLeft: number;
bottomRight: number;
}
interface Point {
x: number;
y: number;
}
// Chart-specific options interfaces
interface BarChartOptions extends ChartOptions<'bar'> {
indexAxis?: 'x' | 'y';
skipNull?: boolean;
grouped?: boolean;
maxBarThickness?: number;
categoryPercentage?: number;
barPercentage?: number;
}
interface LineChartOptions extends ChartOptions<'line'> {
showLine?: boolean;
spanGaps?: boolean | number;
}
interface RadarChartOptions extends ChartOptions<'radar'> {
scale?: RadialLinearScaleOptions;
}
interface DoughnutChartOptions extends ChartOptions<'doughnut'> {
circumference?: number;
rotation?: number;
cutout?: number | string;
radius?: number | string;
}
interface PieChartOptions extends DoughnutChartOptions {
// Inherits from DoughnutChartOptions
}
interface PolarAreaChartOptions extends ChartOptions<'polarArea'> {
scale?: RadialLinearScaleOptions;
}
interface BubbleChartOptions extends ChartOptions<'bubble'> {
// Extends base chart options
}
interface ScatterChartOptions extends LineChartOptions {
showLine?: false; // Typically false for scatter
}
```

View file

@ -0,0 +1,291 @@
# Chart.js
Chart.js is a comprehensive JavaScript charting library that enables developers to create responsive, interactive HTML5 charts using the Canvas element. It provides a wide variety of chart types including line, bar, radar, doughnut, pie, polar area, bubble, and scatter charts with extensive customization options for colors, animations, tooltips, legends, and data interactions.
## Package Information
- **Package Name**: chart.js
- **Package Type**: npm
- **Language**: JavaScript/TypeScript
- **Installation**: `npm install chart.js`
## Core Imports
```javascript
import { Chart } from "chart.js";
```
Import with auto-registration of all components:
```javascript
import Chart from "chart.js/auto";
```
For tree-shaking, import components selectively:
```javascript
import {
Chart,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js";
Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
```
CommonJS:
```javascript
const { Chart } = require("chart.js");
```
## Basic Usage
```javascript
import { Chart } from "chart.js/auto";
// Create a basic bar chart
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 205, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
```
## Architecture
Chart.js is built around several key components:
- **Chart Class**: The main class for creating and managing chart instances
- **Controllers**: Chart-type-specific logic for data processing (BarController, LineController, etc.)
- **Elements**: Visual components for rendering (BarElement, LineElement, PointElement, ArcElement)
- **Scales**: Data-to-pixel conversion and axis rendering (LinearScale, CategoryScale, TimeScale, etc.)
- **Plugins**: Extensible functionality system (Tooltip, Legend, Title, etc.)
- **Registry**: Component registration and management system
- **Platform**: Environment abstraction (DOM, Basic, Custom platforms)
## Capabilities
### Chart Management
Core Chart class functionality for creating, updating, and managing chart instances.
```javascript { .api }
class Chart {
constructor(item: ChartItem, config: ChartConfiguration);
// Instance methods
update(mode?: UpdateMode): void;
render(): void;
resize(width?: number, height?: number): void;
destroy(): void;
toBase64Image(type?: string, quality?: unknown): string;
// Static methods
static getChart(key: string | CanvasRenderingContext2D | HTMLCanvasElement): Chart;
static register(...items: ChartComponentLike[]): void;
static unregister(...items: ChartComponentLike[]): void;
}
```
[Chart Management](./chart-management.md)
### Chart Types
Built-in chart type controllers for different visualization patterns.
```javascript { .api }
// Available chart types
type ChartType = 'line' | 'bar' | 'radar' | 'doughnut' | 'pie' | 'polarArea' | 'bubble' | 'scatter';
// Controllers
class BarController extends DatasetController { }
class LineController extends DatasetController { }
class RadarController extends DatasetController { }
class DoughnutController extends DatasetController { }
class PieController extends DoughnutController { }
class PolarAreaController extends DatasetController { }
class BubbleController extends DatasetController { }
class ScatterController extends LineController { }
```
[Chart Types](./chart-types.md)
### Scales
Data-to-pixel conversion and axis rendering for different data types.
```javascript { .api }
// Available scale types
type ScaleType = 'linear' | 'logarithmic' | 'category' | 'time' | 'timeseries' | 'radialLinear';
// Base scale interface
interface Scale {
getPixelForValue(value: unknown): number;
getValueForPixel(pixel: number): unknown;
getLabelForValue(value: unknown): string;
getDecimalForPixel(pixel: number): number;
}
```
[Scales](./scales.md)
### Visual Elements
Core visual components for rendering chart elements.
```javascript { .api }
// Element classes
class BarElement extends Element { }
class LineElement extends Element { }
class PointElement extends Element { }
class ArcElement extends Element { }
// Base element interface
interface Element {
draw(ctx: CanvasRenderingContext2D): void;
inRange(mouseX: number, mouseY: number): boolean;
inXRange(mouseX: number): boolean;
inYRange(mouseY: number): boolean;
getCenterPoint(): Point;
}
```
[Visual Elements](./visual-elements.md)
### Plugins
Built-in and extensible plugin system for additional functionality.
```javascript { .api }
// Built-in plugins
const Title: Plugin;
const Legend: Plugin;
const Tooltip: Plugin;
const SubTitle: Plugin;
const Filler: Plugin;
const Decimation: Plugin;
const Colors: Plugin;
// Plugin interface
interface Plugin {
id: string;
beforeInit?(chart: Chart, args: EmptyObject, options: O): void;
afterInit?(chart: Chart, args: EmptyObject, options: O): void;
beforeUpdate?(chart: Chart, args: UpdateArgs, options: O): void;
afterUpdate?(chart: Chart, args: UpdateArgs, options: O): void;
// ... more lifecycle hooks
}
```
[Plugins](./plugins.md)
### Utilities
Helper functions for common operations, calculations, and canvas manipulation.
```javascript { .api }
// Color utilities
function color(value: string | CanvasGradient | CanvasPattern): Color;
function getHoverColor(color: Color): Color;
// Core utilities
function isArray(value: unknown): value is unknown[];
function isObject(value: unknown): value is object;
function valueOrDefault<T>(value: T | undefined, defaultValue: T): T;
// Canvas utilities
function clearCanvas(ctx: CanvasRenderingContext2D): void;
function drawPoint(ctx: CanvasRenderingContext2D, options: PointOptions, x: number, y: number): void;
```
[Utilities](./utilities.md)
## Types
```javascript { .api }
interface ChartConfiguration<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> {
type: TType;
data: ChartData<TType, TData, TLabel>;
options?: ChartOptions<TType>;
plugins?: Plugin[];
}
interface ChartData<TType extends ChartType = ChartType, TData = DefaultDataPoint<TType>, TLabel = unknown> {
labels?: TLabel[];
datasets: ChartDataset<TType, TData>[];
}
interface ChartOptions<TType extends ChartType = ChartType> {
responsive?: boolean;
maintainAspectRatio?: boolean;
aspectRatio?: number;
resizeDelay?: number;
devicePixelRatio?: number;
hover?: HoverOptions<TType>;
events?: Event[];
onClick?: (event: ChartEvent, elements: ActiveElement[], chart: Chart) => void;
plugins?: PluginOptionsByType<TType>;
scales?: ScaleOptionsByType<TType>;
elements?: ElementOptionsByType<TType>;
layout?: LayoutOptions;
animation?: AnimationOptions<TType>;
animations?: AnimationOptions<TType>;
transitions?: TransitionsSpec<TType>;
}
type UpdateMode = 'resize' | 'reset' | 'none' | 'hide' | 'show' | 'default' | 'active';
interface Point {
x: number;
y: number;
}
interface Color {
r: number;
g: number;
b: number;
a: number;
}
type ChartItem =
| string
| CanvasRenderingContext2D
| HTMLCanvasElement
| { canvas: HTMLCanvasElement }
| ArrayLike<CanvasRenderingContext2D | HTMLCanvasElement>;
```

View file

@ -0,0 +1,529 @@
# Plugins
Built-in and extensible plugin system for additional functionality. Plugins provide lifecycle hooks and configurable features that extend chart behavior.
## Capabilities
### Title Plugin
Displays chart titles and subtitles.
```javascript { .api }
const Title: Plugin = {
id: 'title',
defaults: object,
// Plugin implementation
};
interface TitleOptions {
display?: boolean;
position?: 'top' | 'left' | 'bottom' | 'right';
align?: 'start' | 'center' | 'end';
color?: Color;
font?: FontSpec;
fullSize?: boolean;
padding?: number | Padding;
text?: string | string[];
}
```
**Usage Example:**
```javascript
const config = {
type: 'bar',
data: { /* data */ },
options: {
plugins: {
title: {
display: true,
text: 'Chart Title',
position: 'top',
align: 'center',
color: '#333',
font: {
size: 18,
weight: 'bold'
},
padding: {
top: 10,
bottom: 30
}
}
}
}
};
```
### Legend Plugin
Displays chart legend for datasets.
```javascript { .api }
const Legend: Plugin = {
id: 'legend',
defaults: object,
// Plugin implementation
};
interface LegendOptions {
display?: boolean;
position?: 'top' | 'left' | 'bottom' | 'right' | 'chartArea';
align?: 'start' | 'center' | 'end';
maxHeight?: number;
maxWidth?: number;
fullSize?: boolean;
onClick?: (event: ChartEvent, legendItem: LegendItem, legend: LegendElement) => void;
onHover?: (event: ChartEvent, legendItem: LegendItem, legend: LegendElement) => void;
onLeave?: (event: ChartEvent, legendItem: LegendItem, legend: LegendElement) => void;
reverse?: boolean;
labels?: LegendLabelOptions;
rtl?: boolean;
textDirection?: string;
title?: LegendTitleOptions;
}
interface LegendLabelOptions {
boxWidth?: number;
boxHeight?: number;
color?: Color;
font?: FontSpec;
padding?: number;
generateLabels?: (chart: Chart) => LegendItem[];
filter?: (item: LegendItem, data: ChartData) => boolean;
sort?: (a: LegendItem, b: LegendItem, data: ChartData) => number;
pointStyle?: PointStyle | HTMLImageElement | HTMLCanvasElement;
textAlign?: 'left' | 'right' | 'center';
usePointStyle?: boolean;
useBorderRadius?: boolean;
borderRadius?: number;
}
interface LegendItem {
text: string;
lineCap?: CanvasLineCap;
lineDash?: number[];
lineDashOffset?: number;
lineJoin?: CanvasLineJoin;
lineWidth?: number;
strokeStyle?: Color;
pointStyle?: PointStyle | HTMLImageElement | HTMLCanvasElement;
rotation?: number;
boxWidth?: number;
boxHeight?: number;
color?: Color;
fillStyle?: Color;
fontColor?: Color;
hidden?: boolean;
index?: number;
datasetIndex?: number;
}
```
**Usage Example:**
```javascript
const config = {
type: 'line',
data: { /* data */ },
options: {
plugins: {
legend: {
display: true,
position: 'bottom',
align: 'center',
labels: {
usePointStyle: true,
padding: 20,
font: {
size: 14
},
generateLabels: (chart) => {
// Custom label generation
return chart.data.datasets.map((dataset, i) => ({
text: dataset.label,
fillStyle: dataset.backgroundColor,
strokeStyle: dataset.borderColor,
hidden: !chart.isDatasetVisible(i),
index: i
}));
}
}
}
}
}
};
```
### Tooltip Plugin
Interactive tooltips on hover.
```javascript { .api }
const Tooltip: Plugin = {
id: 'tooltip',
defaults: object,
// Plugin implementation
};
interface TooltipOptions {
enabled?: boolean;
external?: (context: TooltipContext) => void;
mode?: InteractionMode;
intersect?: boolean;
position?: 'average' | 'nearest' | TooltipPositioner;
callbacks?: TooltipCallbacks;
itemSort?: (a: TooltipItem, b: TooltipItem, data: ChartData) => number;
filter?: (tooltipItem: TooltipItem, index: number, tooltipItems: TooltipItem[], data: ChartData) => boolean;
backgroundColor?: Color;
titleColor?: Color;
titleFont?: FontSpec;
titleAlign?: 'left' | 'center' | 'right';
titleSpacing?: number;
titleMarginBottom?: number;
bodyColor?: Color;
bodyFont?: FontSpec;
bodyAlign?: 'left' | 'center' | 'right';
bodySpacing?: number;
footerColor?: Color;
footerFont?: FontSpec;
footerAlign?: 'left' | 'center' | 'right';
footerSpacing?: number;
footerMarginTop?: number;
padding?: number | Padding;
caretPadding?: number;
caretSize?: number;
cornerRadius?: number;
multiKeyBackground?: Color;
displayColors?: boolean;
boxWidth?: number;
boxHeight?: number;
boxPadding?: number;
usePointStyle?: boolean;
borderColor?: Color;
borderWidth?: number;
rtl?: boolean;
textDirection?: string;
xAlign?: 'left' | 'center' | 'right';
yAlign?: 'top' | 'center' | 'bottom';
}
interface TooltipCallbacks {
beforeTitle?: (tooltipItems: TooltipItem[]) => string | string[];
title?: (tooltipItems: TooltipItem[]) => string | string[];
afterTitle?: (tooltipItems: TooltipItem[]) => string | string[];
beforeBody?: (tooltipItems: TooltipItem[]) => string | string[];
beforeLabel?: (tooltipItem: TooltipItem) => string | string[];
label?: (tooltipItem: TooltipItem) => string | string[];
labelColor?: (tooltipItem: TooltipItem) => TooltipLabelStyle;
labelTextColor?: (tooltipItem: TooltipItem) => Color;
labelPointStyle?: (tooltipItem: TooltipItem) => PointStyle | { pointStyle: PointStyle; rotation: number };
afterLabel?: (tooltipItem: TooltipItem) => string | string[];
afterBody?: (tooltipItems: TooltipItem[]) => string | string[];
beforeFooter?: (tooltipItems: TooltipItem[]) => string | string[];
footer?: (tooltipItems: TooltipItem[]) => string | string[];
afterFooter?: (tooltipItems: TooltipItem[]) => string | string[];
}
interface TooltipItem {
chart: Chart;
label: string;
parsed: ParsedDataType;
raw: unknown;
formattedValue: string;
dataset: ChartDataset;
datasetIndex: number;
dataIndex: number;
element: Element;
}
```
### Subtitle Plugin
Displays chart subtitles.
```javascript { .api }
const SubTitle: Plugin = {
id: 'subtitle',
defaults: object,
// Plugin implementation
};
// SubTitleOptions extends TitleOptions
interface SubTitleOptions extends TitleOptions {
// Inherits all title options
}
```
### Filler Plugin
Fills areas between datasets or to axes.
```javascript { .api }
const Filler: Plugin = {
id: 'filler',
defaults: object,
// Plugin implementation
};
interface FillerOptions {
propagate?: boolean;
drawTime?: 'beforeDatasetDraw' | 'beforeDatasetsDraw';
}
// Fill configurations (used in dataset options)
type FillTarget = boolean | number | string | 'start' | 'end' | 'origin' | 'stack' | 'shape' | { target: FillTarget; above?: Color; below?: Color };
```
**Usage Example:**
```javascript
const config = {
type: 'line',
data: {
datasets: [{
label: 'Dataset 1',
data: [10, 20, 30, 40],
fill: 'start', // Fill to start of chart
backgroundColor: 'rgba(75, 192, 192, 0.2)'
}, {
label: 'Dataset 2',
data: [15, 25, 35, 45],
fill: '-1', // Fill to previous dataset
backgroundColor: 'rgba(255, 99, 132, 0.2)'
}]
},
options: {
plugins: {
filler: {
propagate: true
}
}
}
};
```
### Decimation Plugin
Reduces data points for performance with large datasets.
```javascript { .api }
const Decimation: Plugin = {
id: 'decimation',
defaults: object,
// Plugin implementation
};
interface DecimationOptions {
enabled?: boolean;
algorithm?: 'lttb' | 'min-max';
samples?: number;
threshold?: number;
}
```
### Colors Plugin
Automatic color assignment for datasets.
```javascript { .api }
const Colors: Plugin = {
id: 'colors',
defaults: object,
// Plugin implementation
};
interface ColorsOptions {
enabled?: boolean;
forceOverride?: boolean;
}
```
### Plugin Interface
Base interface for creating custom plugins.
```javascript { .api }
interface Plugin<TType extends ChartType = ChartType, O = object> {
id: string;
// Lifecycle hooks
beforeInit?(chart: Chart<TType>, args: EmptyObject, options: O): void;
afterInit?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeUpdate?(chart: Chart<TType>, args: UpdateArgs, options: O): boolean | void;
afterUpdate?(chart: Chart<TType>, args: UpdateArgs, options: O): void;
beforeElementsUpdate?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeLayout?(chart: Chart<TType>, args: EmptyObject, options: O): boolean | void;
afterLayout?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeDatasetsUpdate?(chart: Chart<TType>, args: UpdateArgs, options: O): boolean | void;
afterDatasetsUpdate?(chart: Chart<TType>, args: UpdateArgs, options: O): void;
beforeDatasetUpdate?(chart: Chart<TType>, args: DatasetUpdateArgs, options: O): boolean | void;
afterDatasetUpdate?(chart: Chart<TType>, args: DatasetUpdateArgs, options: O): void;
beforeRender?(chart: Chart<TType>, args: EmptyObject, options: O): boolean | void;
afterRender?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeDraw?(chart: Chart<TType>, args: EmptyObject, options: O): boolean | void;
afterDraw?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeDatasetsDraw?(chart: Chart<TType>, args: EmptyObject, options: O): boolean | void;
afterDatasetsDraw?(chart: Chart<TType>, args: EmptyObject, options: O): void;
beforeDatasetDraw?(chart: Chart<TType>, args: DatasetDrawArgs, options: O): boolean | void;
afterDatasetDraw?(chart: Chart<TType>, args: DatasetDrawArgs, options: O): void;
beforeTooltipDraw?(chart: Chart<TType>, args: TooltipDrawArgs, options: O): boolean | void;
afterTooltipDraw?(chart: Chart<TType>, args: TooltipDrawArgs, options: O): void;
beforeEvent?(chart: Chart<TType>, args: EventArgs, options: O): boolean | void;
afterEvent?(chart: Chart<TType>, args: EventArgs, options: O): void;
resize?(chart: Chart<TType>, args: ResizeArgs, options: O): void;
reset?(chart: Chart<TType>, args: EmptyObject, options: O): void;
stop?(chart: Chart<TType>, args: EmptyObject, options: O): void;
destroy?(chart: Chart<TType>, args: EmptyObject, options: O): void;
// Optional properties
defaults?: object;
defaultRoutes?: { [property: string]: string };
descriptors?: { [property: string]: boolean };
}
```
**Usage Example (Custom Plugin):**
```javascript
const customPlugin = {
id: 'customPlugin',
beforeDraw: (chart, args, options) => {
const { ctx, chartArea } = chart;
// Custom drawing logic
ctx.fillStyle = options.backgroundColor || 'lightgray';
ctx.fillRect(chartArea.left, chartArea.top, chartArea.width, chartArea.height);
}
};
// Register and use
Chart.register(customPlugin);
const config = {
type: 'bar',
data: { /* data */ },
plugins: [customPlugin],
options: {
plugins: {
customPlugin: {
backgroundColor: 'lightblue'
}
}
}
};
```
## Plugin Registration
Plugins must be registered before use.
```javascript { .api }
import {
Chart,
Title,
Tooltip,
Legend,
SubTitle,
Filler,
Decimation,
Colors
} from 'chart.js';
// Register built-in plugins
Chart.register(
Title,
Tooltip,
Legend,
SubTitle,
Filler,
Decimation,
Colors
);
```
## Types
```javascript { .api }
type InteractionMode = 'point' | 'nearest' | 'index' | 'dataset' | 'x' | 'y';
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
interface FontSpec {
family?: string;
size?: number;
style?: 'normal' | 'italic' | 'oblique';
weight?: string | number;
lineHeight?: number | string;
}
interface Padding {
top?: number;
right?: number;
bottom?: number;
left?: number;
}
interface EmptyObject {}
interface UpdateArgs {
mode: UpdateMode;
}
interface DatasetUpdateArgs {
index: number;
mode: UpdateMode;
}
interface EventArgs {
event: ChartEvent;
replay: boolean;
changed?: boolean;
cancelable: boolean;
inChartArea: boolean;
}
type Color = string | CanvasGradient | CanvasPattern;
interface ChartEvent {
type: string;
x?: number;
y?: number;
native?: Event;
}
interface TooltipContext<TType extends ChartType = ChartType> {
chart: Chart<TType>;
tooltip: TooltipModel<TType>;
tooltipItems: TooltipItem<TType>[];
}
interface TooltipModel<TType extends ChartType = ChartType> {
opacity: number;
title: string[];
body: TooltipBodyItem[];
footer: string[];
x: number;
y: number;
width: number;
height: number;
caretX: number;
caretY: number;
}
interface TooltipBodyItem {
before: string[];
lines: string[];
after: string[];
}
type TooltipPositioner = (elements: Element[], eventPosition: Point) => Point;
type ParsedDataType = { [key: string]: unknown };
interface LegendElement {
chart: Chart;
options: LegendOptions;
legendItems?: LegendItem[];
}
```

View file

@ -0,0 +1,469 @@
# Scales
Data-to-pixel conversion and axis rendering for different data types. Scales handle the transformation between data values and pixel coordinates on the chart canvas.
## Capabilities
### Linear Scale
Handles linear numeric data with equal intervals.
```javascript { .api }
class LinearScale extends Scale {
static id: 'linear';
static defaults: object;
getPixelForValue(value: number): number;
getValueForPixel(pixel: number): number;
getLabelForValue(value: number): string;
getDecimalForPixel(pixel: number): number;
}
interface LinearScaleOptions {
type: 'linear';
position?: 'left' | 'right' | 'top' | 'bottom' | 'center' | { [scaleId: string]: number };
axis?: 'x' | 'y';
offset?: boolean;
beginAtZero?: boolean;
bounds?: 'data' | 'ticks';
clip?: boolean;
grace?: number | string;
min?: number;
max?: number;
suggestedMin?: number;
suggestedMax?: number;
ticks?: TickOptions;
title?: TitleOptions;
grid?: GridLineOptions;
border?: BorderOptions;
}
```
**Usage Example:**
```javascript
const config = {
type: 'line',
data: { /* data */ },
options: {
scales: {
y: {
type: 'linear',
beginAtZero: true,
min: 0,
max: 100,
ticks: {
stepSize: 10
}
}
}
}
};
```
### Category Scale
Handles categorical/ordinal data with discrete labels.
```javascript { .api }
class CategoryScale extends Scale {
static id: 'category';
static defaults: object;
getPixelForValue(value: string | number): number;
getValueForPixel(pixel: number): number;
getLabelForValue(value: string | number): string;
getLabels(): string[];
}
interface CategoryScaleOptions {
type: 'category';
position?: 'left' | 'right' | 'top' | 'bottom' | 'center' | { [scaleId: string]: number };
axis?: 'x' | 'y';
offset?: boolean;
labels?: string[];
min?: string | number;
max?: string | number;
ticks?: TickOptions;
title?: TitleOptions;
grid?: GridLineOptions;
border?: BorderOptions;
}
```
**Usage Example:**
```javascript
const config = {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green'],
datasets: [{ /* dataset */ }]
},
options: {
scales: {
x: {
type: 'category',
labels: ['Custom Red', 'Custom Blue', 'Custom Yellow', 'Custom Green']
}
}
}
};
```
### Logarithmic Scale
Handles logarithmic numeric data with exponential intervals.
```javascript { .api }
class LogarithmicScale extends Scale {
static id: 'logarithmic';
static defaults: object;
getPixelForValue(value: number): number;
getValueForPixel(pixel: number): number;
getLabelForValue(value: number): string;
}
interface LogarithmicScaleOptions {
type: 'logarithmic';
position?: 'left' | 'right' | 'top' | 'bottom' | 'center' | { [scaleId: string]: number };
axis?: 'x' | 'y';
min?: number;
max?: number;
suggestedMin?: number;
suggestedMax?: number;
ticks?: TickOptions;
title?: TitleOptions;
grid?: GridLineOptions;
border?: BorderOptions;
}
```
### Time Scale
Handles time-based data with temporal intervals.
```javascript { .api }
class TimeScale extends Scale {
static id: 'time';
static defaults: object;
getPixelForValue(value: string | number | Date): number;
getValueForPixel(pixel: number): number;
getLabelForValue(value: string | number | Date): string;
}
interface TimeScaleOptions {
type: 'time';
position?: 'left' | 'right' | 'top' | 'bottom' | 'center' | { [scaleId: string]: number };
axis?: 'x' | 'y';
adapters?: DateAdapter;
bounds?: 'data' | 'ticks';
offset?: boolean;
min?: string | number | Date;
max?: string | number | Date;
suggestedMin?: string | number | Date;
suggestedMax?: string | number | Date;
time?: TimeOptions;
ticks?: TickOptions;
title?: TitleOptions;
grid?: GridLineOptions;
border?: BorderOptions;
}
interface TimeOptions {
displayFormats?: {
millisecond?: string;
second?: string;
minute?: string;
hour?: string;
day?: string;
week?: string;
month?: string;
quarter?: string;
year?: string;
};
isoWeekday?: boolean | number;
parser?: string | ((value: unknown) => Date);
round?: TimeUnit;
tooltipFormat?: string;
unit?: TimeUnit;
stepSize?: number;
minUnit?: TimeUnit;
}
type TimeUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
interface DateAdapter {
formats(): { [key: string]: string };
parse(value: unknown, format?: string): Date | null;
format(time: Date, format: string): string;
add(time: Date, amount: number, unit: TimeUnit): Date;
diff(max: Date, min: Date, unit: TimeUnit): number;
startOf(time: Date, unit: TimeUnit, weekday?: number): Date;
endOf(time: Date, unit: TimeUnit): Date;
}
```
**Usage Example:**
```javascript
const config = {
type: 'line',
data: {
datasets: [{
label: 'Time Series',
data: [
{ x: '2023-01-01', y: 10 },
{ x: '2023-02-01', y: 20 },
{ x: '2023-03-01', y: 15 }
]
}]
},
options: {
scales: {
x: {
type: 'time',
time: {
unit: 'month',
displayFormats: {
month: 'MMM YYYY'
}
}
}
}
}
};
```
### Time Series Scale
Optimized time scale for large time series datasets.
```javascript { .api }
class TimeSeriesScale extends TimeScale {
static id: 'timeseries';
static defaults: object;
}
interface TimeSeriesScaleOptions extends TimeScaleOptions {
type: 'timeseries';
bounds: 'data'; // Always 'data' for timeseries
offset: false; // Always false for timeseries
}
```
### Radial Linear Scale
Handles radial numeric data for radar charts.
```javascript { .api }
class RadialLinearScale extends Scale {
static id: 'radialLinear';
static defaults: object;
getPixelForValue(value: number, index?: number): number;
getValueForPixel(pixel: number): number;
getLabelForValue(value: number): string;
getIndexAngle(index: number): number;
getDistanceFromCenterForValue(value: number): number;
getValueForDistanceFromCenter(distance: number): number;
}
interface RadialLinearScaleOptions {
type: 'radialLinear';
animate?: boolean;
angleLines?: AngleLineOptions;
beginAtZero?: boolean;
grid?: GridLineOptions;
min?: number;
max?: number;
pointLabels?: PointLabelOptions;
startAngle?: number;
ticks?: TickOptions;
title?: TitleOptions;
}
interface AngleLineOptions {
display?: boolean;
color?: Color;
lineWidth?: number;
borderDash?: number[];
borderDashOffset?: number;
}
interface PointLabelOptions {
display?: boolean;
callback?: (label: string, index: number) => string;
color?: Color;
font?: FontSpec;
padding?: number;
centerPointLabels?: boolean;
}
```
### Base Scale Interface
Common interface implemented by all scales.
```javascript { .api }
interface Scale {
readonly id: string;
readonly type: string;
readonly ctx: CanvasRenderingContext2D;
readonly chart: Chart;
// Core conversion methods
getPixelForValue(value: unknown, index?: number): number;
getValueForPixel(pixel: number): unknown;
getPixelForTick(index: number): number;
getPixelForDecimal(decimal: number): number;
getDecimalForPixel(pixel: number): number;
getBaseValue(): number;
getBasePixel(): number;
// Label methods
getLabelForValue(value: unknown): string;
// Lifecycle methods
beforeLayout(): void;
afterLayout(): void;
beforeUpdate(mode: UpdateMode): void;
update(width: number, height: number, margins: ChartArea): void;
afterUpdate(mode: UpdateMode): void;
// Drawing methods
beforeDraw(): void;
draw(chartArea: ChartArea): void;
afterDraw(): void;
// Utility methods
isHorizontal(): boolean;
buildTicks(): Tick[];
configure(): void;
fit(): void;
}
```
## Scale Registration
Scales must be registered before use in charts.
```javascript { .api }
import {
Chart,
LinearScale,
CategoryScale,
TimeScale,
LogarithmicScale,
RadialLinearScale,
TimeSeriesScale
} from 'chart.js';
// Register scales
Chart.register(
LinearScale,
CategoryScale,
TimeScale,
LogarithmicScale,
RadialLinearScale,
TimeSeriesScale
);
```
## Types
```javascript { .api }
type ScaleType = 'linear' | 'logarithmic' | 'category' | 'time' | 'timeseries' | 'radialLinear';
interface TickOptions {
display?: boolean;
align?: 'start' | 'center' | 'end' | 'inner';
callback?: (tickValue: number | string, index: number, ticks: Tick[]) => string | string[] | number | number[];
color?: Color | Color[];
count?: number;
crossAlign?: 'near' | 'center' | 'far';
font?: FontSpec | FontSpec[];
includeBounds?: boolean;
labelOffset?: number;
maxRotation?: number;
maxTicksLimit?: number;
minRotation?: number;
mirror?: boolean;
padding?: number;
precision?: number;
sampleSize?: number;
showLabelBackdrop?: boolean;
source?: 'auto' | 'data' | 'labels';
stepSize?: number;
textStrokeColor?: Color | Color[];
textStrokeWidth?: number | number[];
z?: number;
}
interface TitleOptions {
display?: boolean;
align?: 'start' | 'center' | 'end';
color?: Color;
font?: FontSpec;
padding?: Padding;
text?: string | string[];
}
interface GridLineOptions {
display?: boolean;
circular?: boolean;
color?: Color | Color[];
lineWidth?: number | number[];
drawBorder?: boolean;
drawOnChartArea?: boolean;
drawTicks?: boolean;
tickBorderDash?: number[];
tickBorderDashOffset?: number;
tickColor?: Color | Color[];
tickLength?: number;
tickWidth?: number;
offset?: boolean;
z?: number;
}
interface BorderOptions {
display?: boolean;
color?: Color;
width?: number;
dash?: number[];
dashOffset?: number;
z?: number;
}
interface Tick {
value: number;
label?: string;
major?: boolean;
}
interface ChartArea {
left: number;
top: number;
right: number;
bottom: number;
width: number;
height: number;
}
interface FontSpec {
family?: string;
size?: number;
style?: 'normal' | 'italic' | 'oblique';
weight?: string | number;
lineHeight?: number | string;
}
interface Padding {
top?: number;
right?: number;
bottom?: number;
left?: number;
}
```

View file

@ -0,0 +1,495 @@
# Utilities
Helper functions for common operations, calculations, and canvas manipulation. The helpers module provides utility functions organized by category for various chart-related tasks.
## Capabilities
### Color Utilities
Functions for color parsing, manipulation, and conversion.
```javascript { .api }
/**
* Parse and create color objects
* @param value - Color string, gradient, or pattern
* @returns Color object with RGBA properties
*/
function color(value: string | CanvasGradient | CanvasPattern): Color;
/**
* Generate hover color variant
* @param color - Base color
* @returns Darker/lighter variant for hover state
*/
function getHoverColor(color: Color): Color;
/**
* Check if value is a pattern or gradient
* @param value - Value to check
* @returns True if pattern or gradient
*/
function isPatternOrGradient(value: unknown): value is CanvasGradient | CanvasPattern;
```
**Usage Example:**
```javascript
import { color, getHoverColor } from 'chart.js/helpers';
// Parse color
const blue = color('rgba(54, 162, 235, 0.8)');
console.log(blue.r, blue.g, blue.b, blue.a); // 54, 162, 235, 0.8
// Generate hover color
const hoverBlue = getHoverColor(blue);
```
### Core Utilities
Basic utility functions for type checking and value handling.
```javascript { .api }
/**
* Check if value is an array
* @param value - Value to check
* @returns True if array
*/
function isArray(value: unknown): value is unknown[];
/**
* Check if value is an object
* @param value - Value to check
* @returns True if object (not null, array, or primitive)
*/
function isObject(value: unknown): value is object;
/**
* Check if value is null or undefined
* @param value - Value to check
* @returns True if null or undefined
*/
function isNullOrUndef(value: unknown): value is null | undefined;
/**
* No-operation function
* @returns void
*/
function noop(): void;
/**
* Generate unique identifier
* @returns Unique string ID
*/
function uid(): string;
/**
* Return value or default if undefined
* @param value - Value to check
* @param defaultValue - Default to return
* @returns Value or default
*/
function valueOrDefault<T>(value: T | undefined, defaultValue: T): T;
/**
* Clamp value to range
* @param value - Value to clamp
* @param min - Minimum value
* @param max - Maximum value
* @returns Clamped value
*/
function clamp(value: number, min: number, max: number): number;
```
### Canvas Utilities
Functions for canvas drawing and manipulation.
```javascript { .api }
/**
* Clear entire canvas
* @param ctx - Canvas rendering context
*/
function clearCanvas(ctx: CanvasRenderingContext2D): void;
/**
* Clip canvas to specified area
* @param ctx - Canvas rendering context
* @param area - Area to clip to
*/
function clipArea(ctx: CanvasRenderingContext2D, area: ChartArea): void;
/**
* Restore canvas from clipping
* @param ctx - Canvas rendering context
* @param area - Previously clipped area
*/
function unclipArea(ctx: CanvasRenderingContext2D, area: ChartArea): void;
/**
* Draw point shape at coordinates
* @param ctx - Canvas rendering context
* @param options - Point style options
* @param x - X coordinate
* @param y - Y coordinate
*/
function drawPoint(ctx: CanvasRenderingContext2D, options: PointOptions, x: number, y: number): void;
/**
* Render text with alignment and rotation
* @param ctx - Canvas rendering context
* @param text - Text to render
* @param x - X coordinate
* @param y - Y coordinate
* @param options - Text rendering options
*/
function renderText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, options?: TextOptions): void;
/**
* Measure text dimensions
* @param ctx - Canvas rendering context
* @param text - Text to measure
* @param font - Font specification
* @returns Text metrics
*/
function measureText(ctx: CanvasRenderingContext2D, text: string, font?: FontSpec): TextMetrics;
```
**Usage Example:**
```javascript
import { clearCanvas, drawPoint, renderText } from 'chart.js/helpers';
const ctx = canvas.getContext('2d');
// Clear canvas
clearCanvas(ctx);
// Draw custom point
drawPoint(ctx, {
pointStyle: 'star',
radius: 10,
backgroundColor: 'gold'
}, 100, 100);
// Render text
renderText(ctx, 'Custom Label', 100, 120, {
color: 'black',
font: { size: 14, weight: 'bold' },
textAlign: 'center'
});
```
### Collection Utilities
Functions for array and object manipulation.
```javascript { .api }
/**
* Iterate over collection
* @param loopable - Array or object to iterate
* @param fn - Function to call for each item
* @param thisArg - Context for function calls
*/
function each<T>(loopable: T[] | { [key: string]: T }, fn: (item: T, index: number | string) => void, thisArg?: unknown): void;
/**
* Find index in array matching condition
* @param array - Array to search
* @param callback - Condition function
* @returns Index or -1 if not found
*/
function findIndex<T>(array: T[], callback: (item: T, index: number) => boolean): number;
/**
* Filter collection by condition
* @param collection - Collection to filter
* @param filterCallback - Filter function
* @returns Filtered array
*/
function where<T>(collection: T[], filterCallback: (item: T) => boolean): T[];
/**
* Get unique values from array
* @param array - Input array
* @returns Array with unique values
*/
function uniq<T>(array: T[]): T[];
/**
* Create array with specified length and fill value
* @param length - Array length
* @param value - Fill value
* @returns Filled array
*/
function array<T>(length: number, value: T): T[];
```
### Configuration Utilities
Functions for resolving and merging configuration options.
```javascript { .api }
/**
* Resolve scriptable options
* @param inputs - Option inputs to resolve
* @param context - Resolution context
* @param index - Data index
* @param info - Additional info object
* @returns Resolved value
*/
function resolve<T>(inputs: T[], context?: object, index?: number, info?: object): T;
/**
* Merge objects conditionally
* @param target - Target object
* @param source - Source object
* @returns Merged object
*/
function mergeIf(target: object, source: object): object;
/**
* Deep merge objects
* @param target - Target object
* @param source - Source object(s)
* @returns Merged object
*/
function merge(target: object, ...source: object[]): object;
/**
* Deep clone object or array
* @param source - Object to clone
* @returns Cloned object
*/
function clone<T>(source: T): T;
```
### DOM Utilities
Functions for DOM element manipulation and event handling.
```javascript { .api }
/**
* Get canvas element from various inputs
* @param item - Canvas, context, or selector
* @returns Canvas element
*/
function getCanvas(item: string | HTMLCanvasElement | CanvasRenderingContext2D): HTMLCanvasElement;
/**
* Get relative position of event within chart
* @param event - DOM event
* @param chart - Chart instance
* @returns Relative coordinates
*/
function getRelativePosition(event: Event, chart: Chart): Point;
/**
* Calculate maximum size for canvas
* @param canvas - Canvas element
* @param width - Desired width
* @param height - Desired height
* @param aspectRatio - Aspect ratio to maintain
* @returns Maximum size object
*/
function getMaximumSize(canvas: HTMLCanvasElement, width?: number, height?: number, aspectRatio?: number): Size;
/**
* Get device pixel ratio
* @returns Device pixel ratio
*/
function getDevicePixelRatio(): number;
/**
* Add event listener with options
* @param element - DOM element
* @param type - Event type
* @param listener - Event listener
* @param options - Event options
*/
function addEventListener(element: Element, type: string, listener: EventListener, options?: AddEventListenerOptions): void;
/**
* Remove event listener
* @param element - DOM element
* @param type - Event type
* @param listener - Event listener
*/
function removeEventListener(element: Element, type: string, listener: EventListener): void;
```
### Math Utilities
Mathematical helper functions for calculations.
```javascript { .api }
/**
* Check if two numbers are approximately equal
* @param x - First number
* @param y - Second number
* @param epsilon - Tolerance
* @returns True if approximately equal
*/
function almostEquals(x: number, y: number, epsilon: number): boolean;
/**
* Check if number is almost a whole number
* @param x - Number to check
* @param epsilon - Tolerance
* @returns True if almost whole
*/
function almostWhole(x: number, epsilon: number): boolean;
/**
* Convert degrees to radians
* @param degrees - Angle in degrees
* @returns Angle in radians
*/
function toRadians(degrees: number): number;
/**
* Convert radians to degrees
* @param radians - Angle in radians
* @returns Angle in degrees
*/
function toDegrees(radians: number): number;
/**
* Calculate distance between two points
* @param pt1 - First point
* @param pt2 - Second point
* @returns Distance
*/
function distanceBetweenPoints(pt1: Point, pt2: Point): number;
/**
* Linear interpolation between values
* @param a - Start value
* @param b - End value
* @param t - Interpolation factor (0-1)
* @returns Interpolated value
*/
function lerp(a: number, b: number, t: number): number;
/**
* Get angle of line between two points
* @param pt1 - First point
* @param pt2 - Second point
* @returns Angle in radians
*/
function getAngleFromPoint(pt1: Point, pt2: Point): number;
```
### RTL (Right-to-Left) Utilities
Functions for right-to-left text support.
```javascript { .api }
/**
* Get RTL helper for canvas
* @param rtl - RTL mode
* @param canvas - Canvas element
* @returns RTL helper object
*/
function getRtlHelper(rtl: boolean, canvas: HTMLCanvasElement): RtlHelper;
interface RtlHelper {
x(x: number): number;
setWidth(width: number): void;
textAlign(align: string): string;
xPlus(x: number, value: number): number;
leftForLtr(x: number, itemWidth: number): number;
}
```
### Interpolation Utilities
Functions for value interpolation and animation.
```javascript { .api }
/**
* Interpolate between objects
* @param from - Start object
* @param to - End object
* @param factor - Interpolation factor (0-1)
* @returns Interpolated object
*/
function interpolate<T>(from: T, to: T, factor: number): T;
/**
* Get interpolator function for property
* @param property - Property name
* @returns Interpolator function
*/
function getInterpolator(property: string): (from: unknown, to: unknown, factor: number) => unknown;
```
## Helper Module Access
All helpers are available through the helpers namespace:
```javascript { .api }
import { helpers } from 'chart.js';
// Access utilities via helpers object
const { color, isArray, clearCanvas } = helpers;
// Or import specific helpers
import { color, isArray, clearCanvas } from 'chart.js/helpers';
```
## Types
```javascript { .api }
interface Point {
x: number;
y: number;
}
interface Size {
width: number;
height: number;
}
interface ChartArea {
left: number;
top: number;
right: number;
bottom: number;
width: number;
height: number;
}
interface Color {
r: number;
g: number;
b: number;
a: number;
}
interface FontSpec {
family?: string;
size?: number;
style?: 'normal' | 'italic' | 'oblique';
weight?: string | number;
lineHeight?: number | string;
}
interface PointOptions {
pointStyle: PointStyle | HTMLImageElement | HTMLCanvasElement;
radius: number;
backgroundColor?: Color;
borderColor?: Color;
borderWidth?: number;
rotation?: number;
}
interface TextOptions {
color?: Color;
font?: FontSpec;
textAlign?: CanvasTextAlign;
textBaseline?: CanvasTextBaseline;
rotation?: number;
}
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
```

View file

@ -0,0 +1,418 @@
# Visual Elements
Core visual components for rendering chart elements. Elements handle the drawing and interaction logic for different visual shapes used in charts.
## Capabilities
### Bar Element
Renders rectangular bars for bar charts.
```javascript { .api }
class BarElement extends Element {
static id: 'bar';
static defaults: object;
static defaultRoutes: object;
// Properties
x: number;
y: number;
base: number;
horizontal: boolean;
width: number;
height: number;
// Methods
draw(ctx: CanvasRenderingContext2D): void;
inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean;
inXRange(mouseX: number, useFinalPosition?: boolean): boolean;
inYRange(mouseY: number, useFinalPosition?: boolean): boolean;
getCenterPoint(useFinalPosition?: boolean): Point;
getRange(axis: 'x' | 'y'): number;
}
interface BarElementOptions {
backgroundColor: Color;
borderColor: Color;
borderSkipped: 'start' | 'end' | 'middle' | 'bottom' | 'left' | 'top' | 'right' | boolean;
borderWidth: number;
borderRadius: number | BorderRadius;
inflateAmount: number | 'auto';
}
```
**Usage Example:**
```javascript
// Bar element styling
const config = {
type: 'bar',
data: { /* data */ },
options: {
elements: {
bar: {
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 2,
borderRadius: 4,
borderSkipped: false
}
}
}
};
```
### Line Element
Renders line paths connecting data points.
```javascript { .api }
class LineElement extends Element {
static id: 'line';
static defaults: object;
static defaultRoutes: object;
// Properties
points: PointElement[];
segments: LineSegment[];
first: boolean;
last: boolean;
smooth: boolean;
// Methods
draw(ctx: CanvasRenderingContext2D): void;
interpolate(point: PointElement, property: string): unknown;
pathSegment(ctx: CanvasRenderingContext2D, segment: LineSegment, params: SegmentParams): boolean | void;
path(ctx: CanvasRenderingContext2D): boolean | void;
}
interface LineElementOptions {
backgroundColor: Color;
borderCapStyle: CanvasLineCap;
borderColor: Color;
borderDash: number[];
borderDashOffset: number;
borderJoinStyle: CanvasLineJoin;
borderWidth: number;
fill: boolean | string | number | object;
stepped: boolean | 'before' | 'after' | 'middle';
tension: number;
}
interface LineSegment {
start: number;
end: number;
loop: boolean;
}
interface SegmentParams {
move: boolean;
reverse: boolean;
}
```
**Usage Example:**
```javascript
// Line element styling
const config = {
type: 'line',
data: { /* data */ },
options: {
elements: {
line: {
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 3,
borderDash: [5, 5],
borderCapStyle: 'round',
borderJoinStyle: 'round',
fill: false,
tension: 0.4
}
}
}
};
```
### Point Element
Renders individual data points and markers.
```javascript { .api }
class PointElement extends Element {
static id: 'point';
static defaults: object;
static defaultRoutes: object;
// Properties
x: number;
y: number;
size: number;
// Methods
draw(ctx: CanvasRenderingContext2D, area?: ChartArea): void;
inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean;
inXRange(mouseX: number, useFinalPosition?: boolean): boolean;
inYRange(mouseY: number, useFinalPosition?: boolean): boolean;
getCenterPoint(useFinalPosition?: boolean): Point;
size(options?: PointElementOptions): number;
resolveElementProperties(chart: Chart, options: PointElementOptions): void;
}
interface PointElementOptions {
backgroundColor: Color;
borderColor: Color;
borderWidth: number;
hitRadius: number;
hoverBackgroundColor: Color;
hoverBorderColor: Color;
hoverBorderWidth: number;
hoverRadius: number;
pointStyle: PointStyle | HTMLImageElement | HTMLCanvasElement;
radius: number;
rotation: number;
}
type PointStyle = 'circle' | 'cross' | 'crossRot' | 'dash' | 'line' | 'rect' | 'rectRounded' | 'rectRot' | 'star' | 'triangle';
```
**Usage Example:**
```javascript
// Point element styling
const config = {
type: 'line',
data: { /* data */ },
options: {
elements: {
point: {
backgroundColor: 'rgba(255, 206, 86, 1)',
borderColor: 'rgba(255, 206, 86, 1)',
borderWidth: 2,
radius: 6,
pointStyle: 'circle',
hoverRadius: 8,
hoverBorderWidth: 3
}
}
}
};
```
### Arc Element
Renders arc/sector shapes for pie and doughnut charts.
```javascript { .api }
class ArcElement extends Element {
static id: 'arc';
static defaults: object;
static defaultRoutes: object;
// Properties
x: number;
y: number;
startAngle: number;
endAngle: number;
innerRadius: number;
outerRadius: number;
circumference: number;
// Methods
draw(ctx: CanvasRenderingContext2D): void;
inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean;
inXRange(mouseX: number, useFinalPosition?: boolean): boolean;
inYRange(mouseY: number, useFinalPosition?: boolean): boolean;
getCenterPoint(useFinalPosition?: boolean): Point;
getAngle(): number;
tooltipPosition(): Point;
}
interface ArcElementOptions {
angle: number;
backgroundColor: Color;
borderAlign: 'center' | 'inner';
borderColor: Color;
borderJoinStyle: CanvasLineJoin;
borderRadius: number | ArcBorderRadius;
borderWidth: number;
offset: number;
spacing: number;
circular: boolean;
}
interface ArcBorderRadius {
outerStart?: number;
outerEnd?: number;
innerStart?: number;
innerEnd?: number;
}
```
**Usage Example:**
```javascript
// Arc element styling
const config = {
type: 'doughnut',
data: { /* data */ },
options: {
elements: {
arc: {
backgroundColor: 'rgba(153, 102, 255, 0.2)',
borderColor: 'rgba(153, 102, 255, 1)',
borderWidth: 2,
borderAlign: 'center',
spacing: 2,
borderRadius: 4
}
}
}
};
```
### Base Element Interface
Common interface implemented by all elements.
```javascript { .api }
interface Element {
// Properties
readonly chart: Chart;
active: boolean;
options: ElementOptions;
// Core methods
draw(ctx: CanvasRenderingContext2D, area?: ChartArea): void;
// Hit detection methods
inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean;
inXRange(mouseX: number, useFinalPosition?: boolean): boolean;
inYRange(mouseY: number, useFinalPosition?: boolean): boolean;
// Position methods
getCenterPoint(useFinalPosition?: boolean): Point;
getRange?(axis: 'x' | 'y'): number;
// Lifecycle methods
tooltipPosition?(useFinalPosition?: boolean): Point;
hasValue?(): boolean;
getProps?(props: string[], final?: boolean): { [key: string]: unknown };
}
```
### Element Factory
Helper for creating element instances.
```javascript { .api }
interface ElementFactory {
create<T extends Element>(type: string, scope: object, values: object): T;
update<T extends Element>(element: T, properties: object, active: boolean): void;
}
```
## Element Registration
Elements must be registered before use in charts.
```javascript { .api }
import {
Chart,
BarElement,
LineElement,
PointElement,
ArcElement
} from 'chart.js';
// Register elements
Chart.register(
BarElement,
LineElement,
PointElement,
ArcElement
);
```
## Animation Properties
Elements support property animation during chart updates.
```javascript { .api }
interface AnimatableElement {
// Properties that can be animated
x?: number;
y?: number;
width?: number;
height?: number;
radius?: number;
innerRadius?: number;
outerRadius?: number;
startAngle?: number;
endAngle?: number;
backgroundColor?: Color;
borderColor?: Color;
borderWidth?: number;
}
```
**Usage Example:**
```javascript
// Custom animation configuration
const config = {
type: 'bar',
data: { /* data */ },
options: {
animation: {
duration: 2000,
easing: 'easeInOutQuart'
},
animations: {
x: {
type: 'number',
easing: 'linear',
duration: 1000,
from: 0
},
y: {
type: 'number',
easing: 'easeInOutCubic',
duration: 1500,
delay: 500
}
}
}
};
```
## Types
```javascript { .api }
interface Point {
x: number;
y: number;
}
interface BorderRadius {
topLeft: number;
topRight: number;
bottomLeft: number;
bottomRight: number;
}
interface ChartArea {
left: number;
top: number;
right: number;
bottom: number;
width: number;
height: number;
}
type Color = string | CanvasGradient | CanvasPattern;
interface ElementOptions {
[key: string]: unknown;
}
```

View file

@ -0,0 +1,8 @@
{
"name": "tessl/npm-chart-js",
"version": "4.3.0",
"docs": "docs/index.md",
"describes": "pkg:npm/chart.js@4.3.3",
"summary": "Simple HTML5 charts using the canvas element.",
"private": false
}

View file

@ -0,0 +1,237 @@
# clsx
clsx is a tiny (239B) utility for constructing className strings conditionally. It serves as a faster and smaller drop-in replacement for the popular classnames module, supporting various input types including strings, objects, arrays, and booleans while automatically filtering out falsy values.
## Package Information
- **Package Name**: clsx
- **Package Type**: npm
- **Language**: JavaScript with TypeScript definitions
- **Installation**: `npm install clsx`
## Core Imports
ES Module (default):
```javascript
import clsx from "clsx";
```
ES Module (named):
```javascript
import { clsx } from "clsx";
```
CommonJS:
```javascript
const clsx = require("clsx");
```
Lite version (string-only):
```javascript
import clsx from "clsx/lite";
// or
import { clsx } from "clsx/lite";
```
## Basic Usage
```javascript
import clsx from 'clsx';
// Strings (variadic)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
// Objects
clsx({ foo: true, bar: false, baz: isTrue() });
//=> 'foo baz'
// Arrays
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'
// Mixed arguments (kitchen sink)
clsx('foo', [1 && 'bar', { baz: false, bat: null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'
```
## Capabilities
### Main clsx Function
The primary function that accepts any number of arguments of various types and returns a consolidated className string.
```typescript { .api }
function clsx(...inputs: ClassValue[]): string;
```
**Arguments:**
- `inputs` - Any number of ClassValue arguments (strings, numbers, booleans, objects, arrays, null, undefined)
**Returns:**
- `string` - Consolidated className string with space-separated class names
**Behavior:**
- Falsy values (false, 0, '', null, undefined, NaN) are ignored
- Standalone boolean values are discarded
- String and number values are included directly
- Object keys with truthy values are included as class names
- Arrays are processed recursively, flattening nested structures
- BigInt values are converted to strings and included
### Lite Version
A lightweight variant that only processes string arguments, ignoring all other input types.
```typescript { .api }
function clsx(...inputs: string[]): string;
```
**Arguments:**
- `inputs` - Any number of string arguments (non-string arguments are ignored)
**Returns:**
- `string` - Consolidated className string from valid string inputs only
**Usage:**
```javascript
import clsx from 'clsx/lite';
// Only strings are processed
clsx('hello', true && 'foo', false && 'bar');
//=> "hello foo"
// Non-string inputs are ignored
clsx({ foo: true }, ['bar'], 42);
//=> ""
```
## Types
```typescript { .api }
type ClassValue = ClassArray | ClassDictionary | string | number | bigint | null | boolean | undefined;
type ClassDictionary = Record<string, any>;
type ClassArray = ClassValue[];
```
**Type Descriptions:**
- `ClassValue` - Union type representing all valid input types for clsx
- `ClassDictionary` - Object type where keys are class names and values determine inclusion
- `ClassArray` - Recursive array type allowing nested ClassValue items
## Input Type Behavior
### Strings
- Included directly in the output
- Empty strings are ignored
- Whitespace is preserved as provided
### Numbers
- Converted to strings and included
- Zero (0) is treated as falsy and ignored
- Infinity is converted to "Infinity"
- NaN is treated as falsy and ignored
### Booleans
- Used for conditional logic but not included in output
- Commonly used with logical operators: `condition && 'class-name'`
### Objects
- Keys with truthy values are included as class names
- Keys with falsy values are ignored
- Nested objects within arrays are processed normally
### Arrays
- Processed recursively, supporting unlimited nesting
- Each element is evaluated according to its type
- Empty arrays are ignored
### Null/Undefined
- Always ignored and filtered out
- Safe to pass without conditional checks
## Usage Patterns
### Conditional Classes
```javascript
clsx('base-class', {
'active': isActive,
'disabled': !isEnabled,
'error': hasError
});
```
### Mixed Input Types
```javascript
clsx(
'always-present',
condition && 'conditional-class',
{
'object-based': someBoolean,
'another-class': anotherCondition
},
['array', 'classes', nested && 'nested-class']
);
```
### React Component Example
```javascript
function Button({ variant, size, disabled, className, children }) {
return (
<button
className={clsx(
'btn',
`btn-${variant}`,
`btn-${size}`,
{
'btn-disabled': disabled
},
className
)}
>
{children}
</button>
);
}
```
### Tailwind CSS Integration
```javascript
// Optimal for Tailwind with clsx/lite
import clsx from 'clsx/lite';
const classes = clsx(
'text-base',
props.active && 'text-primary',
props.className
);
```
## Distribution Formats
### Main Package
- **CommonJS**: `dist/clsx.js`
- **ES Module**: `dist/clsx.mjs`
- **UMD**: `dist/clsx.min.js`
- **Size**: 239 bytes (gzipped)
### Lite Package
- **CommonJS**: `dist/lite.js`
- **ES Module**: `dist/lite.mjs`
- **Size**: 140 bytes (gzipped)
## Browser Support
- **Modern Browsers**: All browsers supporting Array.isArray (IE9+)
- **Node.js**: All versions 6+
- **Legacy Support**: clsx@1.0.x for IE8 and below
## Error Handling
clsx is designed to be fault-tolerant:
- Accepts any input without throwing errors
- Gracefully handles undefined, null, and unexpected types
- No validation errors - invalid inputs are simply ignored
- Safe to use with dynamic or untrusted input data

View file

@ -0,0 +1,7 @@
{
"name": "tessl/npm-clsx",
"version": "2.1.0",
"docs": "docs/index.md",
"describes": "pkg:npm/clsx@2.1.1",
"summary": "A tiny (239B) utility for constructing className strings conditionally."
}

View file

@ -0,0 +1,377 @@
# Date Arithmetic
Date arithmetic functions provide core date manipulation capabilities for adding, subtracting, and calculating differences between dates. All functions are pure and return new date instances without modifying the input dates.
## Add Functions
### add
Add a duration to a date.
```typescript { .api }
function add<DateType extends Date>(
date: DateArg<DateType>,
duration: Duration
): DateType;
```
**Parameters:**
- `date` - The date to add the duration to
- `duration` - The duration object specifying amounts to add
**Example:**
```typescript
import { add } from "date-fns";
const result = add(new Date(2014, 8, 1), {
years: 2,
months: 9,
weeks: 1,
days: 7,
hours: 5,
minutes: 9,
seconds: 30,
});
//=> Sun Jun 15 2017 05:09:30
```
### Individual Add Functions
Add specific time units to a date.
```typescript { .api }
function addYears<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addMonths<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addQuarters<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addWeeks<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addDays<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addBusinessDays<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addHours<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addMinutes<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addSeconds<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addMilliseconds<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function addISOWeekYears<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
```
**Parameters:**
- `date` - The date to add to
- `amount` - The amount to add (can be negative for subtraction)
**Examples:**
```typescript
import { addDays, addBusinessDays, addMonths } from "date-fns";
// Add 10 days
addDays(new Date(2014, 8, 1), 10);
//=> Thu Sep 11 2014
// Add 10 business days (skips weekends)
addBusinessDays(new Date(2014, 8, 1), 10);
//=> Mon Sep 15 2014
// Add 2 months
addMonths(new Date(2014, 8, 1), 2);
//=> Wed Nov 01 2014
```
## Subtract Functions
### sub
Subtract a duration from a date.
```typescript { .api }
function sub<DateType extends Date>(
date: DateArg<DateType>,
duration: Duration
): DateType;
```
**Parameters:**
- `date` - The date to subtract the duration from
- `duration` - The duration object specifying amounts to subtract
**Example:**
```typescript
import { sub } from "date-fns";
const result = sub(new Date(2014, 8, 1), {
years: 2,
months: 9,
weeks: 1,
days: 7,
hours: 5,
minutes: 9,
seconds: 30,
});
//=> Tue Sep 04 2012
```
### Individual Subtract Functions
Subtract specific time units from a date.
```typescript { .api }
function subYears<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subMonths<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subQuarters<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subWeeks<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subDays<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subBusinessDays<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subHours<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subMinutes<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subSeconds<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subMilliseconds<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
function subISOWeekYears<DateType extends Date>(date: DateArg<DateType>, amount: number): DateType;
```
## Difference Functions
Calculate the difference between two dates in various units.
```typescript { .api }
function differenceInYears(laterDate: DateArg<Date>, earlierDate: DateArg<Date>, options?: DifferenceInYearsOptions): number;
function differenceInMonths(laterDate: DateArg<Date>, earlierDate: DateArg<Date>, options?: DifferenceInMonthsOptions): number;
function differenceInQuarters(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInWeeks(laterDate: DateArg<Date>, earlierDate: DateArg<Date>, options?: DifferenceInWeeksOptions): number;
function differenceInDays(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInBusinessDays(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInHours(laterDate: DateArg<Date>, earlierDate: DateArg<Date>, options?: DifferenceInHoursOptions): number;
function differenceInMinutes(dateLeft: DateArg<Date>, dateRight: DateArg<Date>, options?: DifferenceInMinutesOptions): number;
function differenceInSeconds(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInMilliseconds(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
```
**Parameters:**
- `laterDate` - The later date (or `dateLeft` for differenceInMinutes)
- `earlierDate` - The earlier date (or `dateRight` for differenceInMinutes)
- `options` - Configuration options for supported functions
**Returns:** The number of units between the dates (positive if laterDate > earlierDate)
**Examples:**
```typescript
import { differenceInDays, differenceInHours, differenceInMonths } from "date-fns";
// Days difference
differenceInDays(new Date(2014, 6, 2), new Date(2014, 0, 1));
//=> 182
// Hours difference
differenceInHours(new Date(2014, 6, 2, 06, 0), new Date(2014, 6, 2, 19, 0));
//=> -13
// Months difference
differenceInMonths(new Date(2014, 6, 2), new Date(2012, 0, 1));
//=> 30
```
## Calendar Difference Functions
Calculate differences based on calendar periods rather than exact time spans.
```typescript { .api }
function differenceInCalendarYears(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInCalendarMonths(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInCalendarQuarters(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInCalendarWeeks(laterDate: DateArg<Date>, earlierDate: DateArg<Date>, options?: DifferenceInCalendarWeeksOptions): number;
function differenceInCalendarDays(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInCalendarISOWeeks(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
function differenceInCalendarISOWeekYears(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
```
**Example:**
```typescript
import { differenceInCalendarMonths, differenceInMonths } from "date-fns";
// Calendar months (counts month boundaries)
differenceInCalendarMonths(new Date(2014, 6, 2), new Date(2014, 0, 31));
//=> 6
// Full months (30+ day periods)
differenceInMonths(new Date(2014, 6, 2), new Date(2014, 0, 31));
//=> 5
```
## ISO Week Functions
ISO week year arithmetic for international week-based calculations.
```typescript { .api }
function differenceInISOWeekYears(laterDate: DateArg<Date>, earlierDate: DateArg<Date>): number;
```
**Example:**
```typescript
import { differenceInISOWeekYears } from "date-fns";
differenceInISOWeekYears(new Date(2014, 6, 2), new Date(2012, 0, 1));
//=> 2
```
## Unit Conversion Functions
Convert between different time units for calculations and display.
```typescript { .api }
// Time unit conversions
function millisecondsToSeconds(milliseconds: number): number;
function millisecondsToMinutes(milliseconds: number): number;
function millisecondsToHours(milliseconds: number): number;
function secondsToMinutes(seconds: number): number;
function secondsToHours(seconds: number): number;
function secondsToMilliseconds(seconds: number): number;
function minutesToSeconds(minutes: number): number;
function minutesToHours(minutes: number): number;
function minutesToMilliseconds(minutes: number): number;
function hoursToMinutes(hours: number): number;
function hoursToSeconds(hours: number): number;
function hoursToMilliseconds(hours: number): number;
// Date unit conversions
function daysToWeeks(days: number): number;
function weeksToDays(weeks: number): number;
function monthsToQuarters(months: number): number;
function monthsToYears(months: number): number;
function quartersToMonths(quarters: number): number;
function quartersToYears(quarters: number): number;
function yearsToDays(years: number): number;
function yearsToMonths(years: number): number;
function yearsToQuarters(years: number): number;
```
**Examples:**
```typescript
import { hoursToMinutes, daysToWeeks, monthsToQuarters } from "date-fns";
hoursToMinutes(2); //=> 120
daysToWeeks(14); //=> 2
monthsToQuarters(9); //=> 3
```
## Utility Functions
### clamp
Clamp a date to fit within an interval.
```typescript { .api }
function clamp<DateType extends Date>(
date: DateArg<DateType>,
interval: Interval
): DateType;
```
**Example:**
```typescript
import { clamp } from "date-fns";
clamp(new Date(2014, 0, 1), {
start: new Date(2014, 0, 5),
end: new Date(2014, 0, 10)
});
//=> Sun Jan 05 2014 (clamped to start)
```
### intervalToDuration
Convert an interval to a duration object.
```typescript { .api }
function intervalToDuration(interval: Interval): Duration;
```
**Example:**
```typescript
import { intervalToDuration } from "date-fns";
intervalToDuration({
start: new Date(2014, 0, 1, 0, 0, 0),
end: new Date(2014, 0, 1, 0, 0, 15)
});
//=> { seconds: 15 }
```
## Rounding Functions
Round dates to the nearest specified time unit.
```typescript { .api }
function roundToNearestHours<DateType extends Date>(
date: DateArg<DateType>,
options?: RoundToNearestHoursOptions
): DateType;
function roundToNearestMinutes<DateType extends Date>(
date: DateArg<DateType>,
options?: RoundToNearestMinutesOptions
): DateType;
```
**Parameters:**
- `date` - The date to round
- `options` - Rounding configuration
**Examples:**
```typescript
import { roundToNearestHours, roundToNearestMinutes } from "date-fns";
// Round to nearest hour
roundToNearestHours(new Date(2014, 6, 10, 12, 30));
//=> Thu Jul 10 2014 13:00:00
// Round to nearest 15 minutes
roundToNearestMinutes(new Date(2014, 6, 10, 12, 7), { nearestTo: 15 });
//=> Thu Jul 10 2014 12:00:00
```
## Duration Conversion
### milliseconds
Convert a duration to milliseconds.
```typescript { .api }
function milliseconds(duration: Duration): number;
```
**Example:**
```typescript
import { milliseconds } from "date-fns";
milliseconds({ hours: 2, minutes: 30 });
//=> 9000000
```
## Options Interfaces
```typescript { .api }
interface DifferenceInYearsOptions extends ContextOptions<Date> {}
interface DifferenceInMonthsOptions extends ContextOptions<Date> {}
interface DifferenceInHoursOptions extends RoundingOptions, ContextOptions<Date> {}
interface DifferenceInMinutesOptions extends RoundingOptions {}
interface DifferenceInWeeksOptions extends RoundingOptions, ContextOptions<Date> {}
interface DifferenceInCalendarWeeksOptions extends LocalizedOptions<"options">, WeekOptions, ContextOptions<Date> {}
interface RoundToNearestHoursOptions<DateType extends Date = Date>
extends NearestToUnitOptions<NearestHours>, RoundingOptions, ContextOptions<DateType> {}
interface RoundToNearestMinutesOptions<DateType extends Date = Date>
extends NearestToUnitOptions<NearestMinutes>, RoundingOptions, ContextOptions<DateType> {}
interface RoundingOptions {
roundingMethod?: "ceil" | "floor" | "round" | "trunc";
}
interface NearestToUnitOptions<Unit extends number> {
nearestTo?: Unit;
}
type NearestHours = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
type NearestMinutes = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30;
```

View file

@ -0,0 +1,527 @@
# Date Components
Date component functions provide utilities for getting and setting individual date parts (year, month, day, hour, etc.) with proper handling of time zones and edge cases. All functions are pure and return new date instances.
## Get Functions
### Year Components
```typescript { .api }
function getYear(date: DateArg<Date>): number;
function getISOWeekYear(date: DateArg<Date>): number;
function getWeekYear(date: DateArg<Date>, options?: WeekYearOptions): number;
function getDecade(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { getYear, getISOWeekYear, getDecade } from "date-fns";
const date = new Date(2014, 1, 11);
getYear(date); //=> 2014
getISOWeekYear(date); //=> 2014
getDecade(date); //=> 2014
// Edge case: ISO week year can differ from calendar year
getYear(new Date(2005, 0, 1)); //=> 2005
getISOWeekYear(new Date(2005, 0, 1)); //=> 2004 (belongs to 2004's ISO week year)
```
### Month and Quarter Components
```typescript { .api }
function getMonth(date: DateArg<Date>): number;
function getQuarter(date: DateArg<Date>): number;
function getDaysInMonth(date: DateArg<Date>): number;
function getDaysInYear(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { getMonth, getQuarter, getDaysInMonth, getDaysInYear } from "date-fns";
const date = new Date(2014, 1, 11); // February 11, 2014
getMonth(date); //=> 1 (February, 0-indexed)
getQuarter(date); //=> 1 (Q1: Jan-Mar)
getDaysInMonth(date); //=> 28 (February 2014)
getDaysInYear(date); //=> 365 (2014 is not a leap year)
// Leap year
getDaysInYear(new Date(2012, 0, 1)); //=> 366
```
### Day Components
```typescript { .api }
function getDate(date: DateArg<Date>): number;
function getDay(date: DateArg<Date>): number;
function getISODay(date: DateArg<Date>): number;
function getDayOfYear(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { getDate, getDay, getISODay, getDayOfYear } from "date-fns";
const date = new Date(2014, 1, 11); // Tuesday, February 11, 2014
getDate(date); //=> 11 (day of month)
getDay(date); //=> 2 (Tuesday, 0=Sunday)
getISODay(date); //=> 2 (Tuesday, 1=Monday in ISO)
getDayOfYear(date); //=> 42 (42nd day of 2014)
```
### Week Components
```typescript { .api }
function getWeek(date: DateArg<Date>, options?: WeekOptions): number;
function getISOWeek(date: DateArg<Date>): number;
function getWeekOfMonth(date: DateArg<Date>, options?: WeekOptions): number;
function getWeeksInMonth(date: DateArg<Date>, options?: WeekOptions): number;
function getISOWeeksInYear(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { getWeek, getISOWeek, getWeekOfMonth } from "date-fns";
const date = new Date(2014, 1, 11);
getWeek(date); //=> 7 (7th week of 2014)
getISOWeek(date); //=> 7 (ISO week 7)
getWeekOfMonth(date); //=> 2 (2nd week of February)
```
### Time Components
```typescript { .api }
function getHours(date: DateArg<Date>): number;
function getMinutes(date: DateArg<Date>): number;
function getSeconds(date: DateArg<Date>): number;
function getMilliseconds(date: DateArg<Date>): number;
function getTime(date: DateArg<Date>): number;
function getUnixTime(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { getHours, getMinutes, getSeconds, getTime, getUnixTime } from "date-fns";
const date = new Date(2014, 1, 11, 14, 30, 45, 500);
getHours(date); //=> 14
getMinutes(date); //=> 30
getSeconds(date); //=> 45
getMilliseconds(date); //=> 500
getTime(date); //=> 1392123045500 (milliseconds since epoch)
getUnixTime(date); //=> 1392123045 (seconds since epoch)
```
## Set Functions
### Year Setting
```typescript { .api }
function setYear<DateType extends Date>(
date: DateArg<DateType>,
year: number
): DateType;
function setISOWeekYear<DateType extends Date>(
date: DateArg<DateType>,
isoWeekYear: number
): DateType;
function setWeekYear<DateType extends Date>(
date: DateArg<DateType>,
weekYear: number,
options?: WeekYearOptions
): DateType;
```
**Examples:**
```typescript
import { setYear, setISOWeekYear } from "date-fns";
const date = new Date(2014, 1, 11);
setYear(date, 2020);
//=> Tue Feb 11 2020
// Handle leap year edge case
setYear(new Date(2016, 1, 29), 2017); // Feb 29, 2016
//=> Tue Feb 28 2017 (2017 is not a leap year)
```
### Month and Quarter Setting
```typescript { .api }
function setMonth<DateType extends Date>(
date: DateArg<DateType>,
month: number
): DateType;
function setQuarter<DateType extends Date>(
date: DateArg<DateType>,
quarter: number
): DateType;
```
**Examples:**
```typescript
import { setMonth, setQuarter } from "date-fns";
const date = new Date(2014, 1, 11);
setMonth(date, 5); // Set to June (0-indexed)
//=> Wed Jun 11 2014
setQuarter(date, 3); // Set to Q3 (July)
//=> Fri Jul 11 2014
// Handle month overflow
setMonth(new Date(2014, 0, 31), 1); // Jan 31 to February
//=> Fri Feb 28 2014 (February doesn't have 31 days)
```
### Day Setting
```typescript { .api }
function setDate<DateType extends Date>(
date: DateArg<DateType>,
dayOfMonth: number
): DateType;
function setDay<DateType extends Date>(
date: DateArg<DateType>,
day: number,
options?: WeekOptions
): DateType;
function setISODay<DateType extends Date>(
date: DateArg<DateType>,
day: number
): DateType;
function setDayOfYear<DateType extends Date>(
date: DateArg<DateType>,
dayOfYear: number
): DateType;
```
**Examples:**
```typescript
import { setDate, setDay, setISODay, setDayOfYear } from "date-fns";
const date = new Date(2014, 1, 11); // Tuesday
setDate(date, 20);
//=> Thu Feb 20 2014
setDay(date, 0); // Set to Sunday
//=> Sun Feb 09 2014
setISODay(date, 1); // Set to Monday (ISO: 1=Monday)
//=> Mon Feb 10 2014
setDayOfYear(date, 100); // 100th day of year
//=> Thu Apr 10 2014
```
### Week Setting
```typescript { .api }
function setWeek<DateType extends Date>(
date: DateArg<DateType>,
week: number,
options?: WeekOptions
): DateType;
function setISOWeek<DateType extends Date>(
date: DateArg<DateType>,
isoWeek: number
): DateType;
```
**Examples:**
```typescript
import { setWeek, setISOWeek } from "date-fns";
const date = new Date(2014, 1, 11);
setWeek(date, 1); // First week of year
//=> Mon Jan 06 2014
setISOWeek(date, 1); // ISO week 1
//=> Mon Jan 06 2014
```
### Time Setting
```typescript { .api }
function setHours<DateType extends Date>(
date: DateArg<DateType>,
hours: number
): DateType;
function setMinutes<DateType extends Date>(
date: DateArg<DateType>,
minutes: number
): DateType;
function setSeconds<DateType extends Date>(
date: DateArg<DateType>,
seconds: number
): DateType;
function setMilliseconds<DateType extends Date>(
date: DateArg<DateType>,
milliseconds: number
): DateType;
```
**Examples:**
```typescript
import { setHours, setMinutes, setSeconds, setMilliseconds } from "date-fns";
const date = new Date(2014, 1, 11, 10, 30, 40, 500);
setHours(date, 14);
//=> Tue Feb 11 2014 14:30:40.500
setMinutes(date, 45);
//=> Tue Feb 11 2014 10:45:40.500
setSeconds(date, 0);
//=> Tue Feb 11 2014 10:30:00.500
setMilliseconds(date, 0);
//=> Tue Feb 11 2014 10:30:40.000
```
## Bulk Setting
### set
Set multiple date components at once.
```typescript { .api }
function set<DateType extends Date>(
date: DateArg<DateType>,
values: {
year?: number;
month?: number;
date?: number;
hours?: number;
minutes?: number;
seconds?: number;
milliseconds?: number;
}
): DateType;
```
**Examples:**
```typescript
import { set } from "date-fns";
const date = new Date(2014, 1, 11, 10, 30, 40, 500);
// Set multiple components
set(date, {
year: 2020,
month: 5, // June (0-indexed)
date: 15,
hours: 14,
minutes: 0,
seconds: 0,
milliseconds: 0
});
//=> Mon Jun 15 2020 14:00:00.000
// Partial setting
set(date, {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0
});
//=> Tue Feb 11 2014 00:00:00.000 (only time components changed)
```
## Configuration
### Default Options
```typescript { .api }
function getDefaultOptions(): DefaultOptions;
function setDefaultOptions(newOptions: DefaultOptions): void;
```
**Examples:**
```typescript
import { getDefaultOptions, setDefaultOptions } from "date-fns";
import { enGB } from "date-fns/locale";
// Get current defaults
const currentOptions = getDefaultOptions();
// Set new defaults
setDefaultOptions({
locale: enGB,
weekStartsOn: 1, // Monday
firstWeekContainsDate: 4
});
// Now all functions will use these defaults unless overridden
```
## Advanced Component Access
### Overlapping Intervals
```typescript { .api }
function getOverlappingDaysInIntervals(
intervalLeft: Interval,
intervalRight: Interval
): number;
```
**Example:**
```typescript
import { getOverlappingDaysInIntervals } from "date-fns";
const leftInterval = {
start: new Date(2014, 0, 10),
end: new Date(2014, 0, 20)
};
const rightInterval = {
start: new Date(2014, 0, 17),
end: new Date(2014, 0, 21)
};
getOverlappingDaysInIntervals(leftInterval, rightInterval);
//=> 4 (days 17, 18, 19, 20)
```
## Component Manipulation Patterns
### Date Normalization
```typescript
import { set } from "date-fns";
// Normalize to start of day
function normalizeToStartOfDay(date: Date): Date {
return set(date, {
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0
});
}
// Normalize to end of day
function normalizeToEndOfDay(date: Date): Date {
return set(date, {
hours: 23,
minutes: 59,
seconds: 59,
milliseconds: 999
});
}
```
### Safe Component Setting
```typescript
import { setMonth, getDaysInMonth, setDate } from "date-fns";
// Safely set month, handling day overflow
function safeSetMonth(date: Date, month: number): Date {
const tempDate = setMonth(date, month);
const daysInNewMonth = getDaysInMonth(tempDate);
const currentDay = getDate(date);
if (currentDay > daysInNewMonth) {
return setDate(tempDate, daysInNewMonth);
}
return tempDate;
}
```
### Component Validation
```typescript
import { isValid, set, getYear, getMonth, getDate } from "date-fns";
function isValidDateComponents(year: number, month: number, day: number): boolean {
try {
const testDate = set(new Date(), { year, month, date: day });
return isValid(testDate) &&
getYear(testDate) === year &&
getMonth(testDate) === month &&
getDate(testDate) === day;
} catch {
return false;
}
}
```
## Option Types
### WeekOptions
```typescript { .api }
interface WeekOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
```
### WeekYearOptions
```typescript { .api }
interface WeekYearOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
```
### DefaultOptions
```typescript { .api }
interface DefaultOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
```
## Edge Cases and Considerations
### Month Overflow
When setting months, date-fns automatically handles overflow:
```typescript
import { setMonth } from "date-fns";
// January 31 → February 28 (not March 3)
setMonth(new Date(2014, 0, 31), 1); //=> Feb 28, 2014
```
### Leap Year Handling
```typescript
import { setYear } from "date-fns";
// February 29 in leap year → February 28 in non-leap year
setYear(new Date(2016, 1, 29), 2017); //=> Feb 28, 2017
```
### Week Year vs Calendar Year
ISO week years can differ from calendar years at year boundaries:
```typescript
import { getYear, getISOWeekYear } from "date-fns";
const date = new Date(2005, 0, 1); // January 1, 2005 (Saturday)
getYear(date); //=> 2005
getISOWeekYear(date); //=> 2004 (belongs to last week of ISO year 2004)
```

View file

@ -0,0 +1,525 @@
# Constants and Utilities
Constants and utility functions provide mathematical constants for time calculations, utility functions for date construction, and helper functions for common operations. These form the foundation of date-fns functionality.
## Time Constants
### Basic Time Units
```typescript { .api }
const millisecondsInSecond: number = 1000;
const millisecondsInMinute: number = 60000;
const millisecondsInHour: number = 3600000;
const millisecondsInDay: number = 86400000;
const millisecondsInWeek: number = 604800000;
```
**Usage:**
```typescript
import { millisecondsInDay, millisecondsInHour } from "date-fns/constants";
// Calculate milliseconds for custom durations
const threeDaysInMs = 3 * millisecondsInDay;
const sixHoursInMs = 6 * millisecondsInHour;
// Convert millisecond differences to days
const msDifference = date2.getTime() - date1.getTime();
const daysDifference = msDifference / millisecondsInDay;
```
### Duration Constants
```typescript { .api }
const secondsInMinute: number = 60;
const secondsInHour: number = 3600;
const secondsInDay: number = 86400;
const secondsInWeek: number = 604800;
const secondsInMonth: number = 2629800; // Average month (secondsInYear / 12)
const secondsInQuarter: number = 7889400; // Average quarter (secondsInMonth * 3)
const secondsInYear: number = 31557600; // Average year (secondsInDay * daysInYear)
```
**Examples:**
```typescript
import { secondsInDay, secondsInHour } from "date-fns/constants";
// Calculate time intervals
const workingHoursPerDay = 8;
const workingSecondsPerDay = workingHoursPerDay * secondsInHour;
// Convert Unix timestamps
const unixTimestamp = Date.now() / 1000;
const daysFromEpoch = unixTimestamp / secondsInDay;
```
### Calendar Constants
```typescript { .api }
const minutesInHour: number = 60;
const minutesInDay: number = 1440;
const minutesInMonth: number = 43200; // Average month
const minutesInYear: number = 525600; // Average year
const daysInWeek: number = 7;
const daysInYear: number = 365.2425; // Average with leap years
const monthsInQuarter: number = 3;
const monthsInYear: number = 12;
const quartersInYear: number = 4;
```
**Examples:**
```typescript
import { daysInWeek, monthsInYear, quartersInYear } from "date-fns/constants";
// Calculate periods
const weeksInYear = daysInYear / daysInWeek; // ~52.18
const monthsInTwoYears = 2 * monthsInYear; // 24
const quartersInFiveYears = 5 * quartersInYear; // 20
```
### Boundary Constants
```typescript { .api }
const maxTime: number = 8640000000000000;
const minTime: number = -8640000000000000;
```
These represent the maximum and minimum valid JavaScript Date values.
**Examples:**
```typescript
import { maxTime, minTime } from "date-fns/constants";
// Create boundary dates
const maxDate = new Date(maxTime);
//=> Sat Sep 13 275760 02:00:00 (maximum valid date)
const minDate = new Date(minTime);
//=> Tue Apr 20 -271821 02:00:00 (minimum valid date)
// Validate date ranges
function isValidDateRange(date: Date): boolean {
const time = date.getTime();
return time >= minTime && time <= maxTime;
}
```
### Construction Symbol
```typescript { .api }
const constructFromSymbol: unique symbol = Symbol.for("constructDateFrom");
```
Internal symbol used for date constructor injection in generic date types and extensions like UTCDate.
## Core Utility Functions
### Date Construction
```typescript { .api }
function toDate<DateType extends Date>(
argument: DateArg<DateType>,
context?: ContextFn<DateType>
): DateType;
function constructFrom<DateType extends Date>(
date: DateArg<DateType> | ContextFn<DateType>,
value: DateArg<Date>
): DateType;
function constructNow<DateType extends Date>(
date: DateArg<DateType> | ContextFn<DateType>
): DateType;
```
**Examples:**
```typescript
import { toDate, constructFrom, constructNow } from "date-fns";
// Convert various inputs to Date
toDate(new Date(2014, 1, 11)); //=> Date object
toDate('2014-02-11'); //=> Date object
toDate(1392076800000); //=> Date object
// Construct date with same type as reference
const utcDate = new UTCDate(2014, 1, 11);
const newUTCDate = constructFrom(utcDate, '2015-02-11');
//=> UTCDate instance
// Construct "now" with same type as reference
const nowUTC = constructNow(utcDate);
//=> UTCDate instance of current time
```
The `toDate` function is the core conversion utility that all date-fns functions use internally. It converts any DateArg input to a proper Date instance, enabling automatic handling of timestamps, date strings, and existing Date objects.
The `constructFrom` function enables generic date construction, preserving the type of the reference date. This is crucial for working with date extensions like UTCDate or TZDate.
The `constructNow` function creates a new date representing the current time, but using the same constructor type as the reference date.
### Date Validation
```typescript { .api }
function isDate(value: any): value is Date;
function isValid(date: any): boolean;
```
**Examples:**
```typescript
import { isDate, isValid } from "date-fns";
// Type checking
isDate(new Date()); //=> true
isDate('2014-02-11'); //=> false
isDate(null); //=> false
// Validity checking
isValid(new Date(2014, 1, 11)); //=> true
isValid(new Date('invalid')); //=> false
isValid(new Date(2014, 13, 1)); //=> false (invalid month)
```
### Unix Time Utilities
```typescript { .api }
function fromUnixTime(unixTime: number): Date;
function getUnixTime(date: DateArg<Date>): number;
```
**Examples:**
```typescript
import { fromUnixTime, getUnixTime } from "date-fns";
// Convert from Unix timestamp (seconds since epoch)
fromUnixTime(1392123045);
//=> Tue Feb 11 2014 11:30:45
// Convert to Unix timestamp
getUnixTime(new Date(2014, 1, 11, 11, 30, 45));
//=> 1392123045
```
## Interval Utilities
### Interval Construction
```typescript { .api }
function interval(start: DateArg<Date>, end: DateArg<Date>): Interval;
```
**Example:**
```typescript
import { interval } from "date-fns";
const dateInterval = interval(
new Date(2014, 0, 1),
new Date(2014, 0, 7)
);
//=> { start: Date(2014-01-01), end: Date(2014-01-07) }
```
### Duration Conversion
```typescript { .api }
function intervalToDuration(interval: Interval): Duration;
function milliseconds(duration: Duration): number;
```
**Examples:**
```typescript
import { intervalToDuration, milliseconds } from "date-fns";
// Convert interval to duration object
const duration = intervalToDuration({
start: new Date(2014, 0, 1, 0, 0, 0),
end: new Date(2014, 0, 1, 1, 30, 15)
});
//=> { hours: 1, minutes: 30, seconds: 15 }
// Get total milliseconds from duration
const totalMs = milliseconds({
hours: 2,
minutes: 30,
seconds: 45
});
//=> 9045000 (2.5 hours + 45 seconds in milliseconds)
```
### Date Clamping
```typescript { .api }
function clamp<DateType extends Date>(
date: DateArg<DateType>,
interval: Interval
): DateType;
```
**Example:**
```typescript
import { clamp } from "date-fns";
// Clamp date to fit within bounds
clamp(new Date(2014, 0, 1), {
start: new Date(2014, 0, 5),
end: new Date(2014, 0, 10)
});
//=> Date(2014-01-05) (clamped to start bound)
clamp(new Date(2014, 0, 15), {
start: new Date(2014, 0, 5),
end: new Date(2014, 0, 10)
});
//=> Date(2014-01-10) (clamped to end bound)
```
## Type Transformation
### Date Transposition
```typescript { .api }
function transpose<InputDate extends Date, ResultDate extends Date>(
date: InputDate,
constructor: ResultDate | GenericDateConstructor<ResultDate> | ContextFn<ResultDate>
): ResultDate;
```
**Examples:**
```typescript
import { transpose } from "date-fns";
// Convert between different date types
const regularDate = new Date(2022, 6, 10); // July 10, 2022 00:00 in local time
const utcDate = transpose(regularDate, UTCDate);
//=> UTCDate instance: 'Sun Jul 10 2022 00:00:00 GMT+0000 (UTC)'
// Transpose to custom date extension
const tzDate = transpose(regularDate, TZDate);
//=> TZDate instance maintaining the date values in the target timezone
```
The `transpose` function is essential for working with date extensions. Unlike simple conversion, it preserves the date values (year, month, day, hour, etc.) while changing the underlying date type. This is particularly useful when moving between different timezone representations or date implementations.
## Mathematical Utilities
### Time Calculations
```typescript
import { millisecondsInDay, secondsInHour, minutesInDay } from "date-fns/constants";
// Calculate elapsed time
function getElapsedDays(startDate: Date, endDate: Date): number {
const diffMs = endDate.getTime() - startDate.getTime();
return diffMs / millisecondsInDay;
}
// Convert between units
function hoursToMinutes(hours: number): number {
return hours * minutesInHour;
}
function daysToSeconds(days: number): number {
return days * secondsInDay;
}
```
### Age Calculations
```typescript
import { daysInYear } from "date-fns/constants";
function calculateAge(birthDate: Date, referenceDate: Date = new Date()): number {
const diffMs = referenceDate.getTime() - birthDate.getTime();
const diffDays = diffMs / millisecondsInDay;
return Math.floor(diffDays / daysInYear);
}
```
## Performance Optimizations
### Constant-Based Calculations
```typescript
import { millisecondsInDay, secondsInHour } from "date-fns/constants";
// Faster than repeated calculations
function addDaysOptimized(date: Date, days: number): Date {
return new Date(date.getTime() + days * millisecondsInDay);
}
// Pre-calculated constants for common operations
const MILLISECONDS_IN_HOUR = millisecondsInHour;
const MILLISECONDS_IN_WEEK = millisecondsInWeek;
function addHoursOptimized(date: Date, hours: number): Date {
return new Date(date.getTime() + hours * MILLISECONDS_IN_HOUR);
}
```
### Boundary Checking
```typescript
import { maxTime, minTime } from "date-fns/constants";
function safeCreateDate(timestamp: number): Date | null {
if (timestamp < minTime || timestamp > maxTime) {
return null; // Invalid timestamp
}
return new Date(timestamp);
}
function isDateInValidRange(date: Date): boolean {
const time = date.getTime();
return !isNaN(time) && time >= minTime && time <= maxTime;
}
```
## Type Definitions
### Core Types
```typescript { .api }
type DateArg<DateType extends Date> = DateType | number | string;
interface Duration {
years?: number;
months?: number;
weeks?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
}
interface Interval {
start: DateArg<Date>;
end: DateArg<Date>;
}
interface GenericDateConstructor<DateType extends Date = Date> {
new (): DateType;
new (value: DateArg<Date> & {}): DateType;
new (
year: number,
month: number,
date?: number,
hours?: number,
minutes?: number,
seconds?: number,
ms?: number,
): DateType;
}
```
### Utility Types
```typescript { .api }
interface ConstructableDate extends Date {
[constructFromSymbol]: <DateType extends Date = Date>(
value: DateArg<Date> & {}
) => DateType;
}
type DurationUnit = keyof Duration;
type IntervalUnit = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
type LocaleUnit = 'second' | 'minute' | 'hour' | 'day' | 'date' | 'month' | 'year';
type Day = 0 | 1 | 2 | 3 | 4 | 5 | 6;
type Month = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
type Quarter = 1 | 2 | 3 | 4;
type Era = 0 | 1; // 0 = AD, 1 = BC
type FirstWeekContainsDate = 1 | 4;
type ISOStringFormat = "extended" | "basic";
type ISOStringRepresentation = "complete" | "date" | "time";
type RoundingMethod = "ceil" | "floor" | "round" | "trunc";
type NearestHours = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
type NearestMinutes = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30;
type ContextFn<DateType extends Date> = (value: DateArg<Date> & {}) => DateType;
```
## Common Utility Patterns
### Safe Date Operations
```typescript
import { isValid, toDate, maxTime, minTime } from "date-fns";
function safeDateOperation<T>(
date: any,
operation: (validDate: Date) => T,
fallback: T
): T {
try {
const converted = toDate(date);
if (!isValid(converted)) {
return fallback;
}
const timestamp = converted.getTime();
if (timestamp < minTime || timestamp > maxTime) {
return fallback;
}
return operation(converted);
} catch {
return fallback;
}
}
```
### Date Range Validation
```typescript
import { isValid, constructFrom } from "date-fns";
function createValidDateRange(start: any, end: any): Interval | null {
try {
const startDate = constructFrom(new Date(), start);
const endDate = constructFrom(new Date(), end);
if (!isValid(startDate) || !isValid(endDate)) {
return null;
}
if (startDate > endDate) {
return { start: endDate, end: startDate }; // Swap if needed
}
return { start: startDate, end: endDate };
} catch {
return null;
}
}
```
### Performance Timing
```typescript
import { millisecondsInSecond } from "date-fns/constants";
function measurePerformance<T>(operation: () => T): { result: T; durationMs: number } {
const start = performance.now();
const result = operation();
const end = performance.now();
return {
result,
durationMs: end - start
};
}
function formatDuration(ms: number): string {
if (ms < millisecondsInSecond) {
return `${ms.toFixed(2)}ms`;
} else {
return `${(ms / millisecondsInSecond).toFixed(2)}s`;
}
}
```

View file

@ -0,0 +1,486 @@
# Date Formatting
Date formatting functions provide comprehensive date-to-string conversion capabilities with extensive customization options, internationalization support, and multiple output formats. All formatting functions are pure and work with various date input types.
## Core Formatting
### format
Format a date according to a given pattern string.
```typescript { .api }
function format(
date: DateArg<Date>,
formatStr: string,
options?: FormatOptions
): string;
```
**Parameters:**
- `date` - The date to format
- `formatStr` - The format pattern string
- `options` - Optional formatting configuration
**Format Tokens:**
- `yyyy` - 4-digit year
- `MM` - 2-digit month (01-12)
- `dd` - 2-digit day (01-31)
- `HH` - 24-hour format hour (00-23)
- `mm` - 2-digit minute (00-59)
- `ss` - 2-digit second (00-59)
- `PP` - Localized date (Jan 1, 2023)
- `pp` - Localized time (12:00 AM)
**Examples:**
```typescript
import { format } from "date-fns";
// Basic formatting
format(new Date(2014, 1, 11), "yyyy-MM-dd");
//=> '2014-02-11'
// Full date with time
format(new Date(2014, 1, 11, 14, 30, 45), "yyyy-MM-dd HH:mm:ss");
//=> '2014-02-11 14:30:45'
// Localized format
format(new Date(2014, 1, 11), "PP");
//=> 'Feb 11, 2014'
// Custom pattern
format(new Date(2014, 1, 11), "EEEE, MMMM do, yyyy");
//=> 'Tuesday, February 11th, 2014'
```
### lightFormat
Lightweight format without locale support for better performance.
```typescript { .api }
function lightFormat(date: DateArg<Date>, formatStr: string): string;
```
**Example:**
```typescript
import { lightFormat } from "date-fns";
lightFormat(new Date(2014, 1, 11), "yyyy-MM-dd");
//=> '2014-02-11'
```
## Distance Formatting
### formatDistance
Format the distance between two dates in words.
```typescript { .api }
function formatDistance(
dateLeft: DateArg<Date>,
dateRight: DateArg<Date>,
options?: FormatDistanceOptions
): string;
```
**Parameters:**
- `dateLeft` - The first date
- `dateRight` - The second date
- `options` - Distance formatting options
**Examples:**
```typescript
import { formatDistance } from "date-fns";
// Basic distance
formatDistance(
new Date(2014, 6, 2),
new Date(2015, 0, 1)
);
//=> '6 months'
// With suffix
formatDistance(
new Date(2014, 6, 2),
new Date(2015, 0, 1),
{ addSuffix: true }
);
//=> 'in 6 months'
// Include seconds
formatDistance(
new Date(2014, 6, 2, 0, 0, 15),
new Date(2014, 6, 2, 0, 0, 0),
{ includeSeconds: true }
);
//=> 'less than 20 seconds'
```
### formatDistanceStrict
Format distance with strict units (no rounding to nearest unit).
```typescript { .api }
function formatDistanceStrict(
dateLeft: DateArg<Date>,
dateRight: DateArg<Date>,
options?: FormatDistanceStrictOptions
): string;
```
**Example:**
```typescript
import { formatDistanceStrict } from "date-fns";
formatDistanceStrict(
new Date(2014, 6, 2, 0, 5),
new Date(2014, 6, 2, 0, 0)
);
//=> '5 minutes'
```
### formatDistanceToNow
Format the distance between a date and now.
```typescript { .api }
function formatDistanceToNow(
date: DateArg<Date>,
options?: FormatDistanceOptions
): string;
```
**Example:**
```typescript
import { formatDistanceToNow } from "date-fns";
formatDistanceToNow(new Date(2014, 6, 2), { addSuffix: true });
//=> '8 years ago' (assuming current date is 2022)
```
### formatDistanceToNowStrict
Strict distance formatting to current time.
```typescript { .api }
function formatDistanceToNowStrict(
date: DateArg<Date>,
options?: FormatDistanceStrictOptions
): string;
```
## Relative Formatting
### formatRelative
Format a date relative to a base date (e.g., "yesterday", "last Friday").
```typescript { .api }
function formatRelative(
date: DateArg<Date>,
baseDate: DateArg<Date>,
options?: FormatRelativeOptions
): string;
```
**Examples:**
```typescript
import { formatRelative } from "date-fns";
const baseDate = new Date(2000, 0, 1, 0, 0, 0);
// Yesterday
formatRelative(new Date(1999, 11, 31), baseDate);
//=> 'yesterday at 12:00 AM'
// Last week
formatRelative(new Date(1999, 11, 27), baseDate);
//=> 'last Monday at 12:00 AM'
// Next week
formatRelative(new Date(2000, 0, 7), baseDate);
//=> 'next Friday at 12:00 AM'
```
## ISO and Standard Formats
### formatISO
Format a date as ISO 8601 string.
```typescript { .api }
function formatISO(date: DateArg<Date>, options?: FormatISOOptions): string;
```
**Options:**
- `representation` - 'complete' | 'date' | 'time'
- `format` - 'extended' | 'basic'
**Examples:**
```typescript
import { formatISO } from "date-fns";
// Complete ISO format
formatISO(new Date(2019, 8, 18, 19, 0, 52));
//=> '2019-09-18T19:00:52+02:00'
// Date only
formatISO(new Date(2019, 8, 18), { representation: 'date' });
//=> '2019-09-18'
// Basic format
formatISO(new Date(2019, 8, 18), { format: 'basic' });
//=> '20190918T190052+0200'
```
### formatISO9075
Format a date as ISO 9075 string (SQL compatible).
```typescript { .api }
function formatISO9075(date: DateArg<Date>, options?: FormatISOOptions): string;
```
**Example:**
```typescript
import { formatISO9075 } from "date-fns";
formatISO9075(new Date(2019, 8, 18, 19, 0, 52));
//=> '2019-09-18 19:00:52'
```
### formatRFC3339
Format a date as RFC 3339 string.
```typescript { .api }
function formatRFC3339(date: DateArg<Date>, options?: FormatRFC3339Options): string;
```
**Example:**
```typescript
import { formatRFC3339 } from "date-fns";
formatRFC3339(new Date(2019, 8, 18, 19, 0, 52));
//=> '2019-09-18T19:00:52+02:00'
```
### formatRFC7231
Format a date as RFC 7231 string (HTTP date).
```typescript { .api }
function formatRFC7231(date: DateArg<Date>): string;
```
**Example:**
```typescript
import { formatRFC7231 } from "date-fns";
formatRFC7231(new Date(2014, 11, 6, 15, 45));
//=> 'Sat, 06 Dec 2014 15:45:00 GMT'
```
## Duration Formatting
### formatDuration
Format a duration object into a human-readable string.
```typescript { .api }
function formatDuration(
duration: Duration,
options?: FormatDurationOptions
): string;
```
**Examples:**
```typescript
import { formatDuration } from "date-fns";
// Basic duration
formatDuration({
years: 2,
months: 9,
weeks: 1,
days: 7,
hours: 5,
minutes: 9,
seconds: 30
});
//=> '2 years 9 months 1 week 7 days 5 hours 9 minutes 30 seconds'
// With custom format
formatDuration({ hours: 1, minutes: 30 }, { format: ['hours', 'minutes'] });
//=> '1 hour 30 minutes'
```
### formatISODuration
Format a duration as ISO 8601 duration string.
```typescript { .api }
function formatISODuration(duration: Duration): string;
```
**Example:**
```typescript
import { formatISODuration } from "date-fns";
formatISODuration({
years: 2,
months: 9,
weeks: 1,
days: 7,
hours: 5,
minutes: 9,
seconds: 30
});
//=> 'P2Y9M1W7DT5H9M30S'
```
## Internationalization Formatting
### intlFormat
Format a date using the Intl.DateTimeFormat API.
```typescript { .api }
function intlFormat(
date: DateArg<Date>,
formatOptions?: Intl.DateTimeFormatOptions,
localeOptions?: string | string[]
): string;
```
**Examples:**
```typescript
import { intlFormat } from "date-fns";
// Default format
intlFormat(new Date(2019, 0, 1));
//=> '1/1/2019'
// Custom format
intlFormat(new Date(2019, 0, 1), {
year: 'numeric',
month: 'long',
day: 'numeric'
});
//=> 'January 1, 2019'
// Different locale
intlFormat(new Date(2019, 0, 1), {
year: 'numeric',
month: 'long',
day: 'numeric'
}, 'de-DE');
//=> '1. Januar 2019'
```
### intlFormatDistance
Format distance using the Intl.RelativeTimeFormat API.
```typescript { .api }
function intlFormatDistance(
dateLeft: DateArg<Date>,
dateRight: DateArg<Date>,
options?: IntlFormatDistanceOptions
): string;
```
**Example:**
```typescript
import { intlFormatDistance } from "date-fns";
intlFormatDistance(
new Date(1986, 3, 4, 11, 30, 0),
new Date(1986, 3, 4, 10, 30, 0),
{ locale: 'en-US' }
);
//=> 'in 1 hour'
```
## Option Types
### FormatOptions
```typescript { .api }
interface FormatOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
```
### FormatDistanceOptions
```typescript { .api }
interface FormatDistanceOptions {
includeSeconds?: boolean;
addSuffix?: boolean;
locale?: Locale;
}
```
### FormatDistanceStrictOptions
```typescript { .api }
interface FormatDistanceStrictOptions {
addSuffix?: boolean;
unit?: 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year';
roundingMethod?: 'floor' | 'ceil' | 'round';
locale?: Locale;
}
```
### FormatRelativeOptions
```typescript { .api }
interface FormatRelativeOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
```
### FormatISOOptions
```typescript { .api }
interface FormatISOOptions {
format?: 'extended' | 'basic';
representation?: 'complete' | 'date' | 'time';
}
```
### FormatRFC3339Options
```typescript { .api }
interface FormatRFC3339Options {
fractionDigits?: 0 | 1 | 2 | 3;
}
```
### FormatDurationOptions
```typescript { .api }
interface FormatDurationOptions {
format?: DurationUnit[];
zero?: boolean;
delimiter?: string;
locale?: Locale;
}
```
### IntlFormatDistanceOptions
```typescript { .api }
interface IntlFormatDistanceOptions {
locale?: string | string[];
unit?: Intl.RelativeTimeFormatUnit;
localeMatcher?: 'lookup' | 'best fit';
numeric?: 'always' | 'auto';
style?: 'long' | 'short' | 'narrow';
}
```

View file

@ -0,0 +1,477 @@
# Functional Programming
The date-fns functional programming (FP) module provides curried versions of all date functions, enabling functional composition and pipeline-style operations. All FP functions automatically reorder parameters for optimal currying and include full TypeScript support.
## FP Module Overview
### Import Structure
```typescript
// Import individual curried functions
import { add, format, isAfter } from "date-fns/fp";
// Import with namespace
import * as fp from "date-fns/fp";
```
### Currying Pattern
All FP functions follow a consistent currying pattern where the data (typically the date) comes last, enabling partial application and composition:
```typescript
// Regular date-fns: data first
format(date, 'yyyy-MM-dd')
// FP version: data last
format('yyyy-MM-dd')(date)
```
## Core FP Functions
### Arithmetic Functions
```typescript { .api }
const add: CurriedFn2<Duration, DateArg<Date>, Date>;
const addDays: CurriedFn2<number, DateArg<Date>, Date>;
const addHours: CurriedFn2<number, DateArg<Date>, Date>;
const addMinutes: CurriedFn2<number, DateArg<Date>, Date>;
const addMonths: CurriedFn2<number, DateArg<Date>, Date>;
const addYears: CurriedFn2<number, DateArg<Date>, Date>;
const sub: CurriedFn2<Duration, DateArg<Date>, Date>;
const subDays: CurriedFn2<number, DateArg<Date>, Date>;
const subHours: CurriedFn2<number, DateArg<Date>, Date>;
const subMinutes: CurriedFn2<number, DateArg<Date>, Date>;
const subMonths: CurriedFn2<number, DateArg<Date>, Date>;
const subYears: CurriedFn2<number, DateArg<Date>, Date>;
```
**Examples:**
```typescript
import { add, addDays, subMonths } from "date-fns/fp";
// Create reusable functions
const addOneWeek = add({ weeks: 1 });
const addFiveDays = addDays(5);
const subtractTwoMonths = subMonths(2);
// Apply to dates
const dates = [
new Date(2014, 0, 1),
new Date(2014, 0, 15),
new Date(2014, 0, 30)
];
const datesPlus5 = dates.map(addFiveDays);
const weekFromNow = addOneWeek(new Date());
```
### Formatting Functions
```typescript { .api }
const format: CurriedFn2<string, DateArg<Date>, string>;
const formatDistance: CurriedFn2<DateArg<Date>, DateArg<Date>, string>;
const formatDistanceToNow: CurriedFn1<DateArg<Date>, string>;
const formatISO: CurriedFn1<DateArg<Date>, string>;
const formatRelative: CurriedFn2<DateArg<Date>, DateArg<Date>, string>;
const lightFormat: CurriedFn2<string, DateArg<Date>, string>;
```
**Examples:**
```typescript
import { format, formatDistance, formatISO } from "date-fns/fp";
// Create format functions
const formatYMD = format('yyyy-MM-dd');
const formatLong = format('EEEE, MMMM do, yyyy');
const toISO = formatISO;
// Use in pipelines
const dates = [new Date(2014, 0, 1), new Date(2014, 5, 15)];
const formatted = dates.map(formatYMD);
//=> ['2014-01-01', '2014-06-15']
// Distance from specific date
const distanceFromNewYear = formatDistance(new Date(2014, 0, 1));
distanceFromNewYear(new Date(2014, 5, 15));
//=> '5 months'
```
### Comparison Functions
```typescript { .api }
const compareAsc: CurriedFn2<DateArg<Date>, DateArg<Date>, number>;
const compareDesc: CurriedFn2<DateArg<Date>, DateArg<Date>, number>;
const isAfter: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isBefore: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isEqual: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const max: CurriedFn1<DateArg<Date>[], Date>;
const min: CurriedFn1<DateArg<Date>[], Date>;
```
**Examples:**
```typescript
import { isAfter, isBefore, compareAsc } from "date-fns/fp";
// Create comparison functions
const isAfter2020 = isAfter(new Date(2020, 0, 1));
const isBefore2025 = isBefore(new Date(2025, 0, 1));
// Filter dates
const dates = [
new Date(2019, 0, 1),
new Date(2021, 0, 1),
new Date(2026, 0, 1)
];
const recentDates = dates.filter(isAfter2020).filter(isBefore2025);
//=> [new Date(2021, 0, 1)]
// Sort dates
const sortedDates = dates.sort(compareAsc);
```
### Validation Functions
```typescript { .api }
const isValid: CurriedFn1<any, boolean>;
const isDate: CurriedFn1<any, boolean>;
const isFuture: CurriedFn1<DateArg<Date>, boolean>;
const isPast: CurriedFn1<DateArg<Date>, boolean>;
const isToday: CurriedFn1<DateArg<Date>, boolean>;
const isWeekend: CurriedFn1<DateArg<Date>, boolean>;
const isMonday: CurriedFn1<DateArg<Date>, boolean>;
const isTuesday: CurriedFn1<DateArg<Date>, boolean>;
const isWednesday: CurriedFn1<DateArg<Date>, boolean>;
const isThursday: CurriedFn1<DateArg<Date>, boolean>;
const isFriday: CurriedFn1<DateArg<Date>, boolean>;
const isSaturday: CurriedFn1<DateArg<Date>, boolean>;
const isSunday: CurriedFn1<DateArg<Date>, boolean>;
```
**Examples:**
```typescript
import { isValid, isWeekend, isFuture } from "date-fns/fp";
// Filter valid dates
const mixedInput = [
new Date(2014, 0, 1),
'invalid-date',
new Date(2014, 0, 2),
null
];
const validDates = mixedInput.filter(isValid);
//=> [new Date(2014, 0, 1), new Date(2014, 0, 2)]
// Chain validations
const dates = [/* array of dates */];
const futureWeekends = dates.filter(isFuture).filter(isWeekend);
```
### Same Period Functions
```typescript { .api }
const isSameDay: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameWeek: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameMonth: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameYear: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameHour: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameMinute: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
const isSameSecond: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
```
**Examples:**
```typescript
import { isSameDay, isSameMonth } from "date-fns/fp";
// Find dates matching a reference
const referenceDate = new Date(2014, 0, 15);
const dates = [/* array of dates */];
const sameDayDates = dates.filter(isSameDay(referenceDate));
const sameMonthDates = dates.filter(isSameMonth(referenceDate));
```
## Functional Composition
### Function Composition with Ramda
```typescript
import { pipe, compose } from "ramda";
import { format, addDays, startOfDay } from "date-fns/fp";
// Create a pipeline
const formatTomorrowStart = pipe(
addDays(1),
startOfDay,
format('yyyy-MM-dd HH:mm:ss')
);
formatTomorrowStart(new Date(2014, 0, 1, 14, 30));
//=> '2014-01-02 00:00:00'
// Compose operations (right to left)
const formatNextWeekEnd = compose(
format('PP'),
endOfDay,
addDays(7)
);
```
### Custom Composition Helpers
```typescript
import { addDays, format, startOfWeek, endOfWeek } from "date-fns/fp";
// Create utility functions
const pipe = <T>(...fns: Array<(arg: T) => T>) => (value: T) =>
fns.reduce((acc, fn) => fn(acc), value);
const formatWeekRange = (date: Date) => {
const start = startOfWeek(date);
const end = endOfWeek(date);
const formatDate = format('MMM dd');
return `${formatDate(start)} - ${formatDate(end)}`;
};
// Pipeline for date processing
const processDate = pipe(
startOfDay,
addDays(1),
format('yyyy-MM-dd')
);
```
## Advanced FP Patterns
### Partial Application
```typescript
import { formatDistance, isWithinInterval } from "date-fns/fp";
// Create partially applied functions
const distanceFromToday = formatDistance(new Date());
const isInCurrentYear = isWithinInterval({
start: startOfYear(new Date()),
end: endOfYear(new Date())
});
// Use with arrays
const dates = [/* dates */];
const distances = dates.map(distanceFromToday);
const currentYearDates = dates.filter(isInCurrentYear);
```
### Function Factories
```typescript
import { add, format, isBefore } from "date-fns/fp";
// Factory for creating date ranges
const createDateRange = (duration: Duration) => (start: Date): Date[] => {
const end = add(duration)(start);
return eachDayOfInterval({ start, end });
};
// Factory for deadline checkers
const createDeadlineChecker = (deadline: Date) => (date: Date): {
passed: boolean;
remaining: string;
} => ({
passed: isAfter(deadline)(date),
remaining: formatDistance(deadline)(date)
});
// Usage
const getWeekRange = createDateRange({ weeks: 1 });
const checkProjectDeadline = createDeadlineChecker(new Date(2024, 11, 31));
const weekDates = getWeekRange(new Date());
const status = checkProjectDeadline(new Date());
```
### Data Transformation Pipelines
```typescript
import { flow } from "lodash/fp";
import { parse, format, addBusinessDays, isValid } from "date-fns/fp";
// Process CSV date data
const processDateString = flow(
parse('yyyy-MM-dd', new Date()), // Parse with reference date
date => isValid(date) ? date : null, // Validate
date => date ? addBusinessDays(5)(date) : null, // Add business days if valid
date => date ? format('MM/dd/yyyy')(date) : 'Invalid Date' // Format output
);
const csvDates = ['2014-01-01', '2014-02-15', 'invalid-date'];
const processed = csvDates.map(processDateString);
//=> ['01/08/2014', '02/24/2014', 'Invalid Date']
```
## Option Handling in FP
### Functions with Options
```typescript { .api }
const formatWithOptions: CurriedFn3<FormatOptions, string, DateArg<Date>, string>;
const parseWithOptions: CurriedFn4<ParseOptions, DateArg<Date>, string, string, Date>;
const startOfWeekWithOptions: CurriedFn2<WeekStartOptions, DateArg<Date>, Date>;
```
**Examples:**
```typescript
import { formatWithOptions, startOfWeekWithOptions } from "date-fns/fp";
import { de } from "date-fns/locale";
// Create localized formatters
const formatGerman = formatWithOptions({ locale: de });
const formatGermanDate = formatGerman('EEEE, do MMMM yyyy');
formatGermanDate(new Date(2014, 0, 1));
//=> 'Mittwoch, 1. Januar 2014'
// Week starting on Monday
const startOfWeekMonday = startOfWeekWithOptions({ weekStartsOn: 1 });
const dates = [/* dates */];
const weekStarts = dates.map(startOfWeekMonday);
```
## Performance Considerations
### Memoization
```typescript
import memoize from "lodash/memoize";
import { format, formatDistance } from "date-fns/fp";
// Memoize expensive operations
const memoizedFormat = memoize(format);
const formatISO = memoizedFormat('yyyy-MM-dd');
// Memoize with custom key
const memoizedDistance = memoize(
formatDistance,
(referenceDate) => referenceDate.getTime()
);
const distanceFromEpoch = memoizedDistance(new Date(0));
```
### Lazy Evaluation
```typescript
import { addDays, format, isFuture } from "date-fns/fp";
// Create lazy sequences
function* dateSequence(start: Date, step: number = 1) {
let current = start;
while (true) {
yield current;
current = addDays(step)(current);
}
}
// Use with functional operations
const futureDates = Array.from(dateSequence(new Date()))
.slice(0, 100) // Take first 100
.filter(isFuture)
.map(format('yyyy-MM-dd'))
.slice(0, 10); // Take first 10 future dates
```
## Type Definitions
### Curried Function Types
```typescript { .api }
interface CurriedFn1<A, R> {
(a: A): R;
}
interface CurriedFn2<A, B, R> {
(a: A): CurriedFn1<B, R>;
(a: A, b: B): R;
}
interface CurriedFn3<A, B, C, R> {
(a: A): CurriedFn2<B, C, R>;
(a: A, b: B): CurriedFn1<C, R>;
(a: A, b: B, c: C): R;
}
interface CurriedFn4<A, B, C, D, R> {
(a: A): CurriedFn3<B, C, D, R>;
(a: A, b: B): CurriedFn2<C, D, R>;
(a: A, b: B, c: C): CurriedFn1<D, R>;
(a: A, b: B, c: C, d: D): R;
}
```
## Integration Examples
### React Hooks
```typescript
import { useMemo } from "react";
import { format, addDays, isToday } from "date-fns/fp";
function useDateFormatter(pattern: string) {
return useMemo(() => format(pattern), [pattern]);
}
function useRelativeDates(dates: Date[]) {
return useMemo(() => ({
today: dates.filter(isToday),
tomorrow: dates.filter(date => isSameDay(addDays(1)(new Date()))(date)),
formatted: dates.map(format('MMM dd, yyyy'))
}), [dates]);
}
```
### Redux Selectors
```typescript
import { createSelector } from "reselect";
import { isAfter, format, isSameMonth } from "date-fns/fp";
const getEvents = (state: State) => state.events;
const getCurrentMonth = () => new Date();
const getUpcomingEvents = createSelector(
getEvents,
events => events
.filter(event => isAfter(new Date())(event.date))
.sort(compareAsc)
);
const getCurrentMonthEvents = createSelector(
getEvents,
getCurrentMonth,
(events, currentMonth) => events.filter(
event => isSameMonth(currentMonth)(event.date)
)
);
```
### Validation Schemas
```typescript
import { z } from "zod";
import { isValid, isAfter, isBefore } from "date-fns/fp";
const dateSchema = z.string()
.transform(str => new Date(str))
.refine(isValid, "Invalid date")
.refine(isAfter(new Date(1900, 0, 1)), "Date must be after 1900")
.refine(isBefore(new Date(2100, 0, 1)), "Date must be before 2100");
const eventSchema = z.object({
title: z.string(),
startDate: dateSchema,
endDate: dateSchema
}).refine(
data => isBefore(data.endDate)(data.startDate),
"End date must be after start date"
);
```

View file

@ -0,0 +1,552 @@
# Internationalization
The date-fns internationalization system provides comprehensive support for 97+ languages and regions with customizable date formatting, relative time display, and cultural date conventions. All locale functionality is modular and tree-shakeable.
## Locale System Overview
### Locale Interface
```typescript { .api }
interface Locale {
code: string;
formatDistance?: FormatDistanceFn;
formatLong?: FormatLongOptions;
formatRelative?: FormatRelativeFn;
localize?: LocalizeOptions;
match?: MatchOptions;
options?: LocaleOptions;
}
```
The locale object contains all language-specific formatting rules and translations needed for date operations in different languages and regions.
## Supported Locales
### Major Language Families
**English Variants**
- `enUS` - English (United States) - Default
- `enGB` - English (United Kingdom)
- `enCA` - English (Canada)
- `enAU` - English (Australia)
- `enNZ` - English (New Zealand)
- `enIN` - English (India)
- `enIE` - English (Ireland)
- `enZA` - English (South Africa)
**European Languages**
- `de` - German (Germany)
- `deAT` - German (Austria)
- `fr` - French (France)
- `frCA` - French (Canada)
- `frCH` - French (Switzerland)
- `es` - Spanish (Spain)
- `it` - Italian (Italy)
- `itCH` - Italian (Switzerland)
- `pt` - Portuguese (Portugal)
- `ptBR` - Portuguese (Brazil)
- `nl` - Dutch (Netherlands)
- `nlBE` - Dutch (Belgium)
**Nordic Languages**
- `da` - Danish
- `sv` - Swedish
- `nb` - Norwegian Bokmål
- `nn` - Norwegian Nynorsk
- `fi` - Finnish
- `is` - Icelandic
**Slavic Languages**
- `ru` - Russian
- `pl` - Polish
- `cs` - Czech
- `sk` - Slovak
- `sl` - Slovenian
- `hr` - Croatian
- `sr` - Serbian (Cyrillic)
- `srLatn` - Serbian (Latin)
- `bg` - Bulgarian
- `mk` - Macedonian
- `be` - Belarusian
- `beTarask` - Belarusian (Taraskievica)
- `uk` - Ukrainian
**Asian Languages**
- `zhCN` - Chinese (Simplified, China)
- `zhHK` - Chinese (Traditional, Hong Kong)
- `zhTW` - Chinese (Traditional, Taiwan)
- `ja` - Japanese
- `jaHira` - Japanese (Hiragana)
- `ko` - Korean
- `th` - Thai
- `vi` - Vietnamese
- `hi` - Hindi
- `bn` - Bengali
- `gu` - Gujarati
- `kn` - Kannada
- `ta` - Tamil
- `te` - Telugu
**Middle Eastern & African**
- `ar` - Arabic
- `arDZ` - Arabic (Algeria)
- `arEG` - Arabic (Egypt)
- `arMA` - Arabic (Morocco)
- `arSA` - Arabic (Saudi Arabia)
- `arTN` - Arabic (Tunisia)
- `he` - Hebrew
- `faIR` - Persian (Iran)
- `tr` - Turkish
- `af` - Afrikaans
### Complete Locale List
```typescript
// Import specific locales
import { enUS, de, fr, ja, zhCN, ar } from "date-fns/locale";
// Available locales (97 total)
const locales = [
'af', 'ar', 'arDZ', 'arEG', 'arMA', 'arSA', 'arTN',
'az', 'be', 'beTarask', 'bg', 'bn', 'bs', 'ca', 'ckb',
'cs', 'cy', 'da', 'de', 'deAT', 'el', 'enAU', 'enCA',
'enGB', 'enIE', 'enIN', 'enNZ', 'enUS', 'enZA', 'eo',
'es', 'et', 'eu', 'faIR', 'fi', 'fr', 'frCA', 'frCH',
'fy', 'gd', 'gl', 'gu', 'he', 'hi', 'hr', 'ht', 'hu',
'hy', 'id', 'is', 'it', 'itCH', 'ja', 'jaHira', 'ka',
'kk', 'km', 'kn', 'ko', 'lb', 'lt', 'lv', 'mk', 'mn',
'ms', 'mt', 'nb', 'nl', 'nlBE', 'nn', 'oc', 'pl', 'pt',
'ptBR', 'ro', 'ru', 'se', 'sk', 'sl', 'sq', 'sr', 'srLatn',
'sv', 'ta', 'te', 'th', 'tr', 'ug', 'uk', 'uz', 'uzCyrl',
'vi', 'zhCN', 'zhHK', 'zhTW'
];
```
## Using Locales
### Basic Locale Usage
```typescript
import { format, formatDistance, formatRelative } from "date-fns";
import { de, fr, ja, zhCN } from "date-fns/locale";
const date = new Date(2014, 1, 11);
// German
format(date, "EEEE, do MMMM yyyy", { locale: de });
//=> 'Dienstag, 11. Februar 2014'
// French
format(date, "EEEE, do MMMM yyyy", { locale: fr });
//=> 'mardi, 11e février 2014'
// Japanese
format(date, "EEEE, do MMMM yyyy", { locale: ja });
//=> '火曜日, 11日 2月 2014'
// Chinese (Simplified)
format(date, "EEEE, do MMMM yyyy", { locale: zhCN });
//=> '星期二, 11日 二月 2014'
```
### Distance Formatting with Locales
```typescript
import { formatDistance, formatDistanceToNow } from "date-fns";
import { de, es, ru, ar } from "date-fns/locale";
const date1 = new Date(2014, 6, 2);
const date2 = new Date(2015, 0, 1);
// German
formatDistance(date2, date1, { locale: de });
//=> 'etwa 6 Monate'
// Spanish
formatDistance(date2, date1, { locale: es });
//=> 'alrededor de 6 meses'
// Russian
formatDistance(date2, date1, { locale: ru });
//=> 'около 6 месяцев'
// Arabic
formatDistance(date2, date1, { locale: ar });
//=> 'حوالي 6 أشهر'
```
### Relative Formatting with Locales
```typescript
import { formatRelative } from "date-fns";
import { de, fr, ja } from "date-fns/locale";
const baseDate = new Date(2000, 0, 1, 0, 0, 0); // Jan 1, 2000
const date = new Date(1999, 11, 27); // Dec 27, 1999
// German
formatRelative(date, baseDate, { locale: de });
//=> 'letzten Montag um 00:00'
// French
formatRelative(date, baseDate, { locale: fr });
//=> 'lundi dernier à 00:00'
// Japanese
formatRelative(date, baseDate, { locale: ja });
//=> '先週の月曜日 0:00'
```
## Locale Configuration Options
### Week Configuration
```typescript
import { format, startOfWeek } from "date-fns";
import { de, ar, enUS } from "date-fns/locale";
const date = new Date(2014, 1, 11); // Tuesday
// Different week start days by locale
startOfWeek(date, { locale: enUS }); // Sunday start
//=> Sun Feb 09 2014
startOfWeek(date, { locale: de }); // Monday start
//=> Mon Feb 10 2014
// Manual override
startOfWeek(date, { locale: enUS, weekStartsOn: 1 }); // Force Monday start
//=> Mon Feb 10 2014
```
### First Week Configuration
```typescript
import { getWeek } from "date-fns";
import { de, enUS } from "date-fns/locale";
const date = new Date(2014, 0, 1); // Jan 1, 2014
// Different first week rules
getWeek(date, { locale: enUS });
//=> 1 (US: week containing Jan 1 is week 1)
getWeek(date, { locale: de });
//=> 1 (German: week with 4+ days in new year is week 1)
```
## Locale-Specific Patterns
### Date Patterns by Locale
```typescript
import { format } from "date-fns";
import { enUS, de, ja, ar } from "date-fns/locale";
const date = new Date(2014, 1, 11);
// US format (M/d/yyyy)
format(date, "P", { locale: enUS });
//=> '2/11/2014'
// German format (dd.MM.yyyy)
format(date, "P", { locale: de });
//=> '11.02.2014'
// Japanese format (yyyy/MM/dd)
format(date, "P", { locale: ja });
//=> '2014/02/11'
// Arabic format (d/M/yyyy)
format(date, "P", { locale: ar });
//=> '11/2/2014'
```
### Time Patterns by Locale
```typescript
import { format } from "date-fns";
import { enUS, de, ja } from "date-fns/locale";
const date = new Date(2014, 1, 11, 14, 30);
// US format (2:30 PM)
format(date, "p", { locale: enUS });
//=> '2:30 PM'
// German format (14:30)
format(date, "p", { locale: de });
//=> '14:30'
// Japanese format (14:30)
format(date, "p", { locale: ja });
//=> '14:30'
```
### Long Format Patterns
```typescript
import { format } from "date-fns";
import { enUS, de, fr, ja } from "date-fns/locale";
const date = new Date(2014, 1, 11, 14, 30, 45);
// Full date and time formats
format(date, "PPPPpppp", { locale: enUS });
//=> 'Tuesday, February 11th, 2014 at 2:30:45 PM GMT+2'
format(date, "PPPPpppp", { locale: de });
//=> 'Dienstag, 11. Februar 2014 um 14:30:45 GMT+2'
format(date, "PPPPpppp", { locale: fr });
//=> 'mardi 11 février 2014 à 14:30:45 GMT+2'
format(date, "PPPPpppp", { locale: ja });
//=> '2014年2月11日火曜日 14:30:45 GMT+2'
```
## Creating Custom Locales
### Locale Structure
```typescript { .api }
interface Locale {
code: string;
formatDistance: FormatDistanceFn;
formatLong: FormatLongOptions;
formatRelative: FormatRelativeFn;
localize: LocalizeOptions;
match: MatchOptions;
options?: LocaleOptions;
}
```
### Custom Locale Example
```typescript
import { Locale } from "date-fns";
const customLocale: Locale = {
code: 'custom',
formatDistance: (token, count, options) => {
// Custom distance formatting logic
const formatDistanceLocale = {
lessThanXSeconds: 'less than {{count}} second{{s}}',
xSeconds: '{{count}} second{{s}}',
halfAMinute: 'half a minute',
lessThanXMinutes: 'less than {{count}} minute{{s}}',
xMinutes: '{{count}} minute{{s}}',
aboutXHours: 'about {{count}} hour{{s}}',
xHours: '{{count}} hour{{s}}',
xDays: '{{count}} day{{s}}',
aboutXWeeks: 'about {{count}} week{{s}}',
xWeeks: '{{count}} week{{s}}',
aboutXMonths: 'about {{count}} month{{s}}',
xMonths: '{{count}} month{{s}}',
aboutXYears: 'about {{count}} year{{s}}',
xYears: '{{count}} year{{s}}',
overXYears: 'over {{count}} year{{s}}',
almostXYears: 'almost {{count}} year{{s}}'
};
const result = formatDistanceLocale[token].replace(
'{{count}}',
count.toString()
);
return result.replace('{{s}}', count === 1 ? '' : 's');
},
formatLong: {
time: 'HH:mm:ss',
date: 'dd/MM/yyyy',
dateTime: '{{date}} {{time}}',
longDate: 'MMMM d, yyyy',
fullDate: 'EEEE, MMMM do, yyyy',
shortTime: 'HH:mm',
longTime: 'HH:mm:ss',
fullTime: 'HH:mm:ss'
},
formatRelative: (token, date, baseDate, options) => {
// Custom relative formatting
const formatRelativeLocale = {
lastWeek: "'last' eeee 'at' p",
yesterday: "'yesterday at' p",
today: "'today at' p",
tomorrow: "'tomorrow at' p",
nextWeek: "eeee 'at' p",
other: 'P'
};
return formatRelativeLocale[token];
},
localize: {
// Month names, day names, etc.
month: (month) => ['Jan', 'Feb', 'Mar', /*...*/][month],
day: (day) => ['Sun', 'Mon', 'Tue', /*...*/][day],
// Additional localization functions...
},
match: {
// Parsing patterns for custom locale
// Match functions for parsing formatted dates
},
options: {
weekStartsOn: 1, // Monday
firstWeekContainsDate: 4
}
};
```
### Using Custom Locales
```typescript
import { format } from "date-fns";
// Use the custom locale
format(new Date(), "PPP", { locale: customLocale });
```
## Locale Helper Functions
### Locale Detection
```typescript
// Browser locale detection
function getBrowserLocale(): string {
return navigator.language || navigator.languages[0] || 'en-US';
}
// Map browser locale to date-fns locale
function getDateFnsLocale(browserLocale: string): Locale {
const localeMap: Record<string, () => Promise<Locale>> = {
'de': () => import('date-fns/locale/de'),
'de-DE': () => import('date-fns/locale/de'),
'fr': () => import('date-fns/locale/fr'),
'fr-FR': () => import('date-fns/locale/fr'),
// Add more mappings...
};
const loader = localeMap[browserLocale] ||
localeMap[browserLocale.split('-')[0]] ||
(() => import('date-fns/locale/en-US'));
return loader();
}
```
### Dynamic Locale Loading
```typescript
// Lazy load locales
class LocaleManager {
private loadedLocales = new Map<string, Locale>();
async getLocale(code: string): Promise<Locale> {
if (this.loadedLocales.has(code)) {
return this.loadedLocales.get(code)!;
}
try {
const locale = await import(`date-fns/locale/${code}`);
this.loadedLocales.set(code, locale.default);
return locale.default;
} catch {
// Fallback to English
const enUS = await import('date-fns/locale/en-US');
return enUS.default;
}
}
}
```
## Right-to-Left (RTL) Support
### Arabic and Hebrew Examples
```typescript
import { format } from "date-fns";
import { ar, he } from "date-fns/locale";
const date = new Date(2014, 1, 11);
// Arabic (RTL)
format(date, "EEEE، do MMMM yyyy", { locale: ar });
//=> 'الثلاثاء، 11 فبراير 2014'
// Hebrew (RTL)
format(date, "EEEE, do MMMM yyyy", { locale: he });
//=> 'יום שלישי, 11 פברואר 2014'
```
### Handling RTL in Applications
```typescript
import { ar, he, enUS } from "date-fns/locale";
function isRTLLocale(locale: Locale): boolean {
const rtlCodes = ['ar', 'he', 'fa', 'ur'];
return rtlCodes.includes(locale.code);
}
function formatWithDirection(date: Date, pattern: string, locale: Locale): {
text: string;
direction: 'ltr' | 'rtl';
} {
return {
text: format(date, pattern, { locale }),
direction: isRTLLocale(locale) ? 'rtl' : 'ltr'
};
}
```
## Option Types
### LocaleOptions
```typescript { .api }
interface LocaleOptions {
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
}
```
### FormatLongOptions
```typescript { .api }
interface FormatLongOptions {
time?: string;
date?: string;
dateTime?: string;
longDate?: string;
fullDate?: string;
shortTime?: string;
longTime?: string;
fullTime?: string;
}
```
### FormatDistanceFn
```typescript { .api }
type FormatDistanceFn = (
token: string,
count: number,
options?: { addSuffix?: boolean; comparison?: number }
) => string;
```
### FormatRelativeFn
```typescript { .api }
type FormatRelativeFn = (
token: 'lastWeek' | 'yesterday' | 'today' | 'tomorrow' | 'nextWeek' | 'other',
date: Date,
baseDate: Date,
options?: any
) => string;
```

View file

@ -0,0 +1,267 @@
# date-fns
date-fns provides the most comprehensive, yet simple and consistent toolset for manipulating JavaScript dates in both browser and Node.js environments. It offers over 200 modular functions for all date manipulation needs, supporting tree-shaking and selective imports to minimize bundle size.
## Package Information
- **Package Name**: date-fns
- **Package Type**: npm
- **Language**: JavaScript/TypeScript
- **Installation**: `npm install date-fns`
## Core Imports
```typescript
import { format, addDays, differenceInDays, isValid } from "date-fns";
```
For CommonJS:
```javascript
const { format, addDays, differenceInDays, isValid } = require("date-fns");
```
Individual function imports (recommended for tree-shaking):
```typescript
import { format } from "date-fns/format";
import { addDays } from "date-fns/addDays";
```
Constants import:
```typescript
import { maxTime, minTime } from "date-fns/constants";
```
Locale imports:
```typescript
import { enUS, de, fr } from "date-fns/locale";
```
## Basic Usage
```typescript
import { compareAsc, format } from "date-fns";
// Format dates
format(new Date(2014, 1, 11), "yyyy-MM-dd");
//=> '2014-02-11'
// Add days and format
const futureDate = addDays(new Date(2023, 0, 1), 10);
format(futureDate, "PP");
//=> 'Jan 11, 2023'
// Compare dates
const dates = [
new Date(1995, 6, 2),
new Date(1987, 1, 11),
new Date(1989, 6, 10),
];
dates.sort(compareAsc);
//=> Sorted chronologically
// Validate dates
isValid(new Date("2023-13-01")); //=> false
isValid(new Date("2023-01-01")); //=> true
```
## Architecture
date-fns is built around several key principles:
- **Immutable & Pure**: All functions are pure and return new date instances without modifying inputs
- **Modular Design**: Each function is a separate module enabling tree-shaking and selective imports
- **TypeScript First**: Complete type definitions with generic support for custom date types
- **No Extensions**: Uses native Date objects without extending prototypes for maximum compatibility
- **Functional Programming**: Optional FP module with curried interfaces for functional composition
- **Internationalization**: Comprehensive locale system supporting 97+ languages and regions
## Capabilities
### Date Arithmetic
Core date manipulation functions for adding, subtracting, and calculating differences between dates. Essential for date calculations and scheduling applications.
```typescript { .api }
function add(date: DateArg<DateType>, duration: Duration): DateType;
function addDays(date: DateArg<DateType>, amount: number): DateType;
function differenceInDays(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): number;
function sub(date: DateArg<DateType>, duration: Duration): DateType;
```
[Date Arithmetic](./arithmetic.md)
### Date Formatting
Comprehensive date formatting with customizable patterns, internationalization support, and multiple output formats including ISO, RFC, and relative time formatting.
```typescript { .api }
function format(date: DateArg<Date>, formatStr: string, options?: FormatOptions): string;
function formatDistance(dateLeft: DateArg<Date>, dateRight: DateArg<Date>, options?: FormatDistanceOptions): string;
function formatISO(date: DateArg<Date>, options?: FormatISOOptions): string;
function formatRelative(date: DateArg<Date>, baseDate: DateArg<Date>, options?: FormatRelativeOptions): string;
```
[Date Formatting](./formatting.md)
### Date Parsing
Flexible date parsing from strings, ISO formats, and custom patterns with proper error handling and validation support.
```typescript { .api }
function parse(dateString: string, formatString: string, referenceDate: DateArg<Date>, options?: ParseOptions): Date;
function parseISO(argument: string, options?: ParseOptions): Date;
function parseJSON(argument: string | number | Date): Date;
function isMatch(dateString: string, formatString: string, options?: MatchOptions): boolean;
```
[Date Parsing](./parsing.md)
### Date Validation and Comparison
Comprehensive validation and comparison utilities for checking date validity, temporal relationships, and period-based comparisons.
```typescript { .api }
function isValid(date: any): boolean;
function isAfter(date: DateArg<Date>, dateToCompare: DateArg<Date>): boolean;
function isBefore(date: DateArg<Date>, dateToCompare: DateArg<Date>): boolean;
function isEqual(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function compareAsc(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): number;
```
[Date Validation](./validation.md)
### Date Component Access
Functions for getting and setting individual date components (year, month, day, hour, etc.) with proper handling of time zones and edge cases.
```typescript { .api }
function getYear(date: DateArg<Date>): number;
function getMonth(date: DateArg<Date>): number;
function getDate(date: DateArg<Date>): number;
function setYear<DateType extends Date>(date: DateArg<DateType>, year: number): DateType;
function setMonth<DateType extends Date>(date: DateArg<DateType>, month: number): DateType;
```
[Date Components](./components.md)
### Time Period Utilities
Functions for working with specific time periods like start/end of day, week, month, and iteration over date ranges.
```typescript { .api }
function startOfDay<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfMonth<DateType extends Date>(date: DateArg<DateType>): DateType;
function eachDayOfInterval(interval: Interval, options?: StepOptions): Date[];
function lastDayOfMonth<DateType extends Date>(date: DateArg<DateType>): DateType;
```
[Time Periods](./periods.md)
### Internationalization
Complete locale system supporting 97+ languages and regions with customizable date formatting, relative time display, and cultural date conventions.
```typescript { .api }
interface Locale {
code: string;
formatDistance: FormatDistanceFn;
formatLong: FormatLongOptions;
formatRelative: FormatRelativeFn;
localize: LocalizeOptions;
match: MatchOptions;
options?: LocaleOptions;
}
```
[Internationalization](./i18n.md)
### Constants and Utilities
Mathematical constants for time calculations, utility functions for date construction, and helper functions for common operations.
```typescript { .api }
const daysInWeek = 7;
const millisecondsInDay = 86400000;
const maxTime = 8640000000000000;
function toDate(argument: DateArg<Date>): Date;
function isDate(value: any): value is Date;
```
[Constants and Utilities](./constants.md)
### Functional Programming
Curried function interfaces for functional composition and pipeline-style date operations, with automatic parameter reordering for optimal usage.
```typescript { .api }
const add: CurriedFn2<Duration, DateArg<Date>, Date>;
const format: CurriedFn2<string, DateArg<Date>, string>;
const isAfter: CurriedFn2<DateArg<Date>, DateArg<Date>, boolean>;
```
[Functional Programming](./fp.md)
## Types
### Core Types
```typescript { .api }
type DateArg<DateType extends Date> = DateType | number | string;
interface Duration {
years?: number;
months?: number;
weeks?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
}
interface Interval {
start: DateArg<Date>;
end: DateArg<Date>;
}
interface GenericDateConstructor<DateType extends Date = Date> {
new (): DateType;
new (value: DateArg<Date> & {}): DateType;
new (
year: number,
month: number,
date?: number,
hours?: number,
minutes?: number,
seconds?: number,
ms?: number,
): DateType;
}
```
### Options Interfaces
```typescript { .api }
interface FormatOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
interface ParseOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
interface StepOptions {
step?: number;
}
```

View file

@ -0,0 +1,381 @@
# Date Parsing
Date parsing functions provide flexible conversion from strings to Date objects with support for various formats, ISO standards, and custom patterns. All parsing functions include proper error handling and validation.
## Core Parsing
### parse
Parse a date from a string using a format pattern.
```typescript { .api }
function parse(
dateString: string,
formatString: string,
referenceDate: DateArg<Date>,
options?: ParseOptions
): Date;
```
**Parameters:**
- `dateString` - The string to parse
- `formatString` - The format pattern to match
- `referenceDate` - Reference date for relative parsing
- `options` - Optional parsing configuration
**Format Tokens:**
- `yyyy` - 4-digit year
- `MM` - 2-digit month (01-12)
- `dd` - 2-digit day (01-31)
- `HH` - 24-hour format hour (00-23)
- `mm` - 2-digit minute (00-59)
- `ss` - 2-digit second (00-59)
- `SSS` - Milliseconds (000-999)
**Examples:**
```typescript
import { parse } from "date-fns";
// Basic date parsing
parse('2014-02-11', 'yyyy-MM-dd', new Date());
//=> Tue Feb 11 2014 00:00:00
// Date and time
parse('2014-02-11 14:30:45', 'yyyy-MM-dd HH:mm:ss', new Date());
//=> Tue Feb 11 2014 14:30:45
// Custom format
parse('11.02.2014', 'dd.MM.yyyy', new Date());
//=> Tue Feb 11 2014 00:00:00
// With milliseconds
parse('2014-02-11T14:30:45.123', 'yyyy-MM-ddTHH:mm:ss.SSS', new Date());
//=> Tue Feb 11 2014 14:30:45.123
```
### parseISO
Parse an ISO 8601 date string.
```typescript { .api }
function parseISO<ResultDate extends Date = Date>(
argument: string,
options?: ParseISOOptions<ResultDate>
): ResultDate;
```
**Parameters:**
- `argument` - The ISO 8601 string to parse
- `options` - Optional parsing configuration with additional digits support
**Supported Formats:**
- `YYYY-MM-DD` - Calendar date
- `YYYY-MM-DDTHH:mm:ss` - Complete date and time
- `YYYY-MM-DDTHH:mm:ss.sss` - With milliseconds
- `YYYY-MM-DDTHH:mm:ssZ` - UTC time
- `YYYY-MM-DDTHH:mm:ss+HH:mm` - With timezone offset
**Examples:**
```typescript
import { parseISO } from "date-fns";
// Date only
parseISO('2014-02-11');
//=> Tue Feb 11 2014 00:00:00
// Complete datetime
parseISO('2014-02-11T11:30:30');
//=> Tue Feb 11 2014 11:30:30
// With timezone
parseISO('2014-02-11T11:30:30+05:00');
//=> Tue Feb 11 2014 11:30:30
// UTC time
parseISO('2014-02-11T11:30:30Z');
//=> Tue Feb 11 2014 11:30:30
// With milliseconds
parseISO('2014-02-11T11:30:30.123Z');
//=> Tue Feb 11 2014 11:30:30.123
```
### parseJSON
Parse a date from JSON (handles various JSON date formats).
```typescript { .api }
function parseJSON<ResultDate extends Date = Date>(
dateStr: string,
options?: ParseJSONOptions<ResultDate>
): ResultDate;
```
**Parameters:**
- `dateStr` - The JSON date string to parse
- `options` - Optional parsing configuration with context support
**Examples:**
```typescript
import { parseJSON } from "date-fns";
// ISO string from JSON
parseJSON('2014-02-11T11:30:30.000Z');
//=> Tue Feb 11 2014 11:30:30
// Unix timestamp
parseJSON(1392123030000);
//=> Tue Feb 11 2014 11:30:30
// Already a Date object
parseJSON(new Date(2014, 1, 11));
//=> Tue Feb 11 2014 00:00:00
```
## Validation and Matching
### isMatch
Check if a string matches a date format pattern.
```typescript { .api }
function isMatch(
dateString: string,
formatString: string,
options?: MatchOptions
): boolean;
```
**Parameters:**
- `dateString` - The string to test
- `formatString` - The format pattern to match against
- `options` - Optional matching configuration
**Examples:**
```typescript
import { isMatch } from "date-fns";
// Valid formats
isMatch('2014-02-11', 'yyyy-MM-dd');
//=> true
isMatch('11.02.2014', 'dd.MM.yyyy');
//=> true
// Invalid formats
isMatch('2014-02-11', 'dd.MM.yyyy');
//=> false
isMatch('not-a-date', 'yyyy-MM-dd');
//=> false
// Complex patterns
isMatch('2014-02-11 14:30:45', 'yyyy-MM-dd HH:mm:ss');
//=> true
```
## Advanced Parsing Patterns
### Date Parts Parsing
Parse dates with various separators and formats:
```typescript
import { parse } from "date-fns";
// Different separators
parse('2014/02/11', 'yyyy/MM/dd', new Date());
parse('2014-02-11', 'yyyy-MM-dd', new Date());
parse('2014.02.11', 'yyyy.MM.dd', new Date());
// Different order
parse('11/02/2014', 'dd/MM/yyyy', new Date());
parse('02/11/2014', 'MM/dd/yyyy', new Date());
// Short year
parse('14-02-11', 'yy-MM-dd', new Date());
```
### Time Parsing
Parse various time formats:
```typescript
import { parse } from "date-fns";
// 24-hour format
parse('14:30:45', 'HH:mm:ss', new Date());
// 12-hour format with AM/PM
parse('2:30:45 PM', 'h:mm:ss a', new Date());
// Minutes and seconds only
parse('30:45', 'mm:ss', new Date());
// With milliseconds
parse('14:30:45.123', 'HH:mm:ss.SSS', new Date());
```
### Relative Date Parsing
Use reference date for context-dependent parsing:
```typescript
import { parse } from "date-fns";
const referenceDate = new Date(2020, 0, 1); // Jan 1, 2020
// Parse relative to reference year
parse('02-11', 'MM-dd', referenceDate);
//=> Feb 11, 2020
// Day of year
parse('42', 'D', referenceDate);
//=> Feb 11, 2020 (42nd day of 2020)
```
## Error Handling
### Invalid Date Detection
```typescript
import { parse, isValid } from "date-fns";
// Parse potentially invalid date
const result = parse('invalid-date', 'yyyy-MM-dd', new Date());
// Check if parsing was successful
if (isValid(result)) {
console.log('Parsed successfully:', result);
} else {
console.log('Parsing failed');
}
// Parse with validation
function safeParse(dateString: string, format: string): Date | null {
try {
const parsed = parse(dateString, format, new Date());
return isValid(parsed) ? parsed : null;
} catch {
return null;
}
}
```
### Common Parsing Pitfalls
```typescript
import { parse, parseISO } from "date-fns";
// Ambiguous formats - be explicit
parse('01/02/2014', 'MM/dd/yyyy', new Date()); // Jan 2, 2014
parse('01/02/2014', 'dd/MM/yyyy', new Date()); // Feb 1, 2014
// Timezone handling
parseISO('2014-02-11T11:30:30'); // Local time
parseISO('2014-02-11T11:30:30Z'); // UTC time
// Invalid dates return Invalid Date
parse('2014-13-01', 'yyyy-MM-dd', new Date()); // Invalid Date
parse('2014-02-30', 'yyyy-MM-dd', new Date()); // Invalid Date
```
## Option Types
### ParseOptions
```typescript { .api }
interface ParseOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
```
### MatchOptions
```typescript { .api }
interface MatchOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
```
### ParseISOOptions
```typescript { .api }
interface ParseISOOptions<DateType extends Date = Date> extends ContextOptions<DateType> {
additionalDigits?: 0 | 1 | 2;
}
```
### ParseJSONOptions
```typescript { .api }
interface ParseJSONOptions<DateType extends Date = Date> extends ContextOptions<DateType> {}
```
## Format Token Reference
### Date Tokens
| Token | Description | Example |
|-------|-------------|---------|
| `yyyy` | 4-digit year | 2014 |
| `yy` | 2-digit year | 14 |
| `y` | Year | 2014 |
| `YYYY` | ISO week year | 2014 |
| `YY` | 2-digit ISO week year | 14 |
### Month Tokens
| Token | Description | Example |
|-------|-------------|---------|
| `MMMM` | Full month name | February |
| `MMM` | Short month name | Feb |
| `MM` | 2-digit month | 02 |
| `M` | Month | 2 |
### Day Tokens
| Token | Description | Example |
|-------|-------------|---------|
| `dd` | 2-digit day | 11 |
| `d` | Day | 11 |
| `D` | Day of year | 42 |
| `EEEE` | Full day name | Tuesday |
| `EEE` | Short day name | Tue |
| `e` | Local day of week | 2 |
| `i` | ISO day of week | 2 |
### Time Tokens
| Token | Description | Example |
|-------|-------------|---------|
| `HH` | 24-hour hour | 14 |
| `H` | 24-hour hour | 14 |
| `hh` | 12-hour hour | 02 |
| `h` | 12-hour hour | 2 |
| `mm` | 2-digit minute | 30 |
| `m` | Minute | 30 |
| `ss` | 2-digit second | 45 |
| `s` | Second | 45 |
| `SSS` | Millisecond | 123 |
| `S` | 1/10 second | 1 |
| `SS` | 1/100 second | 12 |
### AM/PM and Timezone
| Token | Description | Example |
|-------|-------------|---------|
| `a` | AM/PM | PM |
| `aa` | AM/PM | PM |
| `aaa` | AM/PM | PM |
| `X` | Timezone offset | +0200 |
| `XX` | Timezone offset | +02:00 |
| `XXX` | Timezone offset | +02:00 |

View file

@ -0,0 +1,612 @@
# Time Periods
Time period utilities provide functions for working with specific time periods like start/end of time units, iteration over date ranges, and navigation to specific dates. All functions handle edge cases and timezone considerations properly.
## Start of Period Functions
### Basic Time Units
```typescript { .api }
function startOfSecond<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfMinute<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfHour<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfDay<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { startOfSecond, startOfMinute, startOfHour, startOfDay } from "date-fns";
const date = new Date(2014, 1, 11, 14, 30, 45, 500);
startOfSecond(date);
//=> Tue Feb 11 2014 14:30:45.000
startOfMinute(date);
//=> Tue Feb 11 2014 14:30:00.000
startOfHour(date);
//=> Tue Feb 11 2014 14:00:00.000
startOfDay(date);
//=> Tue Feb 11 2014 00:00:00.000
```
### Week and Month Periods
```typescript { .api }
function startOfWeek<DateType extends Date>(
date: DateArg<DateType>,
options?: WeekStartOptions
): DateType;
function startOfISOWeek<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfMonth<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfQuarter<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { startOfWeek, startOfISOWeek, startOfMonth, startOfQuarter } from "date-fns";
const date = new Date(2014, 1, 11); // Tuesday, Feb 11, 2014
startOfWeek(date);
//=> Sun Feb 09 2014 00:00:00 (week starts Sunday by default)
startOfWeek(date, { weekStartsOn: 1 });
//=> Mon Feb 10 2014 00:00:00 (week starts Monday)
startOfISOWeek(date);
//=> Mon Feb 10 2014 00:00:00 (ISO week always starts Monday)
startOfMonth(date);
//=> Sat Feb 01 2014 00:00:00
startOfQuarter(date);
//=> Wed Jan 01 2014 00:00:00 (Q1 starts in January)
```
### Year and Decade Periods
```typescript { .api }
function startOfYear<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfISOWeekYear<DateType extends Date>(date: DateArg<DateType>): DateType;
function startOfWeekYear<DateType extends Date>(
date: DateArg<DateType>,
options?: WeekStartOptions
): DateType;
function startOfDecade<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { startOfYear, startOfISOWeekYear, startOfDecade } from "date-fns";
const date = new Date(2014, 1, 11);
startOfYear(date);
//=> Wed Jan 01 2014 00:00:00
startOfISOWeekYear(date);
//=> Mon Dec 30 2013 00:00:00 (ISO week year 2014 starts in 2013)
startOfDecade(date);
//=> Fri Jan 01 2010 00:00:00 (2010s decade)
```
### Special Date Functions
```typescript { .api }
function startOfToday(): Date;
function startOfTomorrow(): Date;
function startOfYesterday(): Date;
```
**Examples:**
```typescript
import { startOfToday, startOfTomorrow, startOfYesterday } from "date-fns";
// These return dates relative to the current moment
startOfToday(); //=> Today at 00:00:00
startOfTomorrow(); //=> Tomorrow at 00:00:00
startOfYesterday(); //=> Yesterday at 00:00:00
```
## End of Period Functions
### Basic Time Units
```typescript { .api }
function endOfSecond<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfMinute<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfHour<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfDay<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { endOfSecond, endOfMinute, endOfHour, endOfDay } from "date-fns";
const date = new Date(2014, 1, 11, 14, 30, 45, 500);
endOfSecond(date);
//=> Tue Feb 11 2014 14:30:45.999
endOfMinute(date);
//=> Tue Feb 11 2014 14:30:59.999
endOfHour(date);
//=> Tue Feb 11 2014 14:59:59.999
endOfDay(date);
//=> Tue Feb 11 2014 23:59:59.999
```
### Week and Month Periods
```typescript { .api }
function endOfWeek<DateType extends Date>(
date: DateArg<DateType>,
options?: WeekStartOptions
): DateType;
function endOfISOWeek<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfMonth<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfQuarter<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { endOfWeek, endOfISOWeek, endOfMonth, endOfQuarter } from "date-fns";
const date = new Date(2014, 1, 11); // Tuesday, Feb 11, 2014
endOfWeek(date);
//=> Sat Feb 15 2014 23:59:59.999
endOfISOWeek(date);
//=> Sun Feb 16 2014 23:59:59.999
endOfMonth(date);
//=> Fri Feb 28 2014 23:59:59.999
endOfQuarter(date);
//=> Mon Mar 31 2014 23:59:59.999 (Q1 ends in March)
```
### Year and Decade Periods
```typescript { .api }
function endOfYear<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfISOWeekYear<DateType extends Date>(date: DateArg<DateType>): DateType;
function endOfDecade<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { endOfYear, endOfISOWeekYear, endOfDecade } from "date-fns";
const date = new Date(2014, 1, 11);
endOfYear(date);
//=> Wed Dec 31 2014 23:59:59.999
endOfISOWeekYear(date);
//=> Sun Dec 28 2014 23:59:59.999
endOfDecade(date);
//=> Tue Dec 31 2019 23:59:59.999 (2010s decade ends in 2019)
```
### Special Date Functions
```typescript { .api }
function endOfToday(): Date;
function endOfTomorrow(): Date;
function endOfYesterday(): Date;
```
## Last Day Functions
### Period Last Days
```typescript { .api }
function lastDayOfMonth<DateType extends Date>(date: DateArg<DateType>): DateType;
function lastDayOfQuarter<DateType extends Date>(
date: DateArg<DateType>,
options?: QuarterOptions
): DateType;
function lastDayOfYear<DateType extends Date>(date: DateArg<DateType>): DateType;
function lastDayOfDecade<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { lastDayOfMonth, lastDayOfQuarter, lastDayOfYear, lastDayOfDecade } from "date-fns";
const date = new Date(2014, 1, 11); // February 11, 2014
lastDayOfMonth(date);
//=> Fri Feb 28 2014 00:00:00
lastDayOfQuarter(date);
//=> Mon Mar 31 2014 00:00:00 (Q1 ends March 31)
lastDayOfYear(date);
//=> Wed Dec 31 2014 00:00:00
lastDayOfDecade(date);
//=> Tue Dec 31 2019 00:00:00
```
### Week Last Days
```typescript { .api }
function lastDayOfWeek<DateType extends Date>(
date: DateArg<DateType>,
options?: WeekStartOptions
): DateType;
function lastDayOfISOWeek<DateType extends Date>(date: DateArg<DateType>): DateType;
function lastDayOfISOWeekYear<DateType extends Date>(date: DateArg<DateType>): DateType;
```
**Examples:**
```typescript
import { lastDayOfWeek, lastDayOfISOWeek, lastDayOfISOWeekYear } from "date-fns";
const date = new Date(2014, 1, 11); // Tuesday
lastDayOfWeek(date);
//=> Sat Feb 15 2014 00:00:00 (Saturday ends the week)
lastDayOfWeek(date, { weekStartsOn: 1 });
//=> Sun Feb 16 2014 00:00:00 (Sunday ends Monday-starting week)
lastDayOfISOWeek(date);
//=> Sun Feb 16 2014 00:00:00 (ISO week ends Sunday)
lastDayOfISOWeekYear(date);
//=> Sun Dec 28 2014 00:00:00
```
## Iteration Functions
### Basic Iteration
```typescript { .api }
function eachDayOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
function eachHourOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
function eachMinuteOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
```
**Examples:**
```typescript
import { eachDayOfInterval, eachHourOfInterval } from "date-fns";
// Each day in interval
eachDayOfInterval({
start: new Date(2014, 0, 1),
end: new Date(2014, 0, 3)
});
//=> [
// Wed Jan 01 2014,
// Thu Jan 02 2014,
// Fri Jan 03 2014
// ]
// Every other day
eachDayOfInterval({
start: new Date(2014, 0, 1),
end: new Date(2014, 0, 7)
}, { step: 2 });
//=> [Jan 01, Jan 03, Jan 05, Jan 07]
// Each hour
eachHourOfInterval({
start: new Date(2014, 0, 1, 0),
end: new Date(2014, 0, 1, 3)
});
//=> [01 00:00, 01 01:00, 01 02:00, 01 03:00]
```
### Period Iteration
```typescript { .api }
function eachWeekOfInterval(
interval: Interval,
options?: WeekIterationOptions
): Date[];
function eachMonthOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
function eachQuarterOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
function eachYearOfInterval(
interval: Interval,
options?: StepOptions
): Date[];
```
**Examples:**
```typescript
import { eachWeekOfInterval, eachMonthOfInterval, eachYearOfInterval } from "date-fns";
// Each week
eachWeekOfInterval({
start: new Date(2014, 0, 1),
end: new Date(2014, 0, 21)
});
//=> [Dec 29 2013, Jan 05 2014, Jan 12 2014, Jan 19 2014]
// Each month
eachMonthOfInterval({
start: new Date(2014, 0, 1),
end: new Date(2014, 3, 1)
});
//=> [Jan 01 2014, Feb 01 2014, Mar 01 2014, Apr 01 2014]
// Each year with step
eachYearOfInterval({
start: new Date(2010, 0, 1),
end: new Date(2020, 0, 1)
}, { step: 2 });
//=> [2010, 2012, 2014, 2016, 2018, 2020]
```
### Weekend Iteration
```typescript { .api }
function eachWeekendOfInterval(interval: Interval): Date[];
function eachWeekendOfMonth(date: DateArg<Date>): Date[];
function eachWeekendOfYear(date: DateArg<Date>): Date[];
```
**Examples:**
```typescript
import { eachWeekendOfInterval, eachWeekendOfMonth } from "date-fns";
// Weekends in interval
eachWeekendOfInterval({
start: new Date(2018, 8, 17), // Monday
end: new Date(2018, 8, 30) // Sunday
});
//=> [
// Sat Sep 22 2018,
// Sun Sep 23 2018,
// Sat Sep 29 2018,
// Sun Sep 30 2018
// ]
// Weekends in month
eachWeekendOfMonth(new Date(2022, 1, 1));
//=> All Saturday and Sunday dates in February 2022
```
## Navigation Functions
### Next Day Navigation
```typescript { .api }
function nextDay(date: DateArg<Date>, day: Day): Date;
function nextMonday(date: DateArg<Date>): Date;
function nextTuesday(date: DateArg<Date>): Date;
function nextWednesday(date: DateArg<Date>): Date;
function nextThursday(date: DateArg<Date>): Date;
function nextFriday(date: DateArg<Date>): Date;
function nextSaturday(date: DateArg<Date>): Date;
function nextSunday(date: DateArg<Date>): Date;
```
**Examples:**
```typescript
import { nextDay, nextMonday, nextFriday } from "date-fns";
const date = new Date(2020, 2, 20); // Friday, March 20, 2020
nextDay(date, 1); // Next Monday
//=> Mon Mar 23 2020
nextMonday(date);
//=> Mon Mar 23 2020
nextFriday(date);
//=> Fri Mar 27 2020 (next Friday, not same day)
// Using Day enum
nextDay(date, 0); // Next Sunday
//=> Sun Mar 22 2020
```
### Previous Day Navigation
```typescript { .api }
function previousDay(date: DateArg<Date>, day: Day): Date;
function previousMonday(date: DateArg<Date>): Date;
function previousTuesday(date: DateArg<Date>): Date;
function previousWednesday(date: DateArg<Date>): Date;
function previousThursday(date: DateArg<Date>): Date;
function previousFriday(date: DateArg<Date>): Date;
function previousSaturday(date: DateArg<Date>): Date;
function previousSunday(date: DateArg<Date>): Date;
```
**Examples:**
```typescript
import { previousDay, previousMonday, previousFriday } from "date-fns";
const date = new Date(2020, 2, 20); // Friday, March 20, 2020
previousDay(date, 1); // Previous Monday
//=> Mon Mar 16 2020
previousMonday(date);
//=> Mon Mar 16 2020
previousFriday(date);
//=> Fri Mar 13 2020 (previous Friday, not same day)
```
## Rounding Functions
### Time Rounding
```typescript { .api }
function roundToNearestMinutes<DateType extends Date>(
date: DateArg<DateType>,
options?: RoundToNearestMinutesOptions
): DateType;
function roundToNearestHours<DateType extends Date>(
date: DateArg<DateType>,
options?: RoundToNearestHoursOptions
): DateType;
```
**Examples:**
```typescript
import { roundToNearestMinutes, roundToNearestHours } from "date-fns";
const date = new Date(2014, 6, 10, 12, 7, 30);
// Round to nearest 5 minutes
roundToNearestMinutes(date, { nearestTo: 5 });
//=> Thu Jul 10 2014 12:05:00 (rounds down)
// Round to nearest 15 minutes
roundToNearestMinutes(date, { nearestTo: 15 });
//=> Thu Jul 10 2014 12:00:00
// Round to nearest hour (default: 1 hour)
roundToNearestHours(date);
//=> Thu Jul 10 2014 12:00:00
// Round to nearest 4 hours
roundToNearestHours(date, { nearestTo: 4 });
//=> Thu Jul 10 2014 12:00:00
```
## Option Types
### WeekStartOptions
```typescript { .api }
interface WeekStartOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
```
### RoundToNearestMinutesOptions
```typescript { .api }
interface RoundToNearestMinutesOptions<DateType extends Date = Date>
extends NearestToUnitOptions<NearestMinutes>, RoundingOptions, ContextOptions<DateType> {}
```
### RoundToNearestHoursOptions
```typescript { .api }
interface RoundToNearestHoursOptions<DateType extends Date = Date>
extends NearestToUnitOptions<NearestHours>, RoundingOptions, ContextOptions<DateType> {}
```
### WeekIterationOptions
```typescript { .api }
interface WeekIterationOptions extends StepOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
```
### QuarterOptions
```typescript { .api }
interface QuarterOptions {
locale?: Locale;
}
```
### StepOptions
```typescript { .api }
interface StepOptions {
step?: number;
}
```
### Day Enum
```typescript { .api }
enum Day {
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6
}
```
## Common Patterns
### Working Hours Calculation
```typescript
import { eachDayOfInterval, startOfDay, endOfDay, isWeekend } from "date-fns";
function getWorkingDays(start: Date, end: Date): Date[] {
return eachDayOfInterval({ start, end })
.filter(date => !isWeekend(date));
}
function getWorkingHours(date: Date): { start: Date; end: Date } {
return {
start: setHours(startOfDay(date), 9), // 9 AM
end: setHours(startOfDay(date), 17) // 5 PM
};
}
```
### Period Boundaries
```typescript
import { startOfMonth, endOfMonth, eachWeekOfInterval } from "date-fns";
function getMonthBoundaries(date: Date) {
return {
start: startOfMonth(date),
end: endOfMonth(date)
};
}
function getWeeksInMonth(date: Date): Date[] {
const { start, end } = getMonthBoundaries(date);
return eachWeekOfInterval({ start, end });
}
```
### Flexible Iteration
```typescript
import { eachDayOfInterval } from "date-fns";
function getBusinessDays(start: Date, end: Date): Date[] {
return eachDayOfInterval({ start, end })
.filter(date => {
const day = getDay(date);
return day !== 0 && day !== 6; // Not Sunday or Saturday
});
}
```

View file

@ -0,0 +1,467 @@
# Date Validation and Comparison
Date validation and comparison functions provide comprehensive utilities for checking date validity, temporal relationships, and period-based comparisons. All functions handle edge cases and invalid dates gracefully.
## Core Validation
### isValid
Check if a date is valid.
```typescript { .api }
function isValid(date: unknown): boolean;
```
**Parameters:**
- `date` - Any value to check for date validity
**Examples:**
```typescript
import { isValid } from "date-fns";
// Valid dates
isValid(new Date(2014, 1, 11)); //=> true
isValid(new Date('2014-02-11')); //=> true
// Invalid dates
isValid(new Date('invalid')); //=> false
isValid(new Date(2014, 13, 1)); //=> false (month 13)
isValid(new Date(2014, 1, 30)); //=> false (Feb 30)
// Non-date values
isValid('2014-02-11'); //=> false
isValid(null); //=> false
isValid(undefined); //=> false
```
### isDate
Check if a value is a Date object.
```typescript { .api }
function isDate(value: any): value is Date;
```
**Examples:**
```typescript
import { isDate } from "date-fns";
isDate(new Date()); //=> true
isDate(new Date('invalid')); //=> true (still a Date object)
isDate('2014-02-11'); //=> false
isDate(1392098430000); //=> false
```
### isExists
Check if a date exists in the calendar.
```typescript { .api }
function isExists(year: number, month: number, day: number): boolean;
```
**Parameters:**
- `year` - The year to check
- `month` - The month to check (0-11, January is 0)
- `day` - The day to check (1-31)
**Examples:**
```typescript
import { isExists } from "date-fns";
// Valid dates
isExists(2014, 1, 11); //=> true
isExists(2014, 1, 28); //=> true
// Invalid dates
isExists(2014, 1, 30); //=> false (Feb 30)
isExists(2014, 13, 1); //=> false (month 13)
isExists(2014, 1, 0); //=> false (day 0)
```
### isMatch
Check if a date string matches a format pattern.
```typescript { .api }
function isMatch(dateStr: string, formatStr: string, options?: IsMatchOptions): boolean;
```
**Parameters:**
- `dateStr` - The date string to validate
- `formatStr` - The format pattern to match against
- `options` - Optional matching configuration
**Examples:**
```typescript
import { isMatch } from "date-fns";
// Valid formats
isMatch('2014-02-11', 'yyyy-MM-dd'); //=> true
isMatch('11/02/2014', 'MM/dd/yyyy'); //=> true
isMatch('Feb 11, 2014', 'MMM dd, yyyy'); //=> true
// Invalid formats
isMatch('2014-02-11', 'dd/MM/yyyy'); //=> false
isMatch('invalid date', 'yyyy-MM-dd'); //=> false
isMatch('2014-13-01', 'yyyy-MM-dd'); //=> false (invalid month)
```
## Date Comparison
### Basic Comparison
```typescript { .api }
function isAfter(date: DateArg<Date>, dateToCompare: DateArg<Date>): boolean;
function isBefore(date: DateArg<Date>, dateToCompare: DateArg<Date>): boolean;
function isEqual(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isAfter, isBefore, isEqual } from "date-fns";
const date1 = new Date(2014, 0, 1);
const date2 = new Date(2014, 0, 2);
isAfter(date2, date1); //=> true
isBefore(date1, date2); //=> true
isEqual(date1, date1); //=> true
```
### Comparison Functions
```typescript { .api }
function compareAsc(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): number;
function compareDesc(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): number;
```
**Returns:**
- `-1` if first date is before second date
- `0` if dates are equal
- `1` if first date is after second date
**Examples:**
```typescript
import { compareAsc, compareDesc } from "date-fns";
const date1 = new Date(2014, 0, 1);
const date2 = new Date(2014, 0, 2);
compareAsc(date1, date2); //=> -1
compareAsc(date2, date1); //=> 1
compareAsc(date1, date1); //=> 0
// For sorting
const dates = [
new Date(1995, 6, 2),
new Date(1987, 1, 11),
new Date(1989, 6, 10)
];
dates.sort(compareAsc);
// Now sorted chronologically
```
### Min/Max Functions
```typescript { .api }
function min(dates: DateArg<Date>[]): Date;
function max(dates: DateArg<Date>[]): Date;
```
**Examples:**
```typescript
import { min, max } from "date-fns";
const dates = [
new Date(1989, 6, 10),
new Date(1987, 1, 11),
new Date(1995, 6, 2)
];
min(dates); //=> new Date(1987, 1, 11)
max(dates); //=> new Date(1995, 6, 2)
```
## Temporal Validation
### Relative to Current Time
```typescript { .api }
function isFuture(date: DateArg<Date>): boolean;
function isPast(date: DateArg<Date>): boolean;
function isToday(date: DateArg<Date>): boolean;
function isTomorrow(date: DateArg<Date>): boolean;
function isYesterday(date: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isFuture, isPast, isToday, isTomorrow, isYesterday } from "date-fns";
const now = new Date();
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
isFuture(tomorrow); //=> true
isPast(new Date(2020, 0, 1)); //=> true (assuming current year > 2020)
isToday(now); //=> true
```
### This Period Validation
```typescript { .api }
function isThisSecond(date: DateArg<Date>): boolean;
function isThisMinute(date: DateArg<Date>): boolean;
function isThisHour(date: DateArg<Date>): boolean;
function isThisWeek(date: DateArg<Date>, options?: WeekOptions): boolean;
function isThisISOWeek(date: DateArg<Date>): boolean;
function isThisMonth(date: DateArg<Date>): boolean;
function isThisQuarter(date: DateArg<Date>): boolean;
function isThisYear(date: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isThisWeek, isThisMonth, isThisYear } from "date-fns";
const now = new Date();
isThisWeek(now); //=> true
isThisMonth(now); //=> true
isThisYear(now); //=> true
// Last year
isThisYear(new Date(2020, 0, 1)); //=> false (if current year > 2020)
```
## Day of Week Validation
### Specific Day Checks
```typescript { .api }
function isMonday(date: DateArg<Date>): boolean;
function isTuesday(date: DateArg<Date>): boolean;
function isWednesday(date: DateArg<Date>): boolean;
function isThursday(date: DateArg<Date>): boolean;
function isFriday(date: DateArg<Date>): boolean;
function isSaturday(date: DateArg<Date>): boolean;
function isSunday(date: DateArg<Date>): boolean;
function isWeekend(date: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isMonday, isSaturday, isWeekend } from "date-fns";
// Monday, Feb 10, 2014
const monday = new Date(2014, 1, 10);
isMonday(monday); //=> true
isSaturday(monday); //=> false
// Saturday, Feb 8, 2014
const saturday = new Date(2014, 1, 8);
isWeekend(saturday); //=> true
```
## Same Period Validation
### Same Time Unit Checks
```typescript { .api }
function isSameSecond(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameMinute(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameHour(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameDay(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameWeek(dateLeft: DateArg<Date>, dateRight: DateArg<Date>, options?: WeekOptions): boolean;
function isSameISOWeek(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameMonth(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameQuarter(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameYear(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
function isSameISOWeekYear(dateLeft: DateArg<Date>, dateRight: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isSameDay, isSameWeek, isSameMonth, isSameYear } from "date-fns";
const date1 = new Date(2014, 8, 6, 14, 0);
const date2 = new Date(2014, 8, 6, 15, 30);
isSameDay(date1, date2); //=> true (same day, different time)
isSameWeek(date1, date2); //=> true
isSameMonth(date1, date2); //=> true
isSameYear(date1, date2); //=> true
// Different days
const date3 = new Date(2014, 8, 7);
isSameDay(date1, date3); //=> false
```
## Period Boundary Validation
### Month Boundaries
```typescript { .api }
function isFirstDayOfMonth(date: DateArg<Date>): boolean;
function isLastDayOfMonth(date: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isFirstDayOfMonth, isLastDayOfMonth } from "date-fns";
isFirstDayOfMonth(new Date(2014, 1, 1)); //=> true
isFirstDayOfMonth(new Date(2014, 1, 2)); //=> false
isLastDayOfMonth(new Date(2014, 1, 28)); //=> true (Feb in non-leap year)
isLastDayOfMonth(new Date(2014, 1, 27)); //=> false
```
### Leap Year
```typescript { .api }
function isLeapYear(date: DateArg<Date>): boolean;
```
**Examples:**
```typescript
import { isLeapYear } from "date-fns";
isLeapYear(new Date(2012, 0, 1)); //=> true
isLeapYear(new Date(2013, 0, 1)); //=> false
isLeapYear(new Date(2000, 0, 1)); //=> true
isLeapYear(new Date(1900, 0, 1)); //=> false
```
## Interval Validation
### Interval Checks
```typescript { .api }
function isWithinInterval(date: DateArg<Date>, interval: Interval): boolean;
function areIntervalsOverlapping(
intervalLeft: Interval,
intervalRight: Interval,
options?: IntervalOptions
): boolean;
```
**Examples:**
```typescript
import { isWithinInterval, areIntervalsOverlapping } from "date-fns";
// Within interval
isWithinInterval(new Date(2014, 0, 3), {
start: new Date(2014, 0, 1),
end: new Date(2014, 0, 7)
}); //=> true
// Overlapping intervals
areIntervalsOverlapping(
{ start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) },
{ start: new Date(2014, 0, 17), end: new Date(2014, 0, 21) }
); //=> true
// Non-overlapping intervals
areIntervalsOverlapping(
{ start: new Date(2014, 0, 10), end: new Date(2014, 0, 20) },
{ start: new Date(2014, 0, 21), end: new Date(2014, 0, 30) }
); //=> false
```
## Utility Functions
### Closest Date Finding
```typescript { .api }
function closestTo(dateToCompare: DateArg<Date>, dates: DateArg<Date>[]): Date;
function closestIndexTo(dateToCompare: DateArg<Date>, dates: DateArg<Date>[]): number;
```
**Examples:**
```typescript
import { closestTo, closestIndexTo } from "date-fns";
const dateToCompare = new Date(2015, 8, 6);
const dates = [
new Date(2015, 0, 1),
new Date(2016, 0, 1),
new Date(2017, 0, 1)
];
closestTo(dateToCompare, dates); //=> new Date(2015, 0, 1)
closestIndexTo(dateToCompare, dates); //=> 0
```
## Option Types
### WeekOptions
```typescript { .api }
interface WeekOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
```
### IsMatchOptions
```typescript { .api }
interface IsMatchOptions {
locale?: Locale;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
firstWeekContainsDate?: 1 | 2 | 3 | 4 | 5 | 6 | 7;
useAdditionalWeekYearTokens?: boolean;
useAdditionalDayOfYearTokens?: boolean;
}
```
### IntervalOptions
```typescript { .api }
interface IntervalOptions {
inclusive?: boolean;
}
```
## Common Patterns
### Validation Chain
```typescript
import { isValid, isAfter, isBefore } from "date-fns";
function validateDateRange(date: any, minDate: Date, maxDate: Date): boolean {
return isValid(date) &&
isAfter(date, minDate) &&
isBefore(date, maxDate);
}
```
### Array Filtering
```typescript
import { isWeekend, isFuture } from "date-fns";
const dates = [/* array of dates */];
// Filter weekend dates
const weekends = dates.filter(isWeekend);
// Filter future dates
const futureDates = dates.filter(isFuture);
```
### Safe Comparison
```typescript
import { isValid, compareAsc } from "date-fns";
function safeCompareAsc(date1: any, date2: any): number {
if (!isValid(date1) || !isValid(date2)) {
throw new Error('Invalid date provided');
}
return compareAsc(date1, date2);
}
```

Some files were not shown because too many files have changed in this diff Show more