diff --git a/.changeset/config.json b/.changeset/config.json index 5c58ec9..ec98e35 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -4,7 +4,7 @@ "commit": false, "fixed": [], "linked": [], - "access": "restricted", + "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] diff --git a/.changeset/v1-0-0-core-foundation.md b/.changeset/v1-0-0-core-foundation.md deleted file mode 100644 index a5a8e5c..0000000 --- a/.changeset/v1-0-0-core-foundation.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -"@deessejs/errors": major ---- - -## v1.0.0 — Core Foundation - -Initial release of `@deessejs/errors`, a function-based error handling library inspired by Python's error system. - -### Added - -- `error()` function for defining error types with Standard Schema support -- `raise()` function for throwing errors -- Native `throw` syntax support -- `is()` function for type checking with inheritance support -- `inherits` option for single and multiple inheritance -- `.from()` method for exception chaining -- `causes()` function for chain traversal (most recent first) -- `err.fields` namespace for user-defined data -- Message templates with `{field}` placeholders -- All error properties always defined (never undefined) -- Standard Schema compliance for field definitions (Zod, Valibot, ArkType) - -### TypeScript Support - -- Generic types: `ErrorFactory`, `ErrorInstance` -- Full type inference with fields -- No `any` — only generics and proper types \ No newline at end of file diff --git a/.claude/agent-memory/design-engineer/MEMORY.md b/.claude/agent-memory/design-engineer/MEMORY.md new file mode 100644 index 0000000..e535223 --- /dev/null +++ b/.claude/agent-memory/design-engineer/MEMORY.md @@ -0,0 +1,4 @@ +# Design Engineer Memory Index + +- [Role & Scope](role-and-scope.md) — My responsibilities in apps/web +- [Tech Stack](project-tech-stack.md) — Current technologies and key files \ No newline at end of file diff --git a/.claude/agent-memory/design-engineer/project-tech-stack.md b/.claude/agent-memory/design-engineer/project-tech-stack.md new file mode 100644 index 0000000..85e512e --- /dev/null +++ b/.claude/agent-memory/design-engineer/project-tech-stack.md @@ -0,0 +1,29 @@ +--- +name: project-tech-stack +description: Tech stack of apps/web +type: reference +--- + +## apps/web Tech Stack + +| Category | Technology | +|----------|------------| +| Framework | Next.js 16.2.6 | +| UI Library | React 19.2.6 | +| Styling | Tailwind CSS v4.3.0 | +| CSS Processor | @tailwindcss/postcss 4.3.0 | +| Documentation | FumaDocs 16.9.1 (fumadocs-core, fumadocs-mdx, fumadocs-ui) | +| Icons | lucide-react 1.16.0 | +| Utility | tailwind-merge 3.6.0 | + +## Key Files + +- `src/app/global.css` — Global styles + Tailwind imports +- `src/app/layout.tsx` — Root layout with Inter font + RootProvider +- `src/lib/source.ts` — FumaDocs content loader +- `src/lib/shared.ts` — App config (name, routes, GitHub) +- `src/components/mdx.tsx` — MDX component factory + +## Package Manager + +pnpm (workspace monorepo) \ No newline at end of file diff --git a/.claude/agent-memory/design-engineer/role-and-scope.md b/.claude/agent-memory/design-engineer/role-and-scope.md new file mode 100644 index 0000000..25b1c87 --- /dev/null +++ b/.claude/agent-memory/design-engineer/role-and-scope.md @@ -0,0 +1,29 @@ +--- +name: role-and-scope +description: My role as Senior Design Engineer for apps/web +type: user +--- + +## Role + +Senior Design Engineer — responsible for all UI/UX design work in `apps/web`. + +## Scope + +- **Next.js 16** + React 19 +- **Tailwind CSS v4** (via `@tailwindcss/postcss`) +- **FumaDocs** for documentation site +- **shadcn/ui** — to be installed and integrated +- **lucide-react** for icons + +## Responsibilities + +- UI component design and implementation +- Tailwind theme customization +- Responsive design +- Design system consistency +- Integration with FumaDocs layouts + +## Working Directory + +`C:\Users\dpereira\Documents\github\deessejs-ecosystem\errors\.claude\worktrees\docs\apps\web` \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/MEMORY.md b/.claude/agent-memory/technical-writer/MEMORY.md new file mode 100644 index 0000000..ef4f314 --- /dev/null +++ b/.claude/agent-memory/technical-writer/MEMORY.md @@ -0,0 +1,9 @@ +# Technical Writer Memory Index + +- [Project Analysis](project-analysis.md) — Monorepo structure, tech stack, core library API, documentation status +- [Fumadocs Docs](fumadocs-docs-reference.md) — fumadocs.dev reference for MDX/UI components +- [Fumadocs meta.json](fumadocs-meta-json.md) — Navigation tree configuration +- [Fumadocs Components](fumadocs-components.md) — Complete components reference with Twoslash +- [Documentation Rules](feedback-documentation-rules.md) — h1/description forbidden, Cards for See Also, explanatory guides +- [Writing Style](documentation-writing-style.md) — Style from better-auth, Next.js, Fumadocs +- [Project Notes](project-notes.md) — First PR notes, Vercel config, theme, site config \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/documentation-writing-style.md b/.claude/agent-memory/technical-writer/documentation-writing-style.md new file mode 100644 index 0000000..dd03a8f --- /dev/null +++ b/.claude/agent-memory/technical-writer/documentation-writing-style.md @@ -0,0 +1,77 @@ +--- +name: documentation-writing-style +description: Writing style analysis from better-auth, Next.js, and Fumadocs +type: reference +--- + +# Documentation Writing Style Guide + +Analyzed from better-auth.com, nextjs.org/docs, and fumadocs.dev + +## Recurring Patterns + +### Structure +1. **Intro** — 1-2 contextual sentences before diving in +2. **Steps** — Numbered or bulleted sequences +3. **Code blocks** — Always with filename header or context +4. **Callouts** — Tips, warnings, "Good to know" +5. **Navigation** — "On this page" sidebar or related links at bottom +6. **See Also** — Cards at end of page for cross-linking + +### Code Blocks +- Include filename in header or comment above +- Show language/format +- Display in tabs when multiple options (npm/pnpm/yarn) +- Use transformers for highlighting (twoslash for types) + +### Callouts +- `info` (default) — general info +- `warn`/`warning` — caution +- `error` — danger +- `success` — positive outcome +- `idea` — tip or enhancement + +### Prose Style +- Short, action-oriented sentences ("Let's start by...") +- Explain WHY before showing HOW +- Contextual paragraphs between code blocks +- Tables for options/configuration +- Numbered steps for procedures + +### Navigation +- "On this page" in-page TOC (Fumadocs auto-generates) +- Cards at bottom for related pages +- Never bullet lists for related links + +## Template for New Pages + +```mdx +--- +title: Page Title +description: One-line SEO description +--- + +Intro paragraph explaining the concept in 1-2 sentences. + +## First Section + +Contextual prose... + +```ts filename.ts +// code here +``` + +More explanatory text. + +## Second Section + +... + +## See Also + + + + Brief description. + + +``` \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/feedback-documentation-rules.md b/.claude/agent-memory/technical-writer/feedback-documentation-rules.md new file mode 100644 index 0000000..188ae3e --- /dev/null +++ b/.claude/agent-memory/technical-writer/feedback-documentation-rules.md @@ -0,0 +1,47 @@ +--- +name: documentation-rules +description: Rules for writing documentation in @deessejs/errors +type: feedback +--- + +# Documentation Writing Rules + +## Rule 1: No h1 in MDX content +**Why:** The page title is set via frontmatter `title` property, not in content. +**How to apply:** Start content with h2 (`##`) or higher. Never use `#` headings. + +## Rule 2: No description in content +**Why:** Description for SEO is set via frontmatter `description` property. +**How to apply:** Don't repeat the description in the content body. + +## Rule 3: No code in titles +**Why:** Titles should be readable and descriptive without code. +**How to apply:** Write titles as concepts, not API names. Exception: if the concept IS the code (e.g., "The `error()` function"), but avoid raw code in titles. + +## Rule 4: No code-only blocks +**Why:** Documentation should be educational guides, not reference dumps. +**How to apply:** Every code block must be preceded by explanatory paragraphs. Tell WHY, not just WHAT. + +## Rule 5: Explain, don't just show +**Why:** Readers need context to understand when and why to use a feature. +**How to apply:** +- Lead with prose explaining the concept +- Include paragraphs between code blocks +- Explain the output/what happens +- Add "why would you use this?" context + +## Rule 6: Every file needs a meaningful filename +**Why:** URLs should be descriptive and SEO-friendly. +**How to apply:** Use kebab-case descriptive names like `error-factory.mdx`, not `api.mdx` or `guide1.mdx`. + +## Rule 7: See Also section uses Cards, not lists +**Why:** Cards are the Fumadocs standard for cross-linking related pages. +**How to apply:** +```mdx + + + Brief description of why this is related. + + +``` +Never use markdown bullet lists for related links. \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/fumadocs-components.md b/.claude/agent-memory/technical-writer/fumadocs-components.md new file mode 100644 index 0000000..af45641 --- /dev/null +++ b/.claude/agent-memory/technical-writer/fumadocs-components.md @@ -0,0 +1,207 @@ +--- +name: fumadocs-components +description: Complete list of Fumadocs UI components with usage examples +type: reference +--- + +# Fumadocs UI Components Reference + +## MDX Components (default, included) + +### Cards +```mdx + + Description + } title="With Icon" href="/">Desc + Content here + +``` + +### Callouts +```mdx +Default info +Content +Content +Content +Content +``` + +### Steps (remark plugin) +```mdx +import { Step, Steps } from 'fumadocs-ui/components/steps'; + + + ### Installation + ### Configuration + ### Deploy + +``` +Or via markdown: `### Installation [step]` + +## Additional Components (install with CLI) + +### Tabs +```mdx +import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; + + + npm install + pnpm add + yarn add + + +// Shared value across pages + + npm install + pnpm add + +``` + +### Accordion +```mdx +import { Accordion, Accordions } from 'fumadocs-ui/components/accordion'; + + + Answer 1 + Answer 2 + +``` + +### Files (file tree) +```mdx +import { File, Folder, Files } from 'fumadocs-ui/components/files'; + + + + + + + + +``` + +### TypeTable +```mdx +import { TypeTable } from 'fumadocs-ui/components/type-table'; + + +``` + +### ImageZoom +```tsx +// In components/mdx.tsx +import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; + +return { + ...defaultComponents, + img: (props) => , +}; +``` + +### Banner (root layout) +```tsx +import { Banner } from 'fumadocs-ui/components/banner'; + +Announcement here +Colorful banner +Dismissible banner +``` + +## Code Blocks Features + +### Line Numbers +````md +```ts lineNumbers +const a = 'Hello'; +console.log(a); +``` +```` + +### Shiki Transformers +````md +```tsx +// [!code highlight] - highlight line +// [!code word:word] - highlight word +// [!code --] - red (removal) +// [!code ++] - green (addition) +// [!code focus] - focus line +``` + +```ts twoslash - TypeScript type hover +``` +```` + +### Tab Groups (built-in) +````md +```ts tab="npm" +npm install package +``` + +```ts tab="pnpm" +pnpm add package +``` +```` + +## Twoslash Setup + +```bash +npm install fumadocs-twoslash twoslash +``` + +```ts +// next.config.mjs +{ + serverExternalPackages: ['typescript', 'twoslash'], +} + +// source.config.ts +import { transformerTwoslash } from 'fumadocs-twoslash'; +import { rehypeCodeDefaultOptions } from 'fumadocs-core/mdx-plugins'; + +export default defineConfig({ + mdxOptions: { + rehypeCodeOptions: { + langs: ['js', 'jsx', 'ts', 'tsx'], + transformers: [...rehypeCodeDefaultOptions.transformers, transformerTwoslash()], + }, + }, +}); + +// Tailwind v4 +@import 'fumadocs-twoslash/twoslash.css'; + +// components/mdx.tsx +import * as Twoslash from 'fumadocs-twoslash/ui'; +return { ...defaultComponents, ...Twoslash }; +``` + +### Twoslash Annotations +- `// ^?` - hover type +- `// @ts-err` - expected error +- comments show inline + +## Frontmatter + +```yaml +--- +title: Page Title +description: SEO description +icon: HomeIcon # Lucide icon name +--- +``` + +## MDX Features + +- Auto links (internal/external) +- Anchor headings +- Include other files: `` +- NPM commands: ` ```npm install ``` ` +- Mermaid diagrams (via plugin) \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/fumadocs-docs-reference.md b/.claude/agent-memory/technical-writer/fumadocs-docs-reference.md new file mode 100644 index 0000000..25a0b35 --- /dev/null +++ b/.claude/agent-memory/technical-writer/fumadocs-docs-reference.md @@ -0,0 +1,38 @@ +--- +name: fumadocs-docs-reference +description: Fumadocs v16 documentation at fumadocs.dev - use fresh to fetch +type: reference +--- + +# Fumadocs Documentation Reference + +**URL:** https://www.fumadocs.dev/ + +**Access:** Use `fresh fetch ` to retrieve content + +## Key areas to explore + +When documenting @deessejs/errors with Fumadocs: + +1. **MDX Collections** — `source.config.ts` uses `defineDocs` and `pageSchema` + - Frontmatter schema: title, description + - Post-processing options + +2. **Layout Components** — How `DocsLayout`, `DocsPage` work + - `getMDXComponents()` for custom MDX components + - Relative linking between docs + +3. **UI Components** — Available MDX components + - ``, `` — Navigation + - ``, `` — Code examples + - `` — Tutorials + +4. **Configuration** — `defineConfig` options + - MDX options + - Source plugins (lucide-icons shown in source.ts) + +## Relevant for + +- Creating documentation structure in `content/docs/` +- Custom MDX component development +- Navigation/tree configuration \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/fumadocs-meta-json.md b/.claude/agent-memory/technical-writer/fumadocs-meta-json.md new file mode 100644 index 0000000..d11ec1f --- /dev/null +++ b/.claude/agent-memory/technical-writer/fumadocs-meta-json.md @@ -0,0 +1,71 @@ +--- +name: fumadocs-meta-json +description: meta.json configuration for navigation tree in Fumadocs +type: reference +--- + +# Fumadocs meta.json Reference + +**Location:** `content/docs//meta.json` + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `title` | string | Display name in sidebar | +| `icon` | string | Lucide icon name | +| `defaultOpen` | boolean | Open folder by default | +| `collapsible` | boolean | Allow folder collapse (default: true) | +| `pages` | string[] | Custom page ordering | +| `pagesIndex` | string | Index page path or link | + +## pages[] Item Types + +| Type | Syntax | Description | +|------|--------|-------------| +| Path | `"./path/to/page"` | Path to page or folder | +| Separator | `"---Label---"` | Section separator | +| Link | `"[Text](url)"` | Internal link | +| External | `"external:[Text](url)"` | External link with icon | +| Rest | `"..."` | Include remaining pages (alphabetical) | +| Reversed | `"z...a"` | Include remaining pages (reversed) | +| Extract | `"...folder"` | Extract items from subfolder | +| Except | `"!item"` | Exclude from `...` or `z...a` | + +## Slug Conventions + +| Path Pattern | Slugs | +|--------------|-------| +| `./dir/page.mdx` | `['dir', 'page']` | +| `./dir/index.mdx` | `['dir']` | +| `./(group)/page.mdx` | `['page']` (group not in slug) | + +## Example + +```json +{ + "title": "Guide", + "defaultOpen": true, + "pages": [ + "index", + "getting-started", + "---API Reference---", + "...", + "!deprecated-page", + "[GitHub](https://github.com/...)" + ] +} +``` + +## Root Folder (meta.json with `root: true`) + +Marks folder as root - other folders hidden in sidebar. + +```json +{ + "title": "Framework", + "root": true +} +``` + +Renders as Layout Tabs in Fumadocs UI. \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/project-analysis.md b/.claude/agent-memory/technical-writer/project-analysis.md new file mode 100644 index 0000000..82e8499 --- /dev/null +++ b/.claude/agent-memory/technical-writer/project-analysis.md @@ -0,0 +1,69 @@ +# @deessejs/errors Project Analysis + +## Project Overview + +**@deessejs/errors** is a TypeScript error handling library inspired by Python's exception system. It provides exception chaining, hierarchical inheritance, and rich error semantics through a function-based API. + +## Repository Structure + +``` +@deessejs/errors/ (pnpm monorepo) +├── packages/ +│ └── errors/ # Core library package +│ ├── src/ +│ │ ├── index.ts # Public API exports +│ │ ├── causes/ # Cause chain traversal +│ │ ├── error/ # Error factory (capture.ts, error.ts, format.ts, types.ts) +│ │ ├── is/ # Error type checking +│ │ └── raise/ # Error raising utilities +│ ├── tests/ # Vitest test suite +│ ├── examples/ # Usage examples +│ ├── internal/ # Internal documentation +│ └── learnings/ # Learning notes +├── apps/ +│ └── web/ # Documentation website +│ ├── content/docs/ # MDX documentation files (index.mdx, test.mdx) +│ ├── src/app/ # Next.js 16 app router +│ ├── src/components/ # React components +│ └── src/lib/ # Utilities +├── docs/ # Worktree directory (technical-writer agent) +└── temp/ # Temporary files +``` + +## Key Technologies + +| Component | Technology | +|-----------|------------| +| Core lib | TypeScript, Vitest, ESLint | +| Package manager | pnpm 10.30.3 | +| Build | Turbo | +| Versioning | Changesets | +| Docs site | Next.js 16, Fumadocs, Tailwind CSS, React 19 | + +## Core Library API (public exports) + +From `src/index.ts`: +- `error()` — Error factory function +- `raise` — Error raising function +- `is()` — Error type checking +- `causes()` — Cause chain traversal +- Types: `ErrorFactory`, `ErrorInstance`, `ErrorInstanceCore` + +## Documentation Status + +**Current state:** Minimal/starter documentation +- Only 2 placeholder MDX files in `apps/web/content/docs/` +- No real documentation content yet +- This worktree is dedicated to creating documentation + +## Branching Strategy + +- `main` ← `staging` ← `dev` +- All developers push directly to `main` +- Release engineer manages main → staging → main flow + +## Notes + +- CLAUDE.md states: "Always communicate in English" even though user communicates in French +- Uses `fresh` CLI for web searches (not standard search) +- Based on `nesalia-inc/errors` (production version) \ No newline at end of file diff --git a/.claude/agent-memory/technical-writer/project-notes.md b/.claude/agent-memory/technical-writer/project-notes.md new file mode 100644 index 0000000..fdee31c --- /dev/null +++ b/.claude/agent-memory/technical-writer/project-notes.md @@ -0,0 +1,39 @@ +--- +name: project-notes +description: Notes from first documentation PR +type: project +--- + +# Documentation Project Notes + +## PR: First Draft (PR #11) +Branch: `docs/first-draft` → `main` +Status: Ready for review + +## Key Learnings + +### Code Block Titles +- Syntax: ` ```ts title="filename.ts" ` (NOT ` ```ts filename.ts `) +- The title attribute goes AFTER the language, not as part of it + +### Vercel Config +- Root directory: `/` (not `apps/web`) +- Build command: `pnpm --filter web build` +- Output directory: `apps/web/.next` +- Added `vercel.json` at repo root + +### Theme +- File: `apps/web/src/app/global.css` +- Change: `fumadocs-ui/css/neutral.css` → `fumadocs-ui/css/black.css` + +### Site Config +- File: `apps/web/src/lib/shared.ts` +- `appName`: '@deessejs/errors' +- `gitConfig`: user='nesalia-inc', repo='errors' + +## Files Modified +- 13 MDX docs pages in `apps/web/content/docs/` +- `apps/web/content/docs/meta.json` +- `apps/web/src/lib/shared.ts` +- `apps/web/src/app/global.css` +- `vercel.json` (new) \ No newline at end of file diff --git a/.claude/agents/design-engineer/README.md b/.claude/agents/design-engineer/README.md new file mode 100644 index 0000000..8e75bbb --- /dev/null +++ b/.claude/agents/design-engineer/README.md @@ -0,0 +1,11 @@ +--- +name: design-engineer +description: Senior Design Engineer +model: sonnet +memory: project +color: green +--- + +# Senior Design Engineer + +**Role:** You are the Senior Design Engineer. diff --git a/.claude/agents/technical-writer/README.md b/.claude/agents/technical-writer/README.md new file mode 100644 index 0000000..b1e65fd --- /dev/null +++ b/.claude/agents/technical-writer/README.md @@ -0,0 +1,11 @@ +--- +name: technical-writer +description: Senior Technical Writer +model: sonnet +memory: project +color: green +--- + +# Senior Technical Writer + +**Role:** You are the Senior Senior Technical Writer. diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 597bdde..50f7519 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -28,8 +28,7 @@ "Bash(xargs kill*)", "Bash(taskkill*)", - "Bash(git rebase *)", - "Bash(git reset --hard*)", + "Bash(git reset --hard*)", "Bash(git clean*)", "Bash(sudo *)", diff --git a/.claude/skills/triage/SKILL.md b/.claude/skills/triage/SKILL.md new file mode 100644 index 0000000..a38703d --- /dev/null +++ b/.claude/skills/triage/SKILL.md @@ -0,0 +1,205 @@ +--- +name: triage +description: Triage a GitHub issue and apply labels based on project taxonomy +--- + +You are a senior tech lead responsible for triaging incoming GitHub issues. + +## Your Task + +1. Use `gh issue view --json title,body,labels` to fetch the issue details +2. Analyze the issue based on its template type (bug, feature, refactor, etc.) +3. Apply the correct labels based on the taxonomy +4. Post a triage comment explaining the decision + +## Important: GH CLI + +The `gh` CLI is available. Use it directly for all GitHub operations: +- `gh issue view --json title,body,labels` - Get issue details +- `gh issue edit --add-label "label1,label2"` - Add labels +- `gh issue comment create --body "text"` - Post a comment + +## Project Label Taxonomy + +### Type Labels (one required) +- `type: bug` — Bug/defect fix +- `type: feature` — New feature +- `type: refactor` — Code restructuring +- `type: docs` — Documentation +- `type: chore` — Maintenance/task + +### Status Labels (one required) +- `status: triage` — Tech Lead hasn't reviewed yet +- `status: needs-info` — Incomplete, needs more info +- `status: ready` — Validated, ready to pick up +- `status: blocked` — Depends on another task + +### Closure Labels +- `type: duplicate` — Duplicate of another issue +- `type: wontfix` — Will not be addressed +- `type: question` — Question or discussion, not a task + +### Priority Labels (optional) +- `p0: critical` — Everything stops, fix now +- `p1: high` — Required for next release +- `p2: medium` — Normal priority +- `p3: low` — Nice to have + +### Effort Labels (optional) +- `effort: xs` — Few minutes +- `effort: s` — Half a day +- `effort: m` — 1-2 days +- `effort: l` — Week or more + +## Triage Decision Tree + +1. **Is the issue complete?** (has all required fields from template) + - YES → `status: ready` + - NO → `status: needs-info` + +2. **Is it a valid task?** (not a duplicate, question, or wontfix) + - NO → Use closure labels (`type: duplicate`, `type: wontfix`, `type: question`) + +3. **Is it blocked?** (depends on another issue or decision) + - YES → `status: blocked` + link the blocking issue + +## Comment Templates + +Use the appropriate template below based on your triage decision: + +### Template: `status: ready` + +```bash +gh issue comment create --body "## Triage Review + +**Type:** \`type: \` +**Status:** \`status: ready\` - All required information provided +**Priority:** \`p?: \` (if indicated) +**Effort:** \`effort: \` (if indicated) + +**Decision:** + +This issue contains all required information and is ready to be picked up. + +--- +*Triage by Tech Lead Agent*" +``` + +### Template: `status: needs-info` + +```bash +gh issue comment create --body "## Triage Review + +**Status:** \`status: needs-info\` - Additional information required + +**Decision:** This issue is missing required information and cannot be triaged yet. + +**Missing fields:** + + +Please update the issue with the missing information so it can be properly triaged. + +--- +*Triage by Tech Lead Agent*" +``` + +### Template: `type: duplicate` + +```bash +gh issue comment create --body "## Triage Review + +**Status:** \`type: duplicate\` - Duplicate of + +**Decision:** This issue appears to be a duplicate of an existing issue. + + + +--- +*Triage by Tech Lead Agent*" +``` + +### Template: `type: wontfix` + +```bash +gh issue comment create --body "## Triage Review + +**Status:** \`type: wontfix\` - Will not be addressed + +**Decision:** After review, this issue does not align with current priorities or technical direction. + + + +--- +*Triage by Tech Lead Agent*" +``` + +### Template: `type: question` + +```bash +gh issue comment create --body "## Triage Review + +**Status:** \`type: question\` - This appears to be a question + +**Decision:** This issue seems to be a question rather than a task or bug report. + + + +For questions, consider using GitHub Discussions instead of issues. + +--- +*Triage by Tech Lead Agent*" +``` + +### Template: `status: blocked` + +```bash +gh issue comment create --body "## Triage Review + +**Type:** \`type: \` +**Status:** \`status: blocked\` - Blocked by # + +**Decision:** This issue depends on work that is not yet complete. + +**Blocking issue:** # - + +Once the blocking issue is resolved, this can be moved to \`status: ready\`. + +--- +*Triage by Tech Lead Agent*" +``` + +## Workflow + +After making your triage decision: + +1. **Check existing labels first:** + ```bash + gh issue view --json labels + ``` + Note which labels are already present. + +2. **Add only missing labels:** + ```bash + gh issue edit --add-label "label1,label2" + ``` + - Skip labels that are already present + - Only add the labels you determined are needed + - Do NOT remove labels - even if incorrect, leave them for manual review + +3. **Post the appropriate comment** using one of the templates above + +4. **Check for blocking issues** if applicable + +## Label Handling Rules + +- **Always check existing labels** before adding new ones +- **Add missing labels only** - never remove user-added labels +- **Preserve user intent** - if a user added a label, keep it even if it seems incorrect +- **Respect existing status** - if issue already has `status: ready`, don't downgrade to `status: triage` + +## Notes + +- Always post a comment - it helps the submitter understand the decision +- Be concise but informative +- If multiple labels apply, add all of them +- When in doubt, add labels rather than removing them \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d302024 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +MINIMAX_API_KEY=your-minimax-token-plan-key-here \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1c41ea..f7fb274 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Cache Turborepo @@ -38,4 +38,4 @@ jobs: run: pnpm install - name: Run build - run: pnpm build \ No newline at end of file + run: pnpm build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 93e7fe1..04d0bf9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Cache Turborepo @@ -37,5 +37,8 @@ jobs: - name: Install dependencies run: pnpm install + - name: Run prettier check + run: pnpm format:check + - name: Run lint - run: pnpm lint \ No newline at end of file + run: pnpm lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8f2626..2616616 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,21 +6,29 @@ on: pull_request: types: - closed + branches: + - main permissions: contents: write + id-token: write jobs: release: name: Release runs-on: ubuntu-latest - # Only run when PR is merged and has version bump label OR manually triggered - if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'version bump')) + # Only run when PR is merged into main and has version bump label OR manually triggered + if: | + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + github.event.pull_request.base.ref == 'main' && + contains(github.event.pull_request.labels.*.name, 'version bump')) steps: - name: Checkout uses: actions/checkout@v4 with: + ref: main fetch-depth: 0 - name: Setup pnpm @@ -29,12 +37,23 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Install dependencies run: pnpm install + - name: Create versions from changesets + run: pnpm changeset version + + - name: Commit version changes and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "chore: apply changeset version bumps" || true + git push origin HEAD && git push --tags + - name: Build run: pnpm build @@ -42,9 +61,17 @@ jobs: run: pnpm test - name: Publish packages - run: pnpm release + run: pnpm changeset publish + + - name: Get latest tag + id: tag + run: echo "version=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.version }} + body_path: packages/errors/CHANGELOG.md + draft: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ${{ vars.TURBO_TEAM }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9007a37..0b84103 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Cache Turborepo @@ -38,4 +38,4 @@ jobs: run: pnpm install - name: Run tests - run: pnpm test \ No newline at end of file + run: pnpm test diff --git a/.github/workflows/types.yml b/.github/workflows/types.yml index 267f79f..1c29d80 100644 --- a/.github/workflows/types.yml +++ b/.github/workflows/types.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Cache Turborepo @@ -38,4 +38,4 @@ jobs: run: pnpm install - name: Run type check - run: pnpm turbo type-check \ No newline at end of file + run: pnpm turbo type-check diff --git a/.husky/pre-commit b/.husky/pre-commit index f4097ad..5ee7abd 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -pnpm lint && pnpm turbo type-check +pnpm exec lint-staged diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..6ea444f --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*.{ts,tsx,js,mjs,cjs}": ["eslint --fix", "prettier --write"], + "*.{json,md,css,yml,yaml}": ["prettier --write"] +} diff --git a/.npmrc b/.npmrc index 8aac27c..62f3ca8 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ -# Shamefully hoist packages to fix ESLint/TypeScript tooling issues -shamefully-hoist=true +# Per docs/learnings/pnpm/ — shamefully-hoist=true and legacy-peer-deps=true +# were removed in the Tier 2 hardening. apps/web (Next.js) issues with hoisting +# should be addressed via targeted public-hoist-pattern entries in pnpm-workspace.yaml. diff --git a/.prettierignore b/.prettierignore index 8c68afe..197d346 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,16 @@ dist/ .turbo/ .cache/ .tmp/ +temp/ + +# Agent tooling metadata (not project source) +.claude/ + +# Generated files +apps/web/.source/ +apps/web/next-env.d.ts + +# Internal learning docs (informational, formatted manually) +docs/learnings/ + *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index dd912b6..c801f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,4 +17,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -### Security \ No newline at end of file +### Security diff --git a/CLAUDE.md b/CLAUDE.md index 5aeaf57..1432680 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,7 @@ fresh fetch https://react.dev/docs ``` Available commands: + - `fresh auth` - Authentication commands - `fresh search [options]` - Search the web using Exa.ai - `fresh fetch [options] ` - Fetch and extract content from a URL diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2942338..9e232b3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -107,4 +107,4 @@ enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. \ No newline at end of file +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcda89f..63edfcc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,4 +64,4 @@ pnpm turbo test # Run tests across all packages ## Questions? -Open an issue or reach out to us at **support@nesalia.com**. \ No newline at end of file +Open an issue or reach out to us at **support@nesalia.com**. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..26d7d73 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,242 @@ +# Design System — @deessejs/errors Documentation + +## Overview + +Design system and visual guidelines for the `@deessejs/errors` documentation website. Minimal, developer-focused, blueprint aesthetic. + +## Tech Stack + +| Technology | Version | Purpose | +| ------------ | -------- | ----------------------- | +| Next.js | 16.x | React framework | +| Tailwind CSS | v4 | Styling | +| Geist | (Vercel) | Typography (future) | +| shadcn/ui | TBD | Component library | +| FumaDocs | 16.x | Documentation framework | +| lucide-react | 1.x | Icons | + +## Typography + +**Font**: Geist (via `next/font/geist` or Vercel CDN) + +```typescript +import { Geist } from 'next/font/google'; + +const geist = Geist({ + subsets: ['latin'], + display: 'swap', +}); +``` + +**Scale**: + +- Headings: `font-bold tracking-tight` +- Body: `text-lg` for main content, `text-base` for details +- Code: JetBrains Mono (monospace) +- Labels: `font-medium text-gray-500 uppercase tracking-wider` + +## Color Palette + +Using Tailwind's default palette with these semantic usages: + +| Token | Usage | +| ----------------------- | -------------------------------- | +| `gray-950` | Primary text (`#030304`) | +| `gray-500` | Secondary text / muted | +| `gray-300` | Borders | +| `gray-200` | Dividers, subtle backgrounds | +| `white` | Cards, elevated surfaces | +| `#f8f9fb` | Off-white body background | +| `blue-500` / `blue-600` | Accent color (links, highlights) | + +### Dark Mode + +Support both light and dark modes using Tailwind's `dark:` variants. + +## Visual Language + +### Border Radius + +**No rounded corners** — `rounded-none` throughout. + +Exception: `
` code blocks use `rounded-none` with sharp corners.
+
+### Blueprint Grid Background
+
+SVG-based grid pattern for hero sections:
+
+```html
+
+  
+    
+      
+    
+  
+  
+
+```
+
+### Cards
+
+```html
+
+
+ +
+ + + + + +``` + +### Buttons + +**Primary** (dark): + +```html + +``` + +**Secondary** (outlined): + +```html + +``` + +### Floating Squares (Hero Decoration) + +Blueprint-style floating squares for visual interest: + +```tsx +const floatingSquares = [ + { x: 300, y: 120, opacity: 1.0, delay: 0.7, duration: 4 }, + { x: 220, y: 60, opacity: 0.8, delay: 0.3, duration: 4 }, + { x: 160, y: 160, opacity: 0.5, delay: 0.0, duration: 4 }, +]; +``` + +### Code Blocks + +Sharp corners, syntax highlighting, filename tabs: + +```html +
+
+ filename.ts +
+
...
+
+``` + +### Navigation + +```html + +``` + +## Layout Structure + +``` +apps/web/src/ +├── app/ +│ ├── layout.tsx # Root layout (Geist font, RootProvider) +│ ├── global.css # Tailwind imports + custom styles +│ ├── page.tsx # Homepage +│ └── docs/ +│ ├── layout.tsx # DocsLayout with sidebar +│ └── [[...slug]]/ # Dynamic doc pages +├── components/ +│ ├── mdx.tsx # MDX component factory +│ └── ui/ # shadcn/ui components (future) +└── lib/ + ├── source.ts # FumaDocs content loader + └── shared.ts # App config +``` + +### Max Width + +- Content: `max-w-6xl mx-auto px-6` (1152px) +- Hero: `max-w-6xl mx-auto px-6` +- Docs: handled by FumaDocs layouts + +### Spacing Scale + +Use Tailwind's 4px incremental scale consistently: + +- `pt-20 lg:pt-28` for hero sections +- `py-24` for section padding +- `gap-12 lg:gap-20` for large grids + +## Responsive Breakpoints + +| Breakpoint | Target | +| ------------- | ---------------- | +| `sm` (640px) | Mobile landscape | +| `md` (768px) | Tablets | +| `lg` (1024px) | Laptops | +| `xl` (1280px) | Desktops | + +## shadcn/ui Integration + +```bash +npx shadcn@latest init +``` + +### Core Components to Implement + +| Component | Purpose | Status | +| --------- | -------------------------------------- | ------ | +| Button | Primary/secondary actions | TBD | +| Card | Feature highlights, deployment options | TBD | +| Callout | Notes, warnings, tips, errors | TBD | +| Code | Syntax highlighted code blocks | TBD | +| Tabs | API reference navigation | TBD | +| Table | Parameter documentation | TBD | +| Badge | Labels, version tags | TBD | + +### shadcn/ui Configuration + +Update `components.json` to match our design tokens: + +```json +{ + "rounded": "none", + "cssVariables": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/global.css" + } +} +``` + +## Design Principles + +1. **Developer-First**: Clarity over decoration. Code examples are the hero. +2. **Minimal**: Ample whitespace, clean layouts, `rounded-none` everywhere. +3. **Blueprint Aesthetic**: Subtle grid backgrounds, floating squares, schematic vibes. +4. **Dark Mode Ready**: All components work in both light and dark. +5. **Consistent Spacing**: 4px increments via Tailwind scale. + +## Future Enhancements + +- [ ] Integrate shadcn/ui with `rounded-none` +- [ ] Implement Geist font +- [ ] Add blueprint grid SVG components +- [ ] Create floating squares animation +- [ ] Custom callout components for error types +- [ ] Deploy section with platform cards +- [ ] Architecture diagram component + +--- + +**Last updated**: 2026-06-02 diff --git a/README.md b/README.md index 66669dc..0de1045 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ # @deessejs/errors +[![npm](https://img.shields.io/npm/v/@deessejs/errors)](https://www.npmjs.com/package/@deessejs/errors) +[![TypeScript](https://img.shields.io/badge/typescript-%E2%9A%99%EF%B8%8F-blue)](https://www.typescriptlang.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + A TypeScript error handling library with exception chaining, hierarchical inheritance, and rich error semantics — inspired by Python's error system. ## Features -- **Exception Chaining** — Preserve the full context of errors with cause chains -- **Hierarchical Inheritance** — Organize errors in meaningful hierarchies -- **Rich Error Semantics** — Attach metadata, codes, and structured data to errors +- **Exception Chaining** — Preserve the full context of errors with cause chains via `.from()` +- **Hierarchical Inheritance** — Organize errors in meaningful hierarchies with single or multiple inheritance +- **Rich Error Semantics** — Attach structured data, templates, and notes to errors - **TypeScript First** — Full type safety with comprehensive type definitions ## Installation @@ -21,25 +25,154 @@ yarn add @deessejs/errors ## Quick Start +### Creating Errors + +```typescript +import { error } from '@deessejs/errors'; + +// Simple error +const ValidationError = error({ name: 'ValidationError' }); +const err = ValidationError(); + +// Error with message template +const ValidationError = error({ + name: 'ValidationError', + message: 'Field "{field}" is invalid: {reason}', +}); + +const err = ValidationError({ field: 'email', reason: 'invalid format' }); +// err.message === 'Field "email" is invalid: invalid format' +``` + +### Exception Chaining + +```typescript +import { error } from '@deessejs/errors'; + +const ValidationError = error({ name: 'ValidationError' }); +const ProcessingError = error({ name: 'ProcessingError' }); + +const validationErr = ValidationError({ field: 'email' }); +const processingErr = ProcessingError(); + +// Chain errors with .from() +processingErr.from(validationErr); + +console.log(processingErr.message); // "ProcessingError" +console.log(processingErr.cause); // validationErr +console.log(processingErr.causes); // [validationErr] +``` + +### Hierarchical Inheritance + +```typescript +import { error } from '@deessejs/errors'; + +// Single inheritance +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +// Multiple inheritance +const NetworkError = error({ name: 'NetworkError' }); +const StorageError = error({ name: 'StorageError' }); +const CombinedError = error({ + name: 'CombinedError', + inherits: [NetworkError, StorageError], +}); +``` + +### Type Checking + ```typescript -import { BaseError, ErrorKind, errorOf, errorWithCause } from '@deessejs/errors'; +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError(); + +is(err, ValidationError); // true +is(err, AppError); // true (inherits from AppError) +``` + +### Traverse Cause Chain -// Create typed errors -const myError = errorOf( - 'VALIDATION_ERROR', - 'Invalid input provided', - { field: 'email', value: 'not-an-email' } -); +```typescript +import { error, causes } from '@deessejs/errors'; + +const Err1 = error({ name: 'Err1' }); +const Err2 = error({ name: 'Err2' }); +const Err3 = error({ name: 'Err3' }); + +const err1 = Err1(); +const err2 = Err2().from(err1); +const err3 = Err3().from(err2); + +// Iterate through the cause chain +for (const cause of causes(err3)) { + console.log(cause.name); +} +// Output: Err2, Err1 +``` + +## Why @deessejs/errors? + +Built-in JavaScript errors are limited. `@deessejs/errors` brings Python-style error handling to TypeScript. + +| Feature | Built-in `Error` | @deessejs/errors | +| ------------------------ | ---------------- | ---------------- | +| Exception chaining | ❌ | ✅ | +| Hierarchical inheritance | ❌ | ✅ | +| Message templates | ❌ | ✅ | +| Type-safe fields | ❌ | ✅ | +| Standard Schema support | ❌ | ✅ | + +```typescript +// Traditional approach — limited context +throw new Error('Validation failed'); // ❌ Generic, no structure + +// @deessejs/errors — rich, maintainable errors +const err = ValidationError({ field: 'email', reason: 'invalid format' }); +err.from(originalError); // ✅ Chain exceptions, preserve context +``` + +## FAQ + +### How do I create a custom error type? + +```typescript +import { error } from '@deessejs/errors'; + +const ValidationError = error({ name: 'ValidationError' }); +const err = ValidationError({ field: 'email' }); +``` + +### How do I chain exceptions? + +```typescript +import { error } from '@deessejs/errors'; + +const validationErr = ValidationError({ field: 'email' }); +const processingErr = ProcessingError().from(validationErr); + +console.log(processingErr.cause); // validationErr +``` + +### How do I check if an error is of a specific type? + +```typescript +import { error, is } from '@deessejs/errors'; -// Chain errors with context -const wrapped = errorWithCause( - errorOf('PROCESSING_ERROR', 'Failed to process data'), - myError -); +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError', inherits: AppError }); -// Access the full chain -console.log(wrapped.message); // "Failed to process data" -console.log(wrapped.cause?.message); // "Invalid input provided" +is(err, AppError); // true if err is ValidationError or any descendant ``` ## Documentation @@ -48,4 +181,4 @@ For full documentation, visit [errors.deessejs.com](https://errors.deessejs.com) ## License -MIT \ No newline at end of file +MIT diff --git a/SECURITY.md b/SECURITY.md index d148b6c..c8d28a7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -26,4 +26,4 @@ If you discover a security vulnerability, please report it by: ## Security Updates -Security updates are released as patch versions and announced via GitHub Security Advisories. \ No newline at end of file +Security updates are released as patch versions and announced via GitHub Security Advisories. diff --git a/apps/web/content/blog/getting-started-with-deessejs-errors.mdx b/apps/web/content/blog/getting-started-with-deessejs-errors.mdx new file mode 100644 index 0000000..57b0748 --- /dev/null +++ b/apps/web/content/blog/getting-started-with-deessejs-errors.mdx @@ -0,0 +1,80 @@ +--- +title: "Getting Started with @deessejs/errors" +description: "Learn how to implement Python-inspired error handling in TypeScript with exception chaining, hierarchical inheritance, and rich error semantics." +author: "Nesalia Inc" +date: "2026-06-05" +--- + +## Introduction + +`@deessejs/errors` brings Python's powerful error handling system to TypeScript. If you've ever wished JavaScript had the same expressive error handling as Python, this library is for you. + +## Installation + +```bash +npm install @deessejs/errors +``` + +## Creating Your First Error + +Unlike traditional JavaScript errors that use classes, `@deessejs/errors` uses a function-based API: + +```ts +import { error } from '@deessejs/errors'; + +// Create an error factory +const ValidationError = error({ + name: 'ValidationError', + message: 'Validation failed for field "{field}"', +}); + +// Use it +const err = ValidationError({ field: 'email' }); +console.log(err.message); // "Validation failed for field "email"" +``` + +## Exception Chaining + +The `.from()` method lets you chain errors together, preserving the full context of what went wrong: + +```ts +import { error, raise } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +try { + // Something fails + raise(ValidationError({ field: 'email' })); +} catch (e) { + // Chain the error + AppError({}).from(e); +} +``` + +## Hierarchical Inheritance + +Build error hierarchies that let you catch errors at different levels: + +```ts +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +// Now you can catch any AppError +if (is(err, AppError)) { + // Handles both ValidationError and AppError +} +``` + +## Next Steps + +- Read the [Error Factory documentation](/docs/error-factory) to learn about message templates +- Explore [Exception Chaining](/docs/from-method) for advanced error handling +- Check out [Recipes](/docs/recipes) for common patterns + +Happy error handling! diff --git a/apps/web/content/docs/api-reference.mdx b/apps/web/content/docs/api-reference.mdx new file mode 100644 index 0000000..3622e33 --- /dev/null +++ b/apps/web/content/docs/api-reference.mdx @@ -0,0 +1,191 @@ +--- +title: API Reference +description: Complete API reference for all exports from @deessejs/errors. +--- + +This page documents all public exports from @deessejs/errors. Use this as a comprehensive reference for the library's API. + +## error() + +Creates an error factory function for defining typed, structured errors. + +```ts title="title="${f%.mdx}.ts"" +const errorFactory = error(config) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `config.name` | `string` | Error name identifier (required) | +| `config.message` | `string` | Message template with `{field}` placeholders | +| `config.fields` | `StandardSchemaV1` | Field schema for validation (Zod, Valibot, etc.) | +| `config.inherits` | `ErrorFactory \| ErrorFactory[]` | Parent error(s) to inherit from | + +### Returns + +An `ErrorFactory` function that creates error instances. The factory has these properties: + +| Property | Type | Description | +|----------|------|-------------| +| `name` | `string` | The error name | +| `inherits` | `ErrorFactory \| ErrorFactory[] \| undefined` | Parent error types | +| `schema` | `StandardSchemaV1 \| undefined` | Field validation schema | +| `rawMessage` | `string \| undefined` | Original message template | + +### Example + +```ts title="title="${f%.mdx}.ts"" +import { error } from '@deessejs/errors'; + +const ValidationError = error<{ field: string }>({ + name: 'ValidationError', + message: 'Field "{field}" is invalid', + inherits: AppError, +}); + +const err = ValidationError({ field: 'email' }); +``` + +--- + +## raise() + +Throws an error instance. This is the primary mechanism for throwing errors in @deessejs/errors. + +```ts title="title="${f%.mdx}.ts"" +raise(error: ErrorInstance): never +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `error` | `ErrorInstance` | The error to throw | + +### Returns + +`never` — This function always throws. + +### Example + +```ts title="title="${f%.mdx}.ts"" +import { error, raise } from '@deessejs/errors'; + +const ValidationError = error({ name: 'ValidationError' }); + +raise(ValidationError({})); +``` + +--- + +## is() + +Type guard function to check if an error is an instance of a specific error type. + +```ts title="title="${f%.mdx}.ts"" +const result = is(error, ErrorType) +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `error` | `unknown` | The error to check | +| `ErrorType` | `ErrorFactory \| ErrorClass` | The error type to check against | + +### Returns + +`boolean` — `true` if the error matches or inherits from the type. + +### Example + +```ts title="title="${f%.mdx}.ts"" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError({}); + +is(err, ValidationError); // true +is(err, AppError); // true (through inheritance) +``` + +--- + +## causes() + +Returns all causes in an error chain. + +```ts title="title="${f%.mdx}.ts"" +const chain = causes(error: unknown): Error[] +``` + +### Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `error` | `unknown` | The error to get causes from | + +### Returns + +`Error[]` — Array of errors in the cause chain, ordered newest to oldest. Returns an empty array for null, undefined, or errors without causes. + +### Example + +```ts title="title="${f%.mdx}.ts"" +import { error, causes } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const appErr = AppError({}); +appErr.from(new Error('Original error')); + +const chain = causes(appErr); +console.log(chain.length); // 1 +``` + +--- + +## ErrorInstance + +The type of object returned by error factories. It extends the native `Error` type. + +### Properties + +| Property | Type | Description | +|----------|------|-------------| +| `name` | `string` | Error name identifier | +| `message` | `string` | Human-readable error message | +| `stack` | `string` | Stack trace string | +| `fields` | `T` | User-defined fields | +| `notes` | `string[]` | Additional notes | +| `cause` | `Error \| null` | Direct cause of this error | +| `causes` | `Error[]` | Full cause chain | +| `context` | `Record \| null` | Injected context data | +| `inherits` | `ErrorFactory \| ErrorFactory[] \| undefined` | Parent error factories | + +### Methods + +| Method | Description | +|--------|-------------| +| `from(cause: Error)` | Chains a cause error to this error | + +--- + +## See Also + + + + Create custom error types. + + + Use is() for type guards. + + + Chain errors with from(). + + \ No newline at end of file diff --git a/apps/web/content/docs/causes-function.mdx b/apps/web/content/docs/causes-function.mdx new file mode 100644 index 0000000..19f479c --- /dev/null +++ b/apps/web/content/docs/causes-function.mdx @@ -0,0 +1,135 @@ +--- +title: The causes() Function +description: Use the causes() function to traverse error chains and access all causes in an error. +--- + +The `causes()` function provides a safe way to access the cause chain of any error. It works with both @deessejs/errors instances and native JavaScript errors, returning an empty array for errors without causes. + +## Basic Usage + +Call `causes()` with an error to get its cause chain as an array: + +```ts title="basic.ts" +import { error, causes } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('Network timeout')); + +const chain = causes(appErr); + +console.log(chain.length); // 2 +console.log(chain[0].name); // "ValidationError" +console.log(chain[1].message); // "Network timeout" +``` + +The returned array contains only the errors that were explicitly added using `from()`, ordered from newest to oldest. + +## Why Use causes() Instead of Direct Access? + +The `causes()` function handles edge cases that direct property access doesn't: + +```ts title="safety.ts" +import { causes } from '@deessejs/errors'; + +// Works with native errors (no causes property) +const nativeError = new Error('Something failed'); +const chain1 = causes(nativeError); +console.log(chain1.length); // 0 + +// Works with null/undefined (returns empty array) +const chain2 = causes(null); +console.log(chain2.length); // 0 + +const chain3 = causes(undefined); +console.log(chain3.length); // 0 +``` + +This robustness makes `causes()` ideal for writing generic error handling code that doesn't need to check the error type first. + +## Iterating Over the Chain + +You can iterate over the causes array to log or process each error in the chain: + +```ts title="iterate.ts" +import { error, causes } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('Database connection failed')); + +// Log each cause +for (const cause of causes(appErr)) { + console.log(`[${cause.name}] ${cause.message}`); +} + +// Output: +// [ValidationError] ValidationError +// [Error] Database connection failed +``` + +This is useful for building detailed error logs or monitoring dashboards that track where errors originate. + +## Building Error Summaries + +A common pattern is to build a summary message from the error chain: + +```ts title="summary.ts" +import { error, causes } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('DB connection failed')); + +const summary = causes(appErr) + .map((cause) => `${cause.name}: ${cause.message}`) + .join(' → '); + +console.log(summary); +// "ValidationError: ValidationError → Error: DB connection failed" +``` + +## Combining with is() + +Use `causes()` with `is()` to search for specific error types in the chain: + +```ts title="combined.ts" +import { error, causes, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const NetworkError = error({ name: 'NetworkError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(NetworkError({ endpoint: '/api/users' })); +appErr.from(ValidationError({ field: 'email' })); + +// Check if any cause is a NetworkError +const hasNetworkFailure = causes(appErr).some( + (cause) => is(cause, NetworkError) +); + +console.log(hasNetworkFailure); // true +``` + +This pattern is useful when you need to know not just that an error happened, but what kind of errors were involved in its chain. + +## See Also + + + + Learn how to build error chains with from(). + + + Use is() to check error types. + + \ No newline at end of file diff --git a/apps/web/content/docs/error-factory.mdx b/apps/web/content/docs/error-factory.mdx new file mode 100644 index 0000000..b05aa1a --- /dev/null +++ b/apps/web/content/docs/error-factory.mdx @@ -0,0 +1,138 @@ +--- +title: Error Factory +description: Create custom error types with the error() function in @deessejs/errors. +--- + +The `error()` function is the primary way to define error types in @deessejs/errors. It returns a factory function that creates error instances with your specified configuration. + +## Basic Usage + +At minimum, an error factory requires a name. This name identifies the error type and appears in the `name` property of instances. + +```ts title="basic.ts" +import { error } from '@deessejs/errors'; + +const NotFoundError = error({ + name: 'NotFoundError', +}); + +const err = NotFoundError({}); +console.log(err.name); // "NotFoundError" +console.log(err.message); // "NotFoundError" +``` + +When you provide only a name, the message defaults to the same value. This is useful for simple errors where the name itself is descriptive enough. + +## Custom Messages + +You can provide a custom message that gives more context about what went wrong: + +```ts title="custom-message.ts" +const ValidationError = error({ + name: 'ValidationError', + message: 'Validation failed', +}); + +const err = ValidationError({}); +console.log(err.message); // "Validation failed" +``` + +## Creating Instances with Fields + +The real power of error factories comes from attaching structured data. You can define the type of fields your error accepts using a generic parameter: + +```ts title="with-fields.ts" +const UserError = error<{ userId: string; reason: string }>({ + name: 'UserError', +}); + +const err = UserError({ userId: 'usr_123', reason: 'not found' }); +console.log(err.fields.userId); // "usr_123" +console.log(err.fields.reason); // "not found" +``` + +The fields object is completely flexible. You can include any data that helps describe the error: IDs, timestamps, values that caused the error, or any other context. + +## Using Fields with Schema Validation + +For more robust error definitions, you can provide a Standard Schema (compatible with Zod, Valibot, or ArkType) to validate fields at creation time: + +```ts title="with-schema.ts" +import { z } from 'zod'; +import { error } from '@deessejs/errors'; + +const ValidationError = error({ + name: 'ValidationError', + fields: z.object({ + field: z.string(), + reason: z.string(), + }), +}); + +const err = ValidationError({ field: 'email', reason: 'invalid format' }); +``` + +When fields don't match the schema, an error is thrown during error creation. This helps catch configuration mistakes early. + +## Factory Properties + +The function returned by `error()` has several useful properties attached to it: + +```ts title="properties.ts" +const AppError = error({ name: 'AppError' }); + +console.log(AppError.name); // "AppError" +console.log(AppError.schema); // undefined (no schema defined) +console.log(AppError.inherits); // undefined (no parent) +``` + +These properties are useful for introspection and for the `is()` function to check inheritance relationships. + +## Reusing Error Factories + +Error factories are designed to be created once and reused throughout your application. Define them at module level so they're available everywhere: + +```ts title="definitions.ts" +// errors/index.ts +import { error } from '@deessejs/errors'; + +export const NotFoundError = error({ + name: 'NotFoundError', + message: 'Resource not found', +}); + +export const ValidationError = error({ + name: 'ValidationError', + message: 'Validation failed', +}); + +export const NetworkError = error({ + name: 'NetworkError', + message: 'Network request failed', +}); +``` + +Then import and use them wherever needed: + +```ts title="usage.ts" +import { raise } from '@deessejs/errors'; +import { NotFoundError } from './errors'; + +if (!resource) { + raise(NotFoundError({})); +} +``` + +## See Also + + + + Understand the properties and methods available on error instances. + + + Use the is() function to check error types. + + + Learn more about field definitions and schema validation. + + \ No newline at end of file diff --git a/apps/web/content/docs/error-instance.mdx b/apps/web/content/docs/error-instance.mdx new file mode 100644 index 0000000..a201866 --- /dev/null +++ b/apps/web/content/docs/error-instance.mdx @@ -0,0 +1,150 @@ +--- +title: Error Instance +description: Understand the properties and methods available on error instances created by @deessejs/errors. +--- + +When you create an error using an error factory, you get an error instance. This instance extends the native JavaScript `Error` object with additional properties and methods specific to @deessejs/errors. + +## Standard Properties + +Error instances include all standard `Error` properties: + +```ts title="properties.ts" +import { error } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError', message: 'Something went wrong' }); +const err = AppError({}); + +console.log(err.name); // "AppError" +console.log(err.message); // "Something went wrong" +console.log(err.stack); // Stack trace string +console.log(err instanceof Error); // true +``` + +These properties behave exactly like native errors, so they work with all existing error handling code and tools. + +## The fields Property + +The `fields` property contains the data you passed when creating the error. This is where you store structured context about what went wrong. + +```ts title="fields.ts" +const ValidationError = error<{ field: string; value: unknown }>({ + name: 'ValidationError', + message: 'Invalid value', +}); + +const err = ValidationError({ field: 'email', value: 'not-an-email' }); + +console.log(err.fields.field); // "email" +console.log(err.fields.value); // "not-an-email" +``` + +The fields object is typed based on what you defined in the error factory, providing full TypeScript support for accessing these values. + +## The from() Method + +The `from()` method chains another error as the cause of the current error. This is how you preserve the full context of what led to an error. + +```ts title="from-method.ts" +import { error } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const validationErr = ValidationError({ field: 'email' }); +const appErr = AppError({}); + +// Chain the validation error as a cause +appErr.from(validationErr); + +console.log(appErr.cause === validationErr); // true +console.log(appErr.causes.length); // 1 +``` + +The `from()` method returns the error instance, allowing you to chain multiple calls or combine it with other operations. + +## The cause Property + +The `cause` property holds the direct cause of the error — the error passed to the most recent `from()` call. + +```ts title="cause.ts" +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'name' })); + +console.log(appErr.cause instanceof ValidationError); // true +``` + +When no cause has been set, `cause` is `null`. + +## The causes Array + +The `causes` array contains the entire chain of errors, ordered from most recent to oldest. This gives you the full history of what happened. + +```ts title="causes-array.ts" +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('Database connection failed')); + +console.log(appErr.causes.length); // 2 +console.log(appErr.causes[0].name); // "ValidationError" (most recent) +console.log(appErr.causes[1].message); // "Database connection failed" (oldest) +``` + +When you chain multiple errors, the new cause is added to the front of the array, keeping chronological order. + +## The notes Property + +The `notes` property allows you to attach additional context to an error after it's created. This is useful for adding debugging information in catch blocks. + +```ts title="notes.ts" +const AppError = error({ name: 'AppError' }); +const err = AppError({}); + +// Add context notes +err.notes.push('Caught in user controller'); +err.notes.push('User ID was usr_123'); + +console.log(err.notes); +// ["Caught in user controller", "User ID was usr_123"] +``` + +Notes are stored as an array of strings and can be modified at any time. + +## The context Property + +The `context` property holds additional data that was injected into the error. This is useful for framework integrations or middleware that need to attach extra information. + +```ts title="context.ts" +const AppError = error({ name: 'AppError' }); +const err = AppError({}); + +// Attach context data +err.context = { + requestId: 'req_abc123', + timestamp: new Date().toISOString(), +}; + +console.log(err.context.requestId); // "req_abc123" +``` + +When no context has been set, `context` is `null`. + +## See Also + + + + Create error types with the error() function. + + + Learn more about chaining errors with from(). + + + Check error types using the is() function. + + \ No newline at end of file diff --git a/apps/web/content/docs/fields-schema.mdx b/apps/web/content/docs/fields-schema.mdx new file mode 100644 index 0000000..d29fc0b --- /dev/null +++ b/apps/web/content/docs/fields-schema.mdx @@ -0,0 +1,177 @@ +--- +title: Fields and Schema +description: Define structured fields and validation schemas for error types in @deessejs/errors. +--- + +Errors become much more useful when they carry structured data about what went wrong. @deessejs/errors lets you define fields on your error factories and optionally validate them using Standard Schema compatible libraries. + +## Defining Fields + +When you create an error factory, you can specify a generic type that defines the shape of the fields object: + +```ts title="basic.ts" +import { error } from '@deessejs/errors'; + +const ValidationError = error<{ + field: string; + reason: string; + value?: unknown; +}>({ + name: 'ValidationError', + message: 'Validation failed', +}); +``` + +Now when you create an error instance, TypeScript ensures you provide the required fields: + +```ts title="usage.ts" +// All required fields provided +const err = ValidationError({ + field: 'email', + reason: 'invalid format', +}); + +// Optional field omitted (valid) +const err2 = ValidationError({ + field: 'email', + reason: 'invalid format', + value: 'not-an-email', +}); +``` + +The fields object is always accessible on the error instance, giving you a consistent place to look for error context. + +## Accessing Field Values + +Once an error is caught, you can access its fields to provide meaningful feedback or logging: + +```ts title="access.ts" +import { error } from '@deessejs/errors'; +import { raise } from '@deessejs/errors'; + +const ValidationError = error<{ field: string; reason: string }>({ + name: 'ValidationError', + message: 'Validation failed', +}); + +try { + raise(ValidationError({ field: 'email', reason: 'not a valid email address' })); +} catch (err) { + // TypeScript knows the shape of fields + const fields = (err as { fields: { field: string; reason: string } }).fields; + console.log(`Error in field "${fields.field}": ${fields.reason}`); + // Output: Error in field "email": not a valid email address +} +``` + +For full type inference in catch blocks, use the `is()` function with type narrowing. + +## Schema Validation + +For production applications, you may want to validate field values when errors are created. @deessejs/errors supports Standard Schema, which means you can use any compatible validation library like Zod, Valibot, or ArkType. + +### Using Zod + +```ts title="zod.ts" +import { z } from 'zod'; +import { error } from '@deessejs/errors'; + +const ValidationError = error({ + name: 'ValidationError', + fields: z.object({ + field: z.string().min(1), + reason: z.string().min(1), + }), +}); + +// Valid error creation +const err = ValidationError({ field: 'email', reason: 'invalid' }); + +// Invalid creation - throws ZodError +try { + ValidationError({ field: '', reason: 'invalid' }); +} catch (e) { + console.log(e instanceof Error); // true +} +``` + +### Using Valibot + +```ts title="valibot.ts" +import { valibot } from 'fumadocs-core/source'; +import { error } from '@deessejs/errors'; + +const ValidationError = error({ + name: 'ValidationError', + fields: valibot({ + field: 'string', + reason: 'string', + }), +}); +``` + +### Using ArkType + +```ts title="arktype.ts" +import { error } from '@deessejs/errors'; +import { t } from 'arktype'; + +const ValidationError = error({ + name: 'ValidationError', + fields: t.type({ + field: 'string', + reason: 'string', + }), +}); +``` + +## Why Use Schema Validation? + +Schema validation in error factories provides several benefits: + +**Early error detection** — Configuration mistakes in your error definitions are caught immediately rather than causing subtle bugs later. + +**Self-documenting code** — The schema serves as documentation for what data each error type expects. + +**Consistent data** — All errors of a given type have the same structure, making logging and monitoring easier. + +## Common Field Patterns + +Here are some common patterns for error fields: + +```ts title="patterns.ts" +// Database errors +const DatabaseError = error<{ + query: string; + table?: string; + code?: string; +}>({ name: 'DatabaseError' }); + +// API errors +const ApiError = error<{ + endpoint: string; + statusCode: number; + response?: unknown; +}>({ name: 'ApiError' }); + +// Validation errors +const ValidationError = error<{ + field: string; + reason: string; + value?: unknown; + constraints?: Record; +}>({ name: 'ValidationError' }); +``` + +Design your fields to capture what's useful for debugging and logging, not just what's required to identify the error. + +## See Also + + + + Create error types with the error() function. + + + See practical examples of error definitions for common scenarios. + + \ No newline at end of file diff --git a/apps/web/content/docs/from-method.mdx b/apps/web/content/docs/from-method.mdx new file mode 100644 index 0000000..e2128ac --- /dev/null +++ b/apps/web/content/docs/from-method.mdx @@ -0,0 +1,136 @@ +--- +title: Exception Chaining +description: Chain errors together using the from() method to preserve the full context of what went wrong. +--- + +When an error occurs as a result of another error, it's important to preserve that relationship. @deessejs/errors provides the `from()` method for exactly this purpose, creating chains that maintain the full history of failures. + +## Basic Chaining + +The `from()` method attaches a cause error to the current error. Call it on an error instance to establish the relationship: + +```ts title="basic.ts" +import { error } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +// Create the original error +const validationErr = ValidationError({ field: 'email' }); + +// Create the wrapping error +const appErr = AppError({}); + +// Chain the validation error as the cause +appErr.from(validationErr); + +console.log(appErr.cause === validationErr); // true +``` + +The `from()` method returns the error instance, so you can chain method calls or use it inline. + +## Multiple Causes + +A single error can have multiple causes in its chain. Each call to `from()` adds a new cause to the front of the chain: + +```ts title="multiple.ts" +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); + +// Add causes in order +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('Database connection failed')); + +// Most recent cause is first +console.log(appErr.causes.length); // 2 +console.log(appErr.causes[0].name); // "ValidationError" +console.log(appErr.causes[1].message); // "Database connection failed" +``` + +The order is always newest first, which makes sense when reading error chains — the most recent cause is what led directly to the current error. + +## Chaining with raise() + +You can combine `from()` with `raise()` for concise error handling: + +```ts title="with-raise.ts" +import { error, raise } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +try { + // Something throws a validation error + throw ValidationError({ field: 'email' }); +} catch (err) { + // Wrap it in an application error + raise(AppError({}).from(err)); +} +``` + +Since `from()` returns the error instance, you can write this more compactly: + +```ts title="compact.ts" +import { error, raise } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +try { + throw ValidationError({ field: 'email' }); +} catch (err) { + raise(AppError({}).from(err)); +} +``` + +## Accessing the Full Chain + +After building an error chain, you can access all causes through the `causes` array or use the `causes()` helper function for more robust access: + +```ts title="access.ts" +import { error, causes } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ name: 'ValidationError' }); + +const appErr = AppError({}); +appErr.from(ValidationError({ field: 'email' })); +appErr.from(new Error('Connection timeout')); + +// Using the causes array directly +console.log(appErr.causes.length); // 2 + +// Using the causes() helper (handles non-@deessejs/errors errors) +const chain = causes(appErr); +console.log(chain.length); // 2 +``` + +The `causes()` function is particularly useful when working with errors from different sources, as it gracefully handles cases where the `causes` array might not exist. + +## Why Chain Errors? + +Exception chaining serves several important purposes: + +**Debugging** — When something goes wrong in production, the full chain tells the complete story of what happened, not just the final error. + +**Logging** — You can log or monitor the entire chain to understand patterns in failures across your application. + +**User experience** — Show users a meaningful error message while keeping technical details for diagnostics. + +**Service boundaries** — When errors cross async boundaries or service calls, chaining preserves context that would otherwise be lost. + +## See Also + + + + Learn about all properties available on error instances. + + + Traverse error chains programmatically. + + + Check error types in catch blocks. + + \ No newline at end of file diff --git a/apps/web/content/docs/index.mdx b/apps/web/content/docs/index.mdx index 1ede18e..33ff3f4 100644 --- a/apps/web/content/docs/index.mdx +++ b/apps/web/content/docs/index.mdx @@ -1,13 +1,106 @@ --- -title: Hello World -description: Your first document +title: Getting Started +description: Get started with @deessejs/errors, a TypeScript error handling library with exception chaining and hierarchical inheritance. --- -Welcome to the docs! You can start writing documents in `/content/docs`. +@deessejs/errors is a TypeScript library that reimagines error handling in JavaScript. Inspired by Python's exception system, it provides exception chaining, hierarchical inheritance, and rich error semantics through a function-based API. -## What is Next? +This library is particularly useful when you need to: + +- Preserve the full context of errors across async boundaries and service calls +- Organize errors in meaningful hierarchies that reflect your domain +- Attach structured data and metadata to errors for better debugging + +## Quick Start + +The quickest way to understand how @deessejs/errors works is to see it in action. Here's a basic example that demonstrates creating typed errors and chaining them together. + +```ts title="index.ts" +import { error, raise } from '@deessejs/errors'; + +// Define your error types +const ValidationError = error({ + name: 'ValidationError', + message: 'Validation failed', +}); + +// Use the factory to create error instances +const validationErr = ValidationError({}); +console.log(validationErr.message); // "Validation failed" +``` + +For catching and handling errors, you can use the standard try/catch syntax or the library's `raise()` function: + +```ts title="handling.ts" +try { + raise(ValidationError({})); +} catch (err) { + console.log(err.name); // "ValidationError" + console.log(err instanceof Error); // true +} +``` + +## Key Features + +### Exception Chaining + +When an error occurs as a result of another error, you can preserve that relationship using the `.from()` method. This creates a chain that maintains the full history of what went wrong. + +```ts title="chaining.ts" +const validationErr = ValidationError({}); +const appErr = error({ name: 'AppError' })(); + +// Chain the validation error as the cause +appErr.from(validationErr); + +console.log(appErr.cause === validationErr); // true +console.log(appErr.causes.length); // 1 +``` + +### Hierarchical Inheritance + +Errors can inherit from other errors, creating hierarchies that are useful for organization and type checking. A child error can check against its parent types. + +```ts title="inheritance.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError({}); + +// Check the error type +console.log(is(err, ValidationError)); // true +console.log(is(err, AppError)); // true (through inheritance) +``` + +### Message Templates + +You can define errors with message templates that include field placeholders. These are replaced at runtime with the data you provide. + +```ts title="templates.ts" +const UserError = error<{ userId: string; reason: string }>({ + name: 'UserError', + message: 'User "{userId}" failed: {reason}', +}); + +const err = UserError({ userId: 'usr_123', reason: 'not found' }); +console.log(err.message); // "User "usr_123" failed: not found" +``` + +## See Also - - - + + Learn how to install and set up the library in your project. + + + Create custom error types with the error() function. + + + Chain errors together to preserve context. + + \ No newline at end of file diff --git a/apps/web/content/docs/installation.mdx b/apps/web/content/docs/installation.mdx new file mode 100644 index 0000000..ecad277 --- /dev/null +++ b/apps/web/content/docs/installation.mdx @@ -0,0 +1,87 @@ +--- +title: Installation +description: Install @deessejs/errors in your TypeScript or JavaScript project. +--- + +Adding @deessejs/errors to your project is straightforward. The library is available on npm and supports all major package managers. + +## Install the Package + +Choose your preferred package manager and run the installation command: + +```bash +npm install @deessejs/errors +``` + +```bash +pnpm add @deessejs/errors +``` + +```bash +yarn add @deessejs/errors +``` + +```bash +bun add @deessejs/errors +``` + +## Requirements + +@deessejs/errors requires Node.js 18 or later. The library is written in TypeScript and ships with full type definitions, so no additional `@types` packages are needed. + +## Import the Library + +Once installed, you can import the core functions from the package: + +```ts title="main.ts" +import { error, raise, is, causes } from '@deessejs/errors'; +``` + +You can also import specific functions if you prefer tree-shaking: + +```ts title="partial.ts" +import { error } from '@deessejs/errors'; +import { is } from '@deessejs/errors'; +``` + +## TypeScript Configuration + +The library works out of the box with TypeScript. No special compiler options are required, but the following settings in your `tsconfig.json` will ensure the best experience: + +```json title="tsconfig.json" +{ + "compilerOptions": { + "strict": true, + "moduleResolution": "bundler" + } +} +``` + +The `moduleResolution: "bundler"` option is recommended if you're using a modern bundler like Vite, webpack 5, or Turbopack. + +## Verify Installation + +To verify that the library is installed correctly, create a simple test file: + +```ts title="verify.ts" +import { error } from '@deessejs/errors'; + +const TestError = error({ name: 'TestError' }); +const err = TestError({}); + +console.log(err.name); // "TestError" +console.log(err instanceof Error); // true +``` + +Run this file with Node.js or your bundler to confirm everything works. + +## See Also + + + + Return to the introduction and quick start guide. + + + Learn how to create custom error types. + + \ No newline at end of file diff --git a/apps/web/content/docs/message-templates.mdx b/apps/web/content/docs/message-templates.mdx new file mode 100644 index 0000000..d889002 --- /dev/null +++ b/apps/web/content/docs/message-templates.mdx @@ -0,0 +1,162 @@ +--- +title: Message Templates +description: Use message templates with field placeholders and modifiers in @deessejs/errors. +--- + +Message templates let you create dynamic error messages that include runtime values. This makes errors more informative and easier to debug, as the message contains specific context about what went wrong. + +## Basic Placeholders + +Wrap field names in curly braces to create placeholders in your message: + +```ts title="basic.ts" +import { error } from '@deessejs/errors'; + +const ValidationError = error<{ field: string }>({ + name: 'ValidationError', + message: 'Field "{field}" is invalid', +}); + +const err = ValidationError({ field: 'email' }); +console.log(err.message); // "Field "email" is invalid" +``` + +The placeholder is replaced with the actual value from the fields object at runtime. + +## Multiple Placeholders + +You can include multiple placeholders in a single message: + +```ts title="multiple.ts" +import { error } from '@deessejs/errors'; + +const FormatError = error<{ expected: string; actual: string }>({ + name: 'FormatError', + message: 'Expected {expected}, got {actual}', +}); + +const err = FormatError({ expected: 'number', actual: 'string' }); +console.log(err.message); // "Expected number, got string" +``` + +Each placeholder is replaced with its corresponding field value. + +## Modifiers + +Modifiers transform the placeholder value before insertion. They follow the field name with a colon. + +### Uppercase Modifier + +The `:upper` modifier converts the value to uppercase: + +```ts title="upper.ts" +import { error } from '@deessejs/errors'; + +const UserCreatedError = error<{ userId: string }>({ + name: 'UserCreatedError', + message: 'Created user: {userId:upper}', +}); + +const err = UserCreatedError({ userId: 'usr_abc123' }); +console.log(err.message); // "Created user: USR_ABC123" +``` + +This is useful for displaying IDs or codes that should be consistently formatted. + +### Lowercase Modifier + +The `:lower` modifier converts the value to lowercase: + +```ts title="lower.ts" +import { error } from '@deessejs/errors'; + +const PathError = error<{ path: string }>({ + name: 'PathError', + message: 'Invalid path: {path:lower}', +}); + +const err = PathError({ path: '/USERS/DATA' }); +console.log(err.message); // "Invalid path: /users/data" +``` + +### JSON Modifier + +The `:json` modifier serializes complex values to JSON: + +```ts title="json.ts" +import { error } from '@deessejs/errors'; + +const DataError = error<{ data: { id: number; name: string } }>({ + name: 'DataError', + message: 'Invalid data: {data:json}', +}); + +const err = DataError({ data: { id: 1, name: 'test' } }); +console.log(err.message); // "Invalid data: {"id":1,"name":"test"}" +``` + +The JSON modifier is particularly useful for debugging when you need to see the full value. + +## Combining Modifiers + +Modifiers can be combined with other fields: + +```ts title="combined.ts" +import { error } from '@deessejs/errors'; + +const ApiError = error<{ + method: string; + endpoint: string; + statusCode: number; +}>({ + name: 'ApiError', + message: '{method:upper} {endpoint} failed with status {statusCode}', +}); + +const err = ApiError({ + method: 'get', + endpoint: '/api/users', + statusCode: 404, +}); +console.log(err.message); // "GET /api/users failed with status 404" +``` + +## Missing Field Values + +If a placeholder references a field that wasn't provided, the placeholder is left unchanged in the message: + +```ts title="missing.ts" +import { error } from '@deessejs/errors'; + +const ValidationError = error<{ field: string }>({ + name: 'ValidationError', + message: 'Field "{field}" is required', +}); + +const err = ValidationError({}); // field not provided +console.log(err.message); // "Field "{field}" is required" +``` + +This behavior is intentional — it helps you identify when expected data is missing. + +## Best Practices + +**Be specific** — Include enough context in the message to understand the error without looking at the code. + +**Keep messages readable** — While placeholders add information, don't overload the message with too many values. + +**Use modifiers appropriately** — Uppercase for codes and IDs, lowercase for paths, JSON for complex objects. + +## See Also + + + + Create error types with the error() function. + + + Define structured fields for errors. + + + Practical examples of error definitions. + + \ No newline at end of file diff --git a/apps/web/content/docs/meta.json b/apps/web/content/docs/meta.json new file mode 100644 index 0000000..8e1bd87 --- /dev/null +++ b/apps/web/content/docs/meta.json @@ -0,0 +1,23 @@ +{ + "title": "@deessejs/errors", + "pages": [ + "index", + "installation", + "---Core Concepts---", + "error-factory", + "error-instance", + "fields-schema", + "---Chaining---", + "from-method", + "causes-function", + "---Inheritance---", + "single-inheritance", + "multiple-inheritance", + "type-checking", + "---Advanced---", + "message-templates", + "---Reference---", + "api-reference", + "recipes" + ] +} diff --git a/apps/web/content/docs/multiple-inheritance.mdx b/apps/web/content/docs/multiple-inheritance.mdx new file mode 100644 index 0000000..54ae026 --- /dev/null +++ b/apps/web/content/docs/multiple-inheritance.mdx @@ -0,0 +1,141 @@ +--- +title: Multiple Inheritance +description: Create errors that inherit from multiple parent error types in @deessejs/errors. +--- + +While single inheritance creates clear parent-child relationships, some errors naturally belong to multiple categories. @deessejs/errors supports multiple inheritance, allowing an error to inherit from several parent types simultaneously. + +## Basic Multiple Inheritance + +To inherit from multiple parents, pass an array of error factories to the `inherits` property: + +```ts title="basic.ts" +import { error, is } from '@deessejs/errors'; + +const NetworkError = error({ name: 'NetworkError' }); +const StorageError = error({ name: 'StorageError' }); + +// Inherits from both NetworkError and StorageError +const NetworkStorageError = error({ + name: 'NetworkStorageError', + inherits: [NetworkError, StorageError], +}); +``` + +Now this error belongs to both categories in the hierarchy. + +## Type Checking with Multiple Parents + +When an error inherits from multiple parents, `is()` returns true for any of its parents: + +```ts title="checking.ts" +const NetworkError = error({ name: 'NetworkError' }); +const StorageError = error({ name: 'StorageError' }); + +const NetworkStorageError = error({ + name: 'NetworkStorageError', + inherits: [NetworkError, StorageError], +}); + +const err = NetworkStorageError({}); + +console.log(is(err, NetworkStorageError)); // true +console.log(is(err, NetworkError)); // true +console.log(is(err, StorageError)); // true +``` + +This flexibility lets you categorize errors from multiple angles. + +## Practical Example + +Consider a caching layer that sits between the network and storage. It might fail in ways that are both network-related and storage-related: + +```ts title="example.ts" +import { error, raise, is } from '@deessejs/errors'; + +const NetworkError = error({ name: 'NetworkError' }); +const StorageError = error({ name: 'StorageError' }); + +const CacheError = error({ + name: 'CacheError', + message: 'Cache operation failed', + inherits: [NetworkError, StorageError], +}); + +// Handler that deals with network issues +function handleNetworkIssue(err: unknown) { + if (is(err, NetworkError)) { + console.log('Network problem detected:', err.message); + } +} + +// Handler that deals with storage issues +function handleStorageIssue(err: unknown) { + if (is(err, StorageError)) { + console.log('Storage problem detected:', err.message); + } +} + +const cacheErr = CacheError({}); + +handleNetworkIssue(cacheErr); // Logs (through NetworkError inheritance) +handleStorageIssue(cacheErr); // Logs (through StorageError inheritance) +``` + +This pattern is useful for middleware and infrastructure code that needs to respond to errors based on their characteristics rather than their specific type. + +## Combining with Specific Error Data + +Multiple inheritance works seamlessly with typed fields: + +```ts title="with-fields.ts" +import { error, is } from '@deessejs/errors'; + +const NetworkError = error<{ endpoint: string }>({ + name: 'NetworkError', + message: 'Network request to {endpoint} failed', +}); + +const StorageError = error<{ path: string }>({ + name: 'StorageError', + message: 'Storage operation at {path} failed', +}); + +const CacheError = error<{ key: string; operation: string }>({ + name: 'CacheError', + inherits: [NetworkError, StorageError], + message: 'Cache {operation} for key {key} failed', +}); + +// The error carries its own specific data +const err = CacheError({ key: 'user:123', operation: 'read' }); +console.log(err.fields.key); // "user:123" +console.log(err.message); // "Cache read for key user:123 failed" + +// But also inherits type checking for parents +console.log(is(err, NetworkError)); // true +console.log(is(err, StorageError)); // true +``` + +## When to Use Multiple Inheritance + +Multiple inheritance is appropriate when: + +**The error has multiple dimensions** — A timeout could be a network issue and a service issue simultaneously. + +**You're building middleware** — Libraries between the application and infrastructure often need to respond to multiple error categories. + +**You want flexible handling** — Code can catch errors by any of their characteristics, not just their exact type. + +Avoid using multiple inheritance just because you can. Prefer single inheritance for most errors, and only use multiple inheritance when the error genuinely belongs to multiple categories. + +## See Also + + + + Learn about basic parent-child error hierarchies. + + + Check error types including inherited ones. + + \ No newline at end of file diff --git a/apps/web/content/docs/recipes.mdx b/apps/web/content/docs/recipes.mdx new file mode 100644 index 0000000..3535500 --- /dev/null +++ b/apps/web/content/docs/recipes.mdx @@ -0,0 +1,248 @@ +--- +title: Recipes +description: Practical examples and patterns for using @deessejs/errors in real applications. +--- + +This page collects common patterns and practical examples for using @deessejs/errors in your applications. These recipes demonstrate how to apply the library's features to solve real-world error handling problems. + +## Validation Errors + +Validation errors are one of the most common error types. They typically include the field that failed validation and a reason for the failure. + +```ts title="validation.ts" +import { error, raise } from '@deessejs/errors'; +import { z } from 'zod'; + +// Define validation schema +const UserSchema = z.object({ + email: z.string().email(), + age: z.number().min(0).max(150), +}); + +// Create the error factory +const ValidationError = error<{ field: string; reason: string; value?: unknown }>({ + name: 'ValidationError', + message: 'Validation failed for field "{field}"', +}); + +// Validation function +function validateUser(data: unknown) { + const result = UserSchema.safeParse(data); + + if (!result.success) { + const issue = result.error.issues[0]; + raise( + ValidationError({ + field: issue.path.join('.'), + reason: issue.message, + value: (result.error as { value?: unknown }).value, + }) + ); + } + + return result.data; +} +``` + +When validation fails, you get an error with specific information about which field failed and why. + +## Network Errors + +Network errors benefit from including the endpoint and any relevant request data. + +```ts title="network.ts" +import { error, raise } from '@deessejs/errors'; + +const NetworkError = error<{ + endpoint: string; + method?: string; + statusCode?: number; +}>({ + name: 'NetworkError', + message: 'Request to {endpoint} failed', +}); + +async function fetchWithError(url: string) { + const response = await fetch(url); + + if (!response.ok) { + raise( + NetworkError({ + endpoint: url, + statusCode: response.status, + }) + ); + } + + return response.json(); +} +``` + +You can extend this pattern to include request headers, body, or timing information. + +## Database Errors + +Database errors typically need the query that failed and any relevant identifiers. + +```ts title="database.ts" +import { error, raise } from '@deessejs/errors'; + +const DatabaseError = error<{ + operation: string; + table?: string; + query?: string; +}>({ + name: 'DatabaseError', + message: 'Database {operation} failed', +}); + +// Wrap database operations +async function executeQuery(query: string, table: string) { + try { + return await db.query(query); + } catch (err) { + raise( + DatabaseError({ + operation: 'query', + table, + query, + }).from(err as Error) + ); + } +} +``` + +Chaining the original error preserves the full context of what went wrong. + +## Error Handling Middleware + +Create a reusable error handler that processes errors based on their type. + +```ts title="handler.ts" +import { error, raise, is, causes } from '@deessejs/errors'; +import { ValidationError, NetworkError, DatabaseError } from './errors'; + +export function handleApiError(err: unknown) { + // Log the full error chain + console.error('Error chain:', causes(err).map((e) => e.message)); + + if (is(err, ValidationError)) { + return { + status: 400, + message: `Invalid input: ${err.fields.field}`, + }; + } + + if (is(err, NetworkError)) { + return { + status: 503, + message: 'External service unavailable', + }; + } + + if (is(err, DatabaseError)) { + return { + status: 500, + message: 'Database operation failed', + }; + } + + // Generic fallback + return { + status: 500, + message: 'Internal server error', + }; +} +``` + +This pattern centralizes error handling logic and ensures consistent responses. + +## Service Layer Error Wrapping + +When building services that call other services, wrap errors to add context while preserving the cause chain. + +```ts title="service.ts" +import { error, raise } from '@deessejs/errors'; +import { NetworkError, UserNotFoundError } from './errors'; + +const AppError = error({ name: 'AppError' }); + +class UserService { + async getUser(userId: string) { + try { + const user = await this.fetchUser(userId); + if (!user) { + raise(UserNotFoundError({ userId })); + } + return user; + } catch (err) { + // Wrap any error from lower layers + raise(AppError({}).from(err as Error)); + } + } + + private async fetchUser(userId: string) { + // Implementation + return null; + } +} +``` + +The caller gets both the context of the current operation and the underlying cause. + +## Hierarchical Error Categories + +Create a hierarchy that lets you handle errors at different levels of granularity. + +```ts title="hierarchy.ts" +import { error, is } from '@deessejs/errors'; + +// Top level +const AppError = error({ name: 'AppError' }); + +// Domain level +const UserError = error({ name: 'UserError', inherits: AppError }); +const ProductError = error({ name: 'ProductError', inherits: AppError }); + +// Specific errors +const UserNotFoundError = error<{ userId: string }>({ + name: 'UserNotFoundError', + inherits: UserError, +}); + +const UserPermissionError = error<{ userId: string; action: string }>({ + name: 'UserPermissionError', + inherits: UserError, +}); + +function handleError(err: unknown) { + if (is(err, AppError)) { + // Catches everything + } + if (is(err, UserError)) { + // Catches UserNotFoundError, UserPermissionError + } + if (is(err, UserNotFoundError)) { + // Catches only user not found errors + } +} +``` + +This lets you write both broad handlers and specific ones depending on the situation. + +## See Also + + + + Create custom error types. + + + Build error hierarchies. + + + Handle errors by type. + + + Preserve error context. + + \ No newline at end of file diff --git a/apps/web/content/docs/single-inheritance.mdx b/apps/web/content/docs/single-inheritance.mdx new file mode 100644 index 0000000..2f765ad --- /dev/null +++ b/apps/web/content/docs/single-inheritance.mdx @@ -0,0 +1,130 @@ +--- +title: Single Inheritance +description: Create error hierarchies with single inheritance in @deessejs/errors. +--- + +Error hierarchies help organize errors by domain and concern. @deessejs/errors supports single inheritance, where one error type can inherit from another, creating a parent-child relationship. + +## Basic Inheritance + +To create an error that inherits from another, use the `inherits` property when defining the error factory: + +```ts title="basic.ts" +import { error, is } from '@deessejs/errors'; + +// Base error for the application +const AppError = error({ name: 'AppError' }); + +// Specific error that inherits from AppError +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); +``` + +Now `ValidationError` is a child of `AppError` in the hierarchy. You can verify this relationship using the `is()` function. + +## Type Checking with Inheritance + +When an error inherits from another, `is()` returns true for both the error itself and all its ancestors: + +```ts title="checking.ts" +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError({ field: 'email' }); + +console.log(is(err, ValidationError)); // true +console.log(is(err, AppError)); // true (through inheritance) +``` + +This is powerful for error handling — you can write a catch block for `AppError` and it will catch all errors that inherit from it, including `ValidationError`, `DatabaseError`, and any other descendants. + +## Practical Example + +Here's how inheritance helps in a real application: + +```ts title="example.ts" +import { error, raise, is } from '@deessejs/errors'; + +// Base errors +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); +const NetworkError = error({ + name: 'NetworkError', + inherits: AppError, +}); +const DatabaseError = error({ + name: 'DatabaseError', + inherits: AppError, +}); + +// Error handler that catches all application errors +function handleError(err: unknown) { + if (is(err, AppError)) { + // Catches ValidationError, NetworkError, DatabaseError, etc. + console.log(`App error: ${err.name}`); + } +} + +handleError(ValidationError({ field: 'email' })); // Logs +handleError(NetworkError({ url: '/api' })); // Logs +``` + +This pattern lets you handle errors at different levels of granularity depending on your needs. + +## Inheriting with Message Templates + +You can combine inheritance with message templates for powerful error definitions: + +```ts title="with-message.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); + +const ValidationError = error<{ field: string }>({ + name: 'ValidationError', + inherits: AppError, + message: 'Field "{field}" is invalid', +}); + +const err = ValidationError({ field: 'email' }); +console.log(err.message); // "Field "email" is invalid" +console.log(is(err, AppError)); // true +``` + +## Accessing Parent Information + +The `inherits` property on the error factory lets you access parent types programmatically: + +```ts title="parents.ts" +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +console.log(ValidationError.inherits === AppError); // true +``` + +This is useful for building introspection tools or generating documentation automatically. + +## See Also + + + + Learn how errors can inherit from multiple parents. + + + Check error types with the is() function. + + + See the complete picture of how errors work together. + + \ No newline at end of file diff --git a/apps/web/content/docs/test.mdx b/apps/web/content/docs/test.mdx deleted file mode 100644 index f475f4a..0000000 --- a/apps/web/content/docs/test.mdx +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Components -description: Components ---- - -## Code Block - -```js -console.log('Hello World'); -``` - -## Cards - - - - - diff --git a/apps/web/content/docs/type-checking.mdx b/apps/web/content/docs/type-checking.mdx new file mode 100644 index 0000000..580258f --- /dev/null +++ b/apps/web/content/docs/type-checking.mdx @@ -0,0 +1,168 @@ +--- +title: Type Checking +description: Use the is() function to check error types and inheritance relationships in @deessejs/errors. +--- + +The `is()` function is your primary tool for checking what kind of error you're dealing with. It works with @deessejs/errors instances, native JavaScript errors, and respects the full inheritance hierarchy. + +## Basic Type Checking + +Pass an error and an error type to `is()` to check if the error is an instance of that type: + +```ts title="basic.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError({ field: 'email' }); + +console.log(is(err, ValidationError)); // true +console.log(is(err, AppError)); // true (through inheritance) +``` + +The function returns `true` if the error matches the type directly or inherits from it. + +## Using is() in Catch Blocks + +The most common use case is inside catch blocks where you need to handle different error types: + +```ts title="catch-block.ts" +import { error, raise, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); +const NetworkError = error({ + name: 'NetworkError', + inherits: AppError, +}); + +function handleError(err: unknown) { + if (is(err, ValidationError)) { + console.log(`Validation failed: ${err.fields?.field}`); + } else if (is(err, NetworkError)) { + console.log(`Network error: ${err.fields?.endpoint}`); + } else if (is(err, AppError)) { + console.log(`General app error: ${err.message}`); + } +} +``` + +TypeScript narrows the type inside each branch when using `is()`, giving you access to the correct fields. + +## Working with Native Errors + +The `is()` function also works with native JavaScript errors: + +```ts title="native.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); + +try { + JSON.parse('invalid json'); +} catch (err) { + if (is(err, SyntaxError)) { + console.log('JSON parsing failed'); + } else if (is(err, AppError)) { + console.log('Application error'); + } +} +``` + +This lets you mix @deessejs/errors with standard JavaScript error handling seamlessly. + +## Type Narrowing + +When `is()` returns true, TypeScript knows the error is of that specific type. This enables type-safe access to fields: + +```ts title="type-safety.ts" +import { error, is } from '@deessejs/errors'; + +const ValidationError = error<{ field: string; reason: string }>({ + name: 'ValidationError', +}); + +function processError(err: unknown) { + if (is(err, ValidationError)) { + // TypeScript knows err is ValidationError + // So we can safely access fields + console.log(`Field: ${err.fields.field}`); + console.log(`Reason: ${err.fields.reason}`); + } +} +``` + +Without the `is()` check, TypeScript would report an error because `err` is `unknown`. + +## Multiple Type Checks + +You can check for multiple types in sequence: + +```ts title="multiple.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); +const NetworkError = error({ + name: 'NetworkError', + inherits: AppError, +}); + +function categorize(err: unknown): string { + if (is(err, NetworkError)) return 'network'; + if (is(err, ValidationError)) return 'validation'; + if (is(err, AppError)) return 'app'; + return 'unknown'; +} +``` + +Order your checks from most specific to least specific to ensure the correct handler is triggered. + +## Why Not Use instanceof? + +Native JavaScript `instanceof` doesn't work with @deessejs/errors errors because they're created with a factory function rather than a class. The `is()` function bridges this gap and adds support for inheritance checking. + +```ts title="comparison.ts" +import { error, is } from '@deessejs/errors'; + +const AppError = error({ name: 'AppError' }); +const ValidationError = error({ + name: 'ValidationError', + inherits: AppError, +}); + +const err = ValidationError({}); + +// This works +console.log(is(err, ValidationError)); // true +console.log(is(err, AppError)); // true + +// This doesn't work for @deessejs/errors +console.log(err instanceof ValidationError); // false +``` + +Always use `is()` instead of `instanceof` when working with @deessejs/errors errors. + +## See Also + + + + Learn how error hierarchies work. + + + Errors that inherit from multiple parents. + + + Properties available on error instances. + + \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 8655f96..080de6d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,10 +7,12 @@ "build": "next build", "dev": "next dev", "start": "next start", + "lint": "eslint .", "postinstall": "fumadocs-mdx && next typegen", "type-check": "tsc --noEmit" }, "dependencies": { + "@vercel/analytics": "^2.0.1", "fumadocs-core": "16.9.1", "fumadocs-mdx": "15.0.9", "fumadocs-ui": "16.9.1", @@ -18,17 +20,21 @@ "next": "16.2.6", "react": "^19.2.6", "react-dom": "^19.2.6", - "tailwind-merge": "^3.6.0" + "shiki": "^4.3.1", + "tailwind-merge": "^3.6.0", + "zod": "^4.4.3" }, "devDependencies": { + "@eslint/js": "^9.0.0", "@tailwindcss/postcss": "^4.3.0", "@types/mdx": "^2.0.13", "@types/node": "^25.9.1", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", + "eslint": "^9.0.0", "eslint-config-next": "16.2.6", "postcss": "^8.5.15", "tailwindcss": "^4.3.0", "typescript": "^6.0.3" } -} \ No newline at end of file +} diff --git a/apps/web/proxy.ts b/apps/web/proxy.ts index c5ce1be..06d5dea 100644 --- a/apps/web/proxy.ts +++ b/apps/web/proxy.ts @@ -4,11 +4,11 @@ import { docsContentRoute, docsRoute } from '@/lib/shared'; const { rewrite: rewriteDocs } = rewritePath( `${docsRoute}{/*path}`, - `${docsContentRoute}{/*path}/content.md`, + `${docsContentRoute}{/*path}/content.md` ); const { rewrite: rewriteSuffix } = rewritePath( `${docsRoute}{/*path}.md`, - `${docsContentRoute}{/*path}/content.md`, + `${docsContentRoute}{/*path}/content.md` ); export default function proxy(request: NextRequest) { diff --git a/apps/web/source.config.ts b/apps/web/source.config.ts index a35628a..2f9071b 100644 --- a/apps/web/source.config.ts +++ b/apps/web/source.config.ts @@ -1,5 +1,6 @@ -import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; +import { defineConfig, defineDocs, defineCollections } from 'fumadocs-mdx/config'; import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; +import { z } from 'zod'; // You can customize Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections @@ -16,6 +17,15 @@ export const docs = defineDocs({ }, }); +export const blog = defineCollections({ + type: 'doc', + dir: 'content/blog', + schema: pageSchema.extend({ + author: z.string(), + date: z.iso.date().or(z.date()), + }), +}); + export default defineConfig({ mdxOptions: { // MDX options diff --git a/apps/web/src/app/(home)/blog/[slug]/page.client.tsx b/apps/web/src/app/(home)/blog/[slug]/page.client.tsx new file mode 100644 index 0000000..f8dc82f --- /dev/null +++ b/apps/web/src/app/(home)/blog/[slug]/page.client.tsx @@ -0,0 +1,20 @@ +'use client'; +import { Check, Share } from 'lucide-react'; +import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'; + +export function ShareButton({ url }: { url: string }) { + const [isChecked, onCopy] = useCopyButton(() => { + void navigator.clipboard.writeText(`${window.location.origin}${url}`); + }); + + return ( + + ); +} diff --git a/apps/web/src/app/(home)/blog/[slug]/page.tsx b/apps/web/src/app/(home)/blog/[slug]/page.tsx new file mode 100644 index 0000000..3788d68 --- /dev/null +++ b/apps/web/src/app/(home)/blog/[slug]/page.tsx @@ -0,0 +1,124 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import Link from 'next/link'; +import { InlineTOC } from 'fumadocs-ui/components/inline-toc'; +import { blogSource } from '@/lib/source'; +import { baseUrl } from '@/lib/shared'; +import { getMDXComponents } from '@/components/mdx'; +import { ShareButton } from './page.client'; + +export default async function Page(props: PageProps<'/blog/[slug]'>) { + const params = await props.params; + const page = blogSource.getPage([params.slug]); + + if (!page) notFound(); + + const MDX = page.data.body; + const toc = page.data.toc; + + return ( +
+ {/* Author& Date */} +
+
+

Written by

+

{page.data.author}

+
+
+

Published

+

{new Date(page.data.date).toDateString()}

+
+
+ + {/* Title & Description */} +

{page.data.title}

+

{page.data.description}

+ + {/* Actions */} +
+ + + Back to Blog + +
+ + {/* Content */} +
+ + +
+ + {/* Article JSON-LD */} +