- A TypeScript library that brings Python-inspired error handling to
- JavaScript. Exception chaining, hierarchical inheritance, and rich
- error semantics through a function-based API.
+ A TypeScript library that brings Python-inspired error handling to JavaScript.
+ Exception chaining, hierarchical inheritance, and rich error semantics through a
+ function-based API.
- The is(){' '}
- function provides type-safe error checking.
+ The is() function provides
+ type-safe error checking.
@@ -315,8 +281,8 @@ export default function HomePage() {
Cause Traversal
- Use causes() to
- iterate the full error chain.
+ Use causes() to iterate the full
+ error chain.
@@ -329,11 +295,11 @@ export default function HomePage() {
Explore the docs
- Learn how to build robust error handling in your TypeScript
- projects.
+ Learn how to build robust error handling in your TypeScript projects.
- Read the docs →
+ Read the docs{' '}
+ →
@@ -348,8 +314,8 @@ export default function HomePage() {
From fragile to robust.
- Stop relying on fragile string matching. Get type-safe, structured
- errors that make debugging a breeze.
+ Stop relying on fragile string matching. Get type-safe, structured errors that make
+ debugging a breeze.
@@ -370,4 +336,4 @@ export default function HomePage() {
>
);
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/app/global.css b/apps/web/src/app/global.css
index e18b8f7..a658a0c 100644
--- a/apps/web/src/app/global.css
+++ b/apps/web/src/app/global.css
@@ -11,7 +11,6 @@ html > body[data-scroll-locked] {
--removed-body-scroll-bar-size: 0px !important;
}
-
@media (prefers-color-scheme: dark) {
.shiki,
.shiki span {
@@ -40,4 +39,4 @@ html.dark .shiki span {
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index e89d2df..4dfebf3 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -24,7 +24,8 @@ function JsonLd() {
'@type': 'WebSite',
name: '@deessejs/errors',
url: baseUrl,
- description: 'TypeScript error handling library with exception chaining and hierarchical inheritance',
+ description:
+ 'TypeScript error handling library with exception chaining and hierarchical inheritance',
potentialAction: {
'@type': 'SearchAction',
target: {
@@ -41,9 +42,7 @@ function JsonLd() {
name: 'Nesalia Inc',
url: 'https://nesalia.com',
logo: `${baseUrl}/icon.svg`,
- sameAs: [
- `https://github.com/${gitConfig.user}`,
- ],
+ sameAs: [`https://github.com/${gitConfig.user}`],
};
return (
@@ -67,7 +66,10 @@ export default function Layout({ children }: LayoutProps<'/'>) {
title="@deessejs/errors Blog"
href="/blog/rss.xml"
/>
-
+
diff --git a/apps/web/src/app/og/blog/[slug]/route.tsx b/apps/web/src/app/og/blog/[slug]/route.tsx
index e34cd68..3a0459f 100644
--- a/apps/web/src/app/og/blog/[slug]/route.tsx
+++ b/apps/web/src/app/og/blog/[slug]/route.tsx
@@ -17,76 +17,74 @@ export async function GET(_req: Request, { params }: RouteContext<'/og/blog/[slu
const bannerBase64 = `data:image/jpeg;base64,${bannerData.toString('base64')}`;
return new ImageResponse(
- (
+
+ {/* Banner */}
+
+
+
+ {/* Content */}
+
- {/* Banner */}
-
+ {new Date(page.data.date).toDateString()}
+
+
+ {page.data.title}
-
- {/* Content */}
-
- {new Date(page.data.date).toDateString()}
-
-
- {page.data.title}
-
-
- {page.data.description}
-
+ {page.data.description}
- ),
- { width: 1200, height: 630 },
+
,
+ { width: 1200, height: 630 }
);
}
diff --git a/apps/web/src/app/og/docs/[...slug]/route.tsx b/apps/web/src/app/og/docs/[...slug]/route.tsx
index 755fe6f..3438d14 100644
--- a/apps/web/src/app/og/docs/[...slug]/route.tsx
+++ b/apps/web/src/app/og/docs/[...slug]/route.tsx
@@ -14,145 +14,139 @@ export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...
// Strip it to get back the real page slugs.
// Defensive: only strip if the last segment is exactly 'image.png'.
const cleanSlug =
- slug.length > 1 && slug[slug.length - 1] === 'image.png'
- ? slug.slice(0, -1)
- : slug;
+ slug.length > 1 && slug[slug.length - 1] === 'image.png' ? slug.slice(0, -1) : slug;
const page = source.getPage(cleanSlug);
if (!page) notFound();
// Load banner image
- const bannerData = await readFile(
- join(process.cwd(), 'src/public/banner.jpg'),
- );
+ const bannerData = await readFile(join(process.cwd(), 'src/public/banner.jpg'));
const bannerBase64 = `data:image/jpeg;base64,${bannerData.toString('base64')}`;
return new ImageResponse(
- (
+
Install the package and start building better errors today.
@@ -62,4 +60,4 @@ export function CtaCard() {
);
-}
\ No newline at end of file
+}
diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx
index 6ee0dd1..5843c20 100644
--- a/apps/web/src/components/footer.tsx
+++ b/apps/web/src/components/footer.tsx
@@ -47,15 +47,12 @@ export function Footer() {
{/* Brand */}
-
+ Nesalia Inc.
- Software engineering as a commodity: agents that code, workflows that scale, infrastructure that works. Built with the DeesseJS ecosystem.
+ Software engineering as a commodity: agents that code, workflows that scale,
+ infrastructure that works. Built with the DeesseJS ecosystem.
@@ -65,7 +62,10 @@ export function Footer() {
{footerLinks.deessejs.map((link) => (
-
+
{link.label}
@@ -79,7 +79,10 @@ export function Footer() {
{footerLinks.learn.map((link) => (
-
+
{link.label}
@@ -93,7 +96,10 @@ export function Footer() {
{footerLinks.usecases.map((link) => (
-
+
{link.label}
@@ -107,7 +113,10 @@ export function Footer() {
{footerLinks.company.map((link) => (
-
+
{link.label}
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 59c41ba..f43f873 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ESNext",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -19,12 +15,8 @@
"jsx": "react-jsx",
"incremental": true,
"paths": {
- "@/*": [
- "./src/*"
- ],
- "collections/*": [
- "./.source/*"
- ]
+ "@/*": ["./src/*"],
+ "collections/*": ["./.source/*"]
},
"plugins": [
{
@@ -39,7 +31,5 @@
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
- "exclude": [
- "node_modules"
- ]
-}
\ No newline at end of file
+ "exclude": ["node_modules"]
+}
diff --git a/docs/internal/product/README.md b/docs/internal/product/README.md
index 85ae762..4858a1e 100644
--- a/docs/internal/product/README.md
+++ b/docs/internal/product/README.md
@@ -28,7 +28,7 @@ import { errors } from '@deessejs/errors';
const { isValidationError } = errors;
if (isValidationError(err)) {
- console.error(err.fields.field); // 'email'
+ console.error(err.fields.field); // 'email'
}
```
@@ -36,71 +36,71 @@ if (isValidationError(err)) {
### Guides
-| Guide | Description |
-|-------|-------------|
-| **[Migration Guide](./guides/migration.md)** | Migrate from native JS errors |
-| **[Testing Guide](./guides/testing.md)** | Patterns for testing error handling |
-| **[Design Philosophy](./design-philosophy.md)** | Core principles and rationale |
+| Guide | Description |
+| ----------------------------------------------- | ----------------------------------- |
+| **[Migration Guide](./guides/migration.md)** | Migrate from native JS errors |
+| **[Testing Guide](./guides/testing.md)** | Patterns for testing error handling |
+| **[Design Philosophy](./design-philosophy.md)** | Core principles and rationale |
### Feature Reference
-| Feature | File | Summary |
-|---------|------|---------|
-| **Error Factory** | [`features/error-function.md`](./features/error-function.md) | Create errors with `error()` |
-| **Raise / Throw** | [`features/raise-function.md`](./features/raise-function.md) | Throw errors with `raise()` or `throw` |
-| **Chaining** | [`features/chaining.md`](./features/chaining.md) | Chain errors with `.from()` |
-| **Chain Traversal** | [`features/chain-traversal.md`](./features/chain-traversal.md) | Navigate causes with `causes()` |
-| **Notes** | [`features/notes.md`](./features/notes.md) | Enrich with `.addNote()` |
-| **Instance Checking** | [`features/is-function.md`](./features/is-function.md) | Check types with `is()` |
-| **Type Guards** | [`features/type-guards.md`](./features/type-guards.md) | Type-safe error narrowing |
-| **Context Injection** | [`features/context-injection.md`](./features/context-injection.md) | Inject context with `withContext()` |
-| **Message Formatting** | [`features/message-formatting.md`](./features/message-formatting.md) | Template messages |
-| **Stack Cleaning** | [`features/stack-cleaning.md`](./features/stack-cleaning.md) | Clean stacks with `stripLibraryFrames()` |
-| **Predefined Errors** | [`features/predefined-errors.md`](./features/predefined-errors.md) | Built-in error types |
-| **Inheritance** | [`features/inheritance.md`](./features/inheritance.md) | Single and multiple inheritance |
-| **Output Formatting** | [`features/output-formatting.md`](./features/output-formatting.md) | Dev vs Prod output |
-| **Async Support** | [`features/async-support.md`](./features/async-support.md) | Async patterns |
+| Feature | File | Summary |
+| ---------------------- | -------------------------------------------------------------------- | ---------------------------------------- |
+| **Error Factory** | [`features/error-function.md`](./features/error-function.md) | Create errors with `error()` |
+| **Raise / Throw** | [`features/raise-function.md`](./features/raise-function.md) | Throw errors with `raise()` or `throw` |
+| **Chaining** | [`features/chaining.md`](./features/chaining.md) | Chain errors with `.from()` |
+| **Chain Traversal** | [`features/chain-traversal.md`](./features/chain-traversal.md) | Navigate causes with `causes()` |
+| **Notes** | [`features/notes.md`](./features/notes.md) | Enrich with `.addNote()` |
+| **Instance Checking** | [`features/is-function.md`](./features/is-function.md) | Check types with `is()` |
+| **Type Guards** | [`features/type-guards.md`](./features/type-guards.md) | Type-safe error narrowing |
+| **Context Injection** | [`features/context-injection.md`](./features/context-injection.md) | Inject context with `withContext()` |
+| **Message Formatting** | [`features/message-formatting.md`](./features/message-formatting.md) | Template messages |
+| **Stack Cleaning** | [`features/stack-cleaning.md`](./features/stack-cleaning.md) | Clean stacks with `stripLibraryFrames()` |
+| **Predefined Errors** | [`features/predefined-errors.md`](./features/predefined-errors.md) | Built-in error types |
+| **Inheritance** | [`features/inheritance.md`](./features/inheritance.md) | Single and multiple inheritance |
+| **Output Formatting** | [`features/output-formatting.md`](./features/output-formatting.md) | Dev vs Prod output |
+| **Async Support** | [`features/async-support.md`](./features/async-support.md) | Async patterns |
## API Summary
### Core Functions
-| Function | Purpose |
-|----------|---------|
-| `raise(errorInstance)` | Raise an error |
-| `throw errorInstance` | Native throw syntax (also supported) |
-| `is(err, ErrorType)` | Check if error is of type |
-| `causes(err)` | Get cause chain (most recent first) |
+| Function | Purpose |
+| ---------------------- | ------------------------------------ |
+| `raise(errorInstance)` | Raise an error |
+| `throw errorInstance` | Native throw syntax (also supported) |
+| `is(err, ErrorType)` | Check if error is of type |
+| `causes(err)` | Get cause chain (most recent first) |
### Methods on Error Instances
-| Method | Purpose |
-|--------|---------|
-| `err.from(cause)` | Chain exceptions |
+| Method | Purpose |
+| ------------------- | -------------------- |
+| `err.from(cause)` | Chain exceptions |
| `err.addNote(note)` | Add enrichment notes |
### Error Properties
All properties are always defined on every error instance:
-| Property | Type | Description |
-|----------|------|-------------|
-| `err.name` | `string` | Error name |
-| `err.message` | `string` | Formatted message |
-| `err.stack` | `string` | Stack trace |
-| `err.fields` | `Record` | User-defined fields (empty object if none) |
-| `err.notes` | `string[]` | All notes (empty array if none) |
-| `err.cause` | `Error \| null` | Direct cause (null if none) |
-| `err.causes` | `Error[]` | Full cause chain (most recent first) |
-| `err.context` | `Record \| null` | Injected context (null if none) |
+| Property | Type | Description |
+| ------------- | --------------------------------- | ------------------------------------------ |
+| `err.name` | `string` | Error name |
+| `err.message` | `string` | Formatted message |
+| `err.stack` | `string` | Stack trace |
+| `err.fields` | `Record` | User-defined fields (empty object if none) |
+| `err.notes` | `string[]` | All notes (empty array if none) |
+| `err.cause` | `Error \| null` | Direct cause (null if none) |
+| `err.causes` | `Error[]` | Full cause chain (most recent first) |
+| `err.context` | `Record \| null` | Injected context (null if none) |
### Utility Functions
-| Function | Purpose |
-|----------|---------|
-| `withContext(ctx, fn)` | Inject context into errors |
-| `stripLibraryFrames(err)` | Remove internal frames |
-| `formatError(err, options)` | Format error for output |
+| Function | Purpose |
+| --------------------------- | -------------------------- |
+| `withContext(ctx, fn)` | Inject context into errors |
+| `stripLibraryFrames(err)` | Remove internal frames |
+| `formatError(err, options)` | Format error for output |
### Predefined Errors
@@ -118,38 +118,38 @@ const { isValidationError, isNotFoundError } = errors;
## Comparison with Native JS Errors
-| Feature | Native JS | @deessejs/errors |
-|---------|----------|------------------|
-| Custom errors | Class inheritance | `error()` factory |
-| Schema validation | Not built-in | Via Standard Schema |
-| Inheritance | `extends` chains | `inherits:` option (multiple) |
-| Instance checking | `instanceof` | `is(err, ErrorType)` |
-| Cause chaining | Manual `err.cause` | First-class `.from()` |
-| Chain traversal | Not available | `causes()` / `err.causes` |
-| Exception notes | Not supported | `.addNote()` |
-| Predefined errors | `new TypeError()` | `errors.TypeError` (namespaced) |
-| Type guards | Manual | `isXxxError()` functions |
-| Colored output | Console + boilerplate | Built-in |
-| JSON serialization | Manual | Automatic |
-| Throwing | `throw err` | `raise(err)` or `throw` |
-| Type interoperability | None | Standard Schema |
+| Feature | Native JS | @deessejs/errors |
+| --------------------- | --------------------- | ------------------------------- |
+| Custom errors | Class inheritance | `error()` factory |
+| Schema validation | Not built-in | Via Standard Schema |
+| Inheritance | `extends` chains | `inherits:` option (multiple) |
+| Instance checking | `instanceof` | `is(err, ErrorType)` |
+| Cause chaining | Manual `err.cause` | First-class `.from()` |
+| Chain traversal | Not available | `causes()` / `err.causes` |
+| Exception notes | Not supported | `.addNote()` |
+| Predefined errors | `new TypeError()` | `errors.TypeError` (namespaced) |
+| Type guards | Manual | `isXxxError()` functions |
+| Colored output | Console + boilerplate | Built-in |
+| JSON serialization | Manual | Automatic |
+| Throwing | `throw err` | `raise(err)` or `throw` |
+| Type interoperability | None | Standard Schema |
## Comparison with Python
-| Feature | Python | @deessejs/errors |
-|---------|--------|------------------|
-| Error definition | `class X(Exception)` | `error({ name, fields? })` |
-| Inheritance | `class X(Y)` | `inherits:` option |
-| Multiple inheritance | `class X(Y, Z)` | `inherits: [Y, Z]` |
-| Instance checking | `isinstance()` | `is(err, Type)` |
-| Raise syntax | `raise X()` | `raise(X())` or `throw X()` |
-| Chaining | `raise X from Y` | `raise(X().from(Y))` |
-| Exception notes | `add_note()` | `.addNote()` |
-| Error groups | `ExceptionGroup` | Async patterns |
-| Context | `__context__` | `withContext()` |
-| Stack cleaning | Not built-in | `stripLibraryFrames()` |
-| JSON serialization | `json.dumps()` | Automatic |
-| Schema spec | Not applicable | Standard Schema |
+| Feature | Python | @deessejs/errors |
+| -------------------- | -------------------- | --------------------------- |
+| Error definition | `class X(Exception)` | `error({ name, fields? })` |
+| Inheritance | `class X(Y)` | `inherits:` option |
+| Multiple inheritance | `class X(Y, Z)` | `inherits: [Y, Z]` |
+| Instance checking | `isinstance()` | `is(err, Type)` |
+| Raise syntax | `raise X()` | `raise(X())` or `throw X()` |
+| Chaining | `raise X from Y` | `raise(X().from(Y))` |
+| Exception notes | `add_note()` | `.addNote()` |
+| Error groups | `ExceptionGroup` | Async patterns |
+| Context | `__context__` | `withContext()` |
+| Stack cleaning | Not built-in | `stripLibraryFrames()` |
+| JSON serialization | `json.dumps()` | Automatic |
+| Schema spec | Not applicable | Standard Schema |
## Goals
diff --git a/docs/internal/product/design-philosophy.md b/docs/internal/product/design-philosophy.md
index 99daf15..09f0ae5 100644
--- a/docs/internal/product/design-philosophy.md
+++ b/docs/internal/product/design-philosophy.md
@@ -9,6 +9,7 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `error({ name, fields })` instead of `class X extends Error`
**Why:**
+
- No `extends` chains to trace through code
- Composition over inheritance
- No `new` keyword, no `super()` calls
@@ -16,6 +17,7 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
- Errors are data, not objects with behavior
**Tradeoff:**
+
- Familiarity: JS developers know class syntax
- Resolution: The function API is simple enough that familiarity isn't needed
@@ -24,11 +26,13 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `error.from(cause)` instead of `from(error, cause)`
**Why:**
+
- Method chaining reads in execution order
- IDE autocomplete works naturally
- "Object.action" is idiomatic
**Tradeoff:**
+
- Some prefer functional pipelines
- Resolution: Methods are for actions on instances, functions are for utilities
@@ -37,11 +41,13 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `err.name` instead of `err.name()`
**Why:**
+
- Properties are for data, methods are for actions
- Less verbose: `err.name` vs `err.name()`
- Consistent with native Error: `err.message`, `err.stack`
**Tradeoff:**
+
- Some data requires computation (not applicable here)
### 4. Single Namespace for User Data
@@ -49,11 +55,13 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `err.fields` for all user-defined data
**Why:**
+
- Avoids collisions with built-in properties: `name`, `message`, `stack`, `cause`, `context`
- Clear separation between library properties and user data
- `fields` is always an object (never `undefined`)
**Tradeoff:**
+
- Double nesting: `err.fields.field` vs `err.field`
- Resolution: Explicit is better than implicit; collisions are prevented
@@ -62,18 +70,21 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** Two separate mechanisms for adding data to errors
**When to use fields:**
+
- Data is specific to this error type
- Field is meaningful for debugging this error
- Field appears in the message template
- Example: `{ field: 'email', reason: 'invalid format' }`
**When to use context:**
+
- Data is request-scoped or infrastructure-level
- Same data appears across many different errors
- Data is needed for correlation (requestId, traceId)
- Example: `{ requestId: 'req-123', userId: 'user-456' }`
**Why both?**
+
- Fields: Error-specific data
- Context: Cross-cutting data
- Separation enables clear logging and analysis
@@ -83,12 +94,14 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `error({ name })` returns a factory, not a class
**Why:**
+
- TypeScript infers types from fields
- No `new` required
- Composition is natural
- Works with `raise()` and `throw`
**Tradeoff:**
+
- Can't use `instanceof` (use `is()` instead)
- Resolution: `is()` is the replacement
@@ -97,6 +110,7 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `error.name` is a property, not the return value
**Why:**
+
- Name is metadata, not instance data
- Consistent with Standard Schema
- Easier to introspect error types without instantiation
@@ -106,11 +120,13 @@ This document explains the core design principles behind `@deessejs/errors`. Eac
**The choice:** `causes(err)` returns `[mostRecent, ..., root]`
**Why:**
+
- Most recent error is usually the most important for handling
- Natural for finding error codes
- Less common to need root-first ordering
**Contrast with Python:**
+
- Python's traceback shows root-first
- We chose most-recent-first for practical reasons
diff --git a/docs/internal/product/features/async-support.md b/docs/internal/product/features/async-support.md
index 0943f8f..db54c4c 100644
--- a/docs/internal/product/features/async-support.md
+++ b/docs/internal/product/features/async-support.md
@@ -28,9 +28,7 @@ async function fetchUser(id: string) {
const user = await getUser(id);
return user;
} catch (err) {
- raise(
- AppError().from(err).addNote(`Failed to fetch user ${id}`)
- );
+ raise(AppError().from(err).addNote(`Failed to fetch user ${id}`));
}
}
```
@@ -43,27 +41,23 @@ When multiple operations run concurrently, handle failures gracefully:
import { errors, raise } from '@deessejs/errors';
async function processAll(items: Item[]) {
- const results = await Promise.allSettled(
- items.map(item => processItem(item))
- );
+ const results = await Promise.allSettled(items.map((item) => processItem(item)));
- const failures = results
- .filter(r => r.status === 'rejected')
- .map(r => r.reason);
+ const failures = results.filter((r) => r.status === 'rejected').map((r) => r.reason);
if (failures.length > 0) {
raise(
- errors.ValidationError({
- field: 'batch',
- message: `${failures.length}/${items.length} items failed`,
- }).from(failures[0])
+ errors
+ .ValidationError({
+ field: 'batch',
+ message: `${failures.length}/${items.length} items failed`,
+ })
+ .from(failures[0])
);
}
// Return successful results
- return results
- .filter(r => r.status === 'fulfilled')
- .map(r => r.value);
+ return results.filter((r) => r.status === 'fulfilled').map((r) => r.value);
}
```
@@ -73,13 +67,10 @@ async function processAll(items: Item[]) {
import { errors, raise, withContext } from '@deessejs/errors';
async function handleRequest(req: Request) {
- return withContext(
- { requestId: req.id, userId: req.userId },
- async () => {
- const user = await fetchUser(req.userId);
- return processUser(user);
- }
- );
+ return withContext({ requestId: req.id, userId: req.userId }, async () => {
+ const user = await fetchUser(req.userId);
+ return processUser(user);
+ });
// Any error raised here has request context
}
```
@@ -105,9 +96,9 @@ async function workflow() {
} catch (err) {
// Log the full chain
console.error('Error chain:');
- causes(err).forEach(cause => {
+ causes(err).forEach((cause) => {
console.error(` - ${cause.name}: ${cause.message}`);
- cause.notes.forEach(note => console.error(` Note: ${note}`));
+ cause.notes.forEach((note) => console.error(` Note: ${note}`));
});
throw err;
}
@@ -136,7 +127,7 @@ async function validateAndProcess(item: Item) {
if (is(err, AppError)) {
validationErrors.push(err as RequiredFieldError);
} else {
- throw err; // Re-throw non-validation errors
+ throw err; // Re-throw non-validation errors
}
}
}
@@ -195,6 +186,7 @@ await withErrorHandling(
**Why no ExceptionGroup?**
Python's `ExceptionGroup` bundles multiple errors for `except*` handling. In JS:
+
1. **No `except*`** — JS lacks Python's `except*` syntax
2. **Promise.allSettled** — Handles the common case well
3. **Simplicity for v1** — One error at a time is simpler
@@ -204,6 +196,7 @@ For v1, we provide patterns that work with `Promise.allSettled` rather than addi
**Async vs Sync API**
All features work the same in sync and async contexts:
+
- `.from()` chains regardless of how error was thrown
- `.addNote()` enriches regardless of sync/async
- `withContext()` works with async functions
diff --git a/docs/internal/product/features/chain-traversal.md b/docs/internal/product/features/chain-traversal.md
index 1348650..bc98973 100644
--- a/docs/internal/product/features/chain-traversal.md
+++ b/docs/internal/product/features/chain-traversal.md
@@ -12,8 +12,8 @@ function causes(errorInstance: ErrorInstance): ErrorInstance[];
### Parameters
-| Parameter | Type | Description |
-|-----------|------|-------------|
+| Parameter | Type | Description |
+| --------------- | --------------- | --------------------- |
| `errorInstance` | `ErrorInstance` | The error to traverse |
### Returns
@@ -34,11 +34,7 @@ const DatabaseError = error({ name: 'DatabaseError' });
try {
doSomething();
} catch (err) {
- throw DatabaseError().from(
- NetworkError().from(
- AppError().from(err)
- )
- );
+ throw DatabaseError().from(NetworkError().from(AppError().from(err)));
}
// Later
@@ -87,7 +83,7 @@ try {
doSomething();
} catch (err) {
// Check if any error in the chain is a TimeoutError
- const hasTimeout = causes(err).some(e => is(e, TimeoutError));
+ const hasTimeout = causes(err).some((e) => is(e, TimeoutError));
if (hasTimeout) {
// Handle timeout specially
@@ -99,7 +95,6 @@ try {
```typescript
import { causes } from '@deessejs/errors';
-
```
## Property Access
@@ -120,6 +115,7 @@ See [Design Philosophy](../design-philosophy.md) for core principles.
**Why most-recent-first ordering?**
Most code needs to find the most relevant (most recent) error first:
+
- Type checking: `err.causes.some(e => is(e, TimeoutError))`
**Why different from `err.cause.cause.cause`?**
diff --git a/docs/internal/product/features/chaining.md b/docs/internal/product/features/chaining.md
index 3de83e6..61bc8e8 100644
--- a/docs/internal/product/features/chaining.md
+++ b/docs/internal/product/features/chaining.md
@@ -15,9 +15,9 @@ interface ErrorInstance {
### Method Signature
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `cause` | `Error \| ErrorInstance \| Error` | The error that caused this one |
+| Parameter | Type | Description |
+| --------- | --------------------------------- | ------------------------------ |
+| `cause` | `Error \| ErrorInstance \| Error` | The error that caused this one |
### Returns
@@ -36,9 +36,7 @@ const HighLevelError = error({ name: 'HighLevelError' });
try {
lowLevel();
} catch (err) {
- raise(
- HighLevelError().from(err)
- );
+ raise(HighLevelError().from(err));
}
```
@@ -54,11 +52,7 @@ const TopError = error({ name: 'TopError' });
try {
doSomething();
} catch (err) {
- throw TopError().from(
- MiddleError().from(
- AppError().from(err)
- )
- );
+ throw TopError().from(MiddleError().from(AppError().from(err)));
}
// Later, traverse the chain (most recent first)
@@ -77,7 +71,7 @@ const AppError = error({ name: 'AppError' });
try {
JSON.parse(invalidJson);
} catch (err) {
- raise(AppError().from(err)); // Works with SyntaxError
+ raise(AppError().from(err)); // Works with SyntaxError
}
```
@@ -100,9 +94,9 @@ const firstError = errors[0];
// The batch error contains info about all failures
// But the cause chain only tracks the first one
-raise(
- BatchError({ count: errors.length }).from(firstError)
-).addNote(`${errors.length} operations failed`);
+raise(BatchError({ count: errors.length }).from(firstError)).addNote(
+ `${errors.length} operations failed`
+);
```
## Design Rationale
@@ -113,7 +107,8 @@ See [Design Philosophy](../design-philosophy.md) for core principles.
**Why not named `cause()` like the property?**
-`.from()` emphasizes the *action* of chaining, while `cause` is the *result*:
+`.from()` emphasizes the _action_ of chaining, while `cause` is the _result_:
+
- `.from(err)` = "set cause to err"
- `err.cause` = "get the cause"
diff --git a/docs/internal/product/features/context-injection.md b/docs/internal/product/features/context-injection.md
index 16b34ae..02bea1e 100644
--- a/docs/internal/product/features/context-injection.md
+++ b/docs/internal/product/features/context-injection.md
@@ -7,15 +7,15 @@ The `withContext()` function injects contextual information into errors that are
## API
```typescript
-function withContext(context: Record, fn: () => T): T
+function withContext(context: Record, fn: () => T): T;
```
### Parameters
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `context` | `Record` | Context key-value pairs to inject |
-| `fn` | `() => T` | Function to run within this context |
+| Parameter | Type | Description |
+| --------- | ------------------------- | ----------------------------------- |
+| `context` | `Record` | Context key-value pairs to inject |
+| `fn` | `() => T` | Function to run within this context |
### Returns
@@ -51,11 +51,11 @@ try {
raise(AppError());
});
} catch (err) {
- err.fields.userId; // '123'
- err.fields.requestId; // 'req-456'
- err.context; // { userId: '123', requestId: 'req-456' }
+ err.fields.userId; // '123'
+ err.fields.requestId; // 'req-456'
+ err.context; // { userId: '123', requestId: 'req-456' }
- err.message; // Formatted message with context
+ err.message; // Formatted message with context
}
```
@@ -92,7 +92,7 @@ try {
doSomething();
});
} catch (err) {
- causes(err).forEach(cause => {
+ causes(err).forEach((cause) => {
console.error({
error: cause.name,
context: cause.context,
@@ -109,23 +109,24 @@ Context injection relies on `AsyncLocalStorage` (Node.js) or equivalent mechanis
### Environment Support
-| Environment | Support | Notes |
-|-------------|---------|-------|
-| Node.js 16+ | ✅ Full | Native AsyncLocalStorage |
-| Node.js 12-14 | ⚠️ Polyfill | Requires manual AsyncLocalStorage polyfill |
-| Deno | ✅ Full | AsyncLocalStorage available |
-| Bun | ✅ Full | AsyncLocalStorage available |
-| Browsers | ⚠️ Partial | Works in modern browsers, not IE11 |
-| Next.js | ✅ Full | Server-side rendering supported |
-| Express/Koa | ✅ Full | Works in request handlers |
-| Cloudflare Workers | ❌ Limited | No AsyncLocalStorage |
-| AWS Lambda | ⚠️ Varies | Depends on runtime (Node.js vs custom) |
-| Vercel Edge | ❌ Limited | No AsyncLocalStorage |
-| Web Workers | ⚠️ Partial | Works in dedicated workers, not shared |
+| Environment | Support | Notes |
+| ------------------ | ----------- | ------------------------------------------ |
+| Node.js 16+ | ✅ Full | Native AsyncLocalStorage |
+| Node.js 12-14 | ⚠️ Polyfill | Requires manual AsyncLocalStorage polyfill |
+| Deno | ✅ Full | AsyncLocalStorage available |
+| Bun | ✅ Full | AsyncLocalStorage available |
+| Browsers | ⚠️ Partial | Works in modern browsers, not IE11 |
+| Next.js | ✅ Full | Server-side rendering supported |
+| Express/Koa | ✅ Full | Works in request handlers |
+| Cloudflare Workers | ❌ Limited | No AsyncLocalStorage |
+| AWS Lambda | ⚠️ Varies | Depends on runtime (Node.js vs custom) |
+| Vercel Edge | ❌ Limited | No AsyncLocalStorage |
+| Web Workers | ⚠️ Partial | Works in dedicated workers, not shared |
### What Happens in Unsupported Environments?
If `withContext()` is called in an environment without `AsyncLocalStorage`:
+
1. The function runs normally
2. No error is thrown
3. `err.context` will be `null` on any raised errors
@@ -164,6 +165,7 @@ withContext({ userId: '123', requestId: 'req-456' }, () => {
**How is context implemented?**
Context is stored using `AsyncLocalStorage`. When an error is raised:
+
1. The library checks for active context via the storage API
2. If found, the context is attached to the error
3. The error instance gets `context` property set
@@ -182,6 +184,7 @@ withContext({ userId: '123' }, () => {
**Why not throw an error in unsupported environments?**
Silent failure allows the code to work in supported and unsupported environments:
+
- In supported environments: full context injection
- In unsupported environments: code still runs, just without context
diff --git a/docs/internal/product/features/error-function.md b/docs/internal/product/features/error-function.md
index b0bd440..3fe846b 100644
--- a/docs/internal/product/features/error-function.md
+++ b/docs/internal/product/features/error-function.md
@@ -14,17 +14,17 @@ function error(config: {
fields?: StandardSchemaV1;
inherits?: ErrorFactory | ErrorFactory[];
message?: string;
-}): ErrorFactory
+}): ErrorFactory;
```
### Parameters
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `name` | `string` | Yes | Error name identifier |
-| `fields` | `StandardSchemaV1` | No | Schema fields (Zod, Valibot, ArkType, etc.) |
-| `inherits` | `ErrorFactory \| ErrorFactory[]` | No | Parent error factory to inherit from |
-| `message` | `string` | No | Message template with `{field}` placeholders |
+| Parameter | Type | Required | Description |
+| ---------- | -------------------------------- | -------- | -------------------------------------------- |
+| `name` | `string` | Yes | Error name identifier |
+| `fields` | `StandardSchemaV1` | No | Schema fields (Zod, Valibot, ArkType, etc.) |
+| `inherits` | `ErrorFactory \| ErrorFactory[]` | No | Parent error factory to inherit from |
+| `message` | `string` | No | Message template with `{field}` placeholders |
### Returns
@@ -61,8 +61,8 @@ const ValidationError = error({
});
const err = ValidationError({ field: 'email', reason: 'invalid format' });
-err.fields.field; // 'email'
-err.fields.reason; // 'invalid format'
+err.fields.field; // 'email'
+err.fields.reason; // 'invalid format'
```
### With Valibot
@@ -106,11 +106,11 @@ const AppError = error({ name: 'AppError' });
const ValidationError = error({
name: 'ValidationError',
- inherits: AppError, // Pass the factory function
+ inherits: AppError, // Pass the factory function
});
-is(err, AppError); // true if err is ValidationError
-is(err, ValidationError); // true
+is(err, AppError); // true if err is ValidationError
+is(err, ValidationError); // true
```
### Multiple Inheritance
@@ -121,11 +121,11 @@ const NetworkError = error({ name: 'NetworkError' });
const CombinedError = error({
name: 'CombinedError',
- inherits: [AppError, NetworkError], // Array of factories
+ inherits: [AppError, NetworkError], // Array of factories
});
-is(err, AppError); // true if err is CombinedError
-is(err, NetworkError); // true if err is CombinedError
+is(err, AppError); // true if err is CombinedError
+is(err, NetworkError); // true if err is CombinedError
```
### With Message Template
@@ -142,7 +142,7 @@ const RequiredFieldError = error({
});
const err = RequiredFieldError({ field: 'email' });
-err.message; // 'Field "email" is required'
+err.message; // 'Field "email" is required'
```
### With HTTP Status
@@ -178,7 +178,7 @@ const ValidationError = error({
}),
});
-ValidationError.name; // 'ValidationError'
+ValidationError.name; // 'ValidationError'
// TypeScript infers types from the Zod schema
```
@@ -186,16 +186,16 @@ ValidationError.name; // 'ValidationError'
All errors have these guaranteed properties:
-| Property | Type | Always Exists | Description |
-|----------|------|---------------|-------------|
-| `name` | `string` | Yes | Error name |
-| `message` | `string` | Yes | Formatted message |
-| `stack` | `string` | Yes | Stack trace |
-| `fields` | `Record` | Yes | User-defined fields (empty object if none) |
-| `notes` | `string[]` | Yes | Notes (empty array if none) |
-| `cause` | `Error \| null` | Yes | Direct cause (null if none) |
-| `causes` | `Error[]` | Yes | Full cause chain (may be empty) |
-| `context` | `Record \| null` | Yes | Injected context (null if none) |
+| Property | Type | Always Exists | Description |
+| --------- | --------------------------------- | ------------- | ------------------------------------------ |
+| `name` | `string` | Yes | Error name |
+| `message` | `string` | Yes | Formatted message |
+| `stack` | `string` | Yes | Stack trace |
+| `fields` | `Record` | Yes | User-defined fields (empty object if none) |
+| `notes` | `string[]` | Yes | Notes (empty array if none) |
+| `cause` | `Error \| null` | Yes | Direct cause (null if none) |
+| `causes` | `Error[]` | Yes | Full cause chain (may be empty) |
+| `context` | `Record \| null` | Yes | Injected context (null if none) |
### Field Access
@@ -215,8 +215,8 @@ const ValidationError = error({
const err = ValidationError({ field: 'email', reason: 'invalid format' });
// Correct
-err.fields.field; // 'email'
-err.fields.reason; // 'invalid format'
+err.fields.field; // 'email'
+err.fields.reason; // 'invalid format'
// Why not err.field directly?
// Because field names could collide with built-in properties like:
@@ -237,8 +237,7 @@ const ValidationError = error({
fields: z.object({ field: z.string() }),
});
-const err = ValidationError({ field: 'email' })
- .addNote('Added at runtime');
+const err = ValidationError({ field: 'email' }).addNote('Added at runtime');
JSON.stringify(err);
// {
@@ -254,6 +253,7 @@ JSON.stringify(err);
```
**Note:** Inheritance relationships are not serialized. When deserializing:
+
- `is(err, ParentError)` will return `false` for re-parsed JSON
- This is a known limitation; use error chaining for portable cause tracking
@@ -264,6 +264,7 @@ See [Design Philosophy](../design-philosophy.md) for core principles.
**Why Standard Schema?**
Standard Schema allows the library to integrate with any validation library:
+
- **Zod**, **Valibot**, **ArkType**, and many others implement this interface
- No additional runtime dependencies beyond your choice of validator
- Integrate once, validate anywhere
@@ -282,14 +283,14 @@ const UserError = error({
```
**Why `inherits` takes a factory function, not a string?**
+
- Type-safe — TypeScript validates the inheritance
- No circular dependency issues
- IDE autocomplete works
-
## Related Features
- [raise-function.md](./raise-function.md) — How to throw errors
- [chaining.md](./chaining.md) — Exception chaining with `.from()`
- [inheritance.md](./inheritance.md) — Deep dive on inheritance
-- [message-formatting.md](./message-formatting.md) — Message templates
\ No newline at end of file
+- [message-formatting.md](./message-formatting.md) — Message templates
diff --git a/docs/internal/product/features/inheritance.md b/docs/internal/product/features/inheritance.md
index 11fd34a..1fd9733 100644
--- a/docs/internal/product/features/inheritance.md
+++ b/docs/internal/product/features/inheritance.md
@@ -9,9 +9,9 @@ Errors can inherit from one or more parent errors, enabling hierarchical error t
```typescript
const MyError = error({
name: 'MyError',
- inherits: ParentError, // Single parent
+ inherits: ParentError, // Single parent
// or
- inherits: [ParentError1, ParentError2], // Multiple parents
+ inherits: [ParentError1, ParentError2], // Multiple parents
});
```
@@ -55,9 +55,9 @@ const CombinedError = error({
// Now CombinedError is ALL of them
const err = CombinedError();
-is(err, AppError); // true
-is(err, NetworkError); // true
-is(err, StorageError); // true
+is(err, AppError); // true
+is(err, NetworkError); // true
+is(err, StorageError); // true
```
### Deep inheritance
@@ -92,10 +92,10 @@ const RequiredFieldError = error({
// └── ValidationError
// └── RequiredFieldError
-is(err, RequiredFieldError); // true
-is(err, ValidationError); // true
-is(err, DomainError); // true
-is(err, AppError); // true
+is(err, RequiredFieldError); // true
+is(err, ValidationError); // true
+is(err, DomainError); // true
+is(err, AppError); // true
```
### Control Flow with Inheritance
@@ -117,7 +117,7 @@ try {
} else if (is(err, AppError)) {
// Generic app error handling
} else {
- throw err; // Re-throw unknown errors
+ throw err; // Re-throw unknown errors
}
}
```
@@ -145,6 +145,7 @@ class ValidationError extends AppError {
```
**Advantages of inheritance approach:**
+
- No class syntax needed
- Inherit from multiple parents
- Errors are just data, not classes
@@ -175,6 +176,7 @@ const MyError = error({ name: 'MyError' });
**Why the `inherits` property?**
Runtime inheritance metadata enables:
+
- `is()` to traverse the hierarchy
- JSON serialization to preserve type info
- IDE/type support for code completion
diff --git a/docs/internal/product/features/is-function.md b/docs/internal/product/features/is-function.md
index 66ae23e..1c35bc8 100644
--- a/docs/internal/product/features/is-function.md
+++ b/docs/internal/product/features/is-function.md
@@ -7,17 +7,14 @@ The `is()` function checks if an error is an instance of a specific error type (
## API
```typescript
-function is(
- error: unknown,
- ErrorType: T
-): boolean
+function is(error: unknown, ErrorType: T): boolean;
```
### Parameters
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `error` | `unknown` | The error to check |
+| Parameter | Type | Description |
+| ----------- | ----------- | ------------------------------- |
+| `error` | `unknown` | The error to check |
| `ErrorType` | `ErrorType` | The error type to check against |
### Returns
@@ -74,10 +71,10 @@ const OperationError = error({
const err = OperationError();
-is(err, NetworkError); // true
-is(err, DatabaseError); // true
-is(err, OperationError); // true
-is(err, AppError); // true (through any parent)
+is(err, NetworkError); // true
+is(err, DatabaseError); // true
+is(err, OperationError); // true
+is(err, AppError); // true (through any parent)
```
### Multiple Type Check
@@ -119,11 +116,11 @@ The function approach is more explicit about the two operands and reads naturall
## Comparison Table
-| Method | Native Errors | Custom Errors | Hierarchy | Syntax |
-|--------|--------------|---------------|----------|--------|
-| `instanceof` | ✓ | ✓ | ✓ | `err instanceof X` |
-| `is()` | ✓ | ✓ | ✓ | `is(err, X)` |
-| `err.name === 'X'` | ✓ | ✓ | ✗ | Manual string compare |
+| Method | Native Errors | Custom Errors | Hierarchy | Syntax |
+| ------------------ | ------------- | ------------- | --------- | --------------------- |
+| `instanceof` | ✓ | ✓ | ✓ | `err instanceof X` |
+| `is()` | ✓ | ✓ | ✓ | `is(err, X)` |
+| `err.name === 'X'` | ✓ | ✓ | ✗ | Manual string compare |
## Related Features
diff --git a/docs/internal/product/features/message-formatting.md b/docs/internal/product/features/message-formatting.md
index 7bf2445..14aee29 100644
--- a/docs/internal/product/features/message-formatting.md
+++ b/docs/internal/product/features/message-formatting.md
@@ -22,32 +22,32 @@ const MyError = error({
// Message is automatically formatted
const err = MyError({ field1: 'email', field2: 42 });
-err.message; // 'Template with email and 42'
+err.message; // 'Template with email and 42'
```
### Access
```typescript
-err.message; // Formatted message string
+err.message; // Formatted message string
```
### Placeholder Syntax
Placeholders use `{fieldName}` syntax:
-| Placeholder | Output |
-|-------------|--------|
-| `{fieldName}` | Inserts the field value |
-| `{fieldName:upper}` | Uppercase the value |
-| `{fieldName:lower}` | Lowercase the value |
-| `{fieldName:json}` | JSON stringify the value |
+| Placeholder | Output |
+| ------------------- | ------------------------ |
+| `{fieldName}` | Inserts the field value |
+| `{fieldName:upper}` | Uppercase the value |
+| `{fieldName:lower}` | Lowercase the value |
+| `{fieldName:json}` | JSON stringify the value |
### Escaping
Prepend `\` to escape:
```typescript
-message: 'Enter \{fieldName\} here'
+message: 'Enter \{fieldName\} here';
// Output: 'Enter {fieldName} here'
```
@@ -67,7 +67,7 @@ const RequiredFieldError = error({
});
const err = RequiredFieldError({ field: 'email' });
-err.message; // 'Field "email" is required'
+err.message; // 'Field "email" is required'
```
### Multiple Fields
@@ -91,7 +91,7 @@ const err = ValidationError({
actual: 'string',
});
-err.message; // 'Field "age" expected number, got string'
+err.message; // 'Field "age" expected number, got string'
```
### Nested Fields
@@ -113,7 +113,7 @@ const err = ConfigError({
path: 'config.json',
});
-err.message; // 'Config key "database.url" not found in config.json'
+err.message; // 'Config key "database.url" not found in config.json'
```
### Modifiers
@@ -130,7 +130,7 @@ const AppError = error({
});
const err = AppError({ userId: 'abc123' });
-err.message; // 'User ID: ABC123'
+err.message; // 'User ID: ABC123'
```
### JSON Modifier
@@ -147,7 +147,7 @@ const DataError = error({
});
const err = DataError({ data: { id: 1, name: 'test' } });
-err.message; // 'Invalid data: {"id":1,"name":"test"}'
+err.message; // 'Invalid data: {"id":1,"name":"test"}'
```
## Error Display
@@ -188,6 +188,7 @@ raise(ValidationError({ field: 'email' }));
**Why placeholder syntax `{field}` not template literals?**
Template literals in JavaScript are `` `hello ${world}` ``. The `{field}` syntax:
+
- Matches Python's str.format()
- Is explicit about which fields are in the message
- Allows modifiers like `:upper`
@@ -195,6 +196,7 @@ Template literals in JavaScript are `` `hello ${world}` ``. The `{field}` syntax
**Limitations**
The template system is intentionally simple:
+
- No conditionals (`{if cond}error{/if}`)
- No loops
- No custom formatters beyond `:upper`, `:lower`, `:json`
@@ -206,11 +208,11 @@ import { z } from 'zod';
const err = AppError({
field: 'email',
- message: computeMessage('email', context), // Custom logic
+ message: computeMessage('email', context), // Custom logic
});
```
## Related Features
- [error-function.md](./error-function.md) — Error definition with Standard Schema
-- [chaining.md](./chaining.md) — Chaining adds context to errors
\ No newline at end of file
+- [chaining.md](./chaining.md) — Chaining adds context to errors
diff --git a/docs/internal/product/features/notes.md b/docs/internal/product/features/notes.md
index 790a308..d24e865 100644
--- a/docs/internal/product/features/notes.md
+++ b/docs/internal/product/features/notes.md
@@ -15,9 +15,9 @@ interface ErrorInstance {
### Method Signature
-| Parameter | Type | Description |
-|-----------|------|-------------|
-| `note` | `string` | The note to add |
+| Parameter | Type | Description |
+| --------- | -------- | --------------- |
+| `note` | `string` | The note to add |
### Returns
@@ -35,9 +35,7 @@ const AppError = error({ name: 'AppError' });
try {
processData(input);
} catch (err) {
- raise(
- AppError().addNote('Processing failed')
- );
+ raise(AppError().addNote('Processing failed'));
}
```
@@ -51,12 +49,7 @@ const AppError = error({ name: 'AppError' });
try {
doSomething();
} catch (err) {
- raise(
- AppError()
- .addNote('Attempt 1 failed')
- .addNote('Retrying...')
- .addNote('Attempt 2 failed')
- );
+ raise(AppError().addNote('Attempt 1 failed').addNote('Retrying...').addNote('Attempt 2 failed'));
}
// err.notes === ['Attempt 1 failed', 'Retrying...', 'Attempt 2 failed']
@@ -88,9 +81,7 @@ const AppError = error({ name: 'AppError' });
try {
doSomething();
} catch (err) {
- raise(
- AppError().addNote(`User: ${err.context?.userId}`)
- );
+ raise(AppError().addNote(`User: ${err.context?.userId}`));
}
```
@@ -103,9 +94,7 @@ async function fetchUser(id: string) {
try {
return await getUser(id);
} catch (err) {
- raise(
- AppError().from(err).addNote(`Failed to fetch user ${id}`)
- );
+ raise(AppError().from(err).addNote(`Failed to fetch user ${id}`));
}
}
```
@@ -142,7 +131,7 @@ raise(AppError().addNote('note'));
const AppError = error({ name: 'AppError', notes: ['note'] });
```
-1. **Deferred enrichment** — Notes are often added *after* catching, not at creation
+1. **Deferred enrichment** — Notes are often added _after_ catching, not at creation
2. **Dynamic content** — Notes often depend on runtime context
3. **Chainability** — `err.addNote('a').addNote('b')` reads naturally
diff --git a/docs/internal/product/features/output-formatting.md b/docs/internal/product/features/output-formatting.md
index cb31093..2f0e605 100644
--- a/docs/internal/product/features/output-formatting.md
+++ b/docs/internal/product/features/output-formatting.md
@@ -8,10 +8,10 @@ The package automatically adapts error output based on the environment. Developm
The library detects the environment via `NODE_ENV`:
-| Environment | `NODE_ENV` | Output Style |
-|-------------|------------|--------------|
-| Development | `development` or unset | Pretty, colored |
-| Production | `production` | Compact, efficient |
+| Environment | `NODE_ENV` | Output Style |
+| ----------- | ---------------------- | ------------------ |
+| Development | `development` or unset | Pretty, colored |
+| Production | `production` | Compact, efficient |
## Development Mode
@@ -57,14 +57,14 @@ ValidationError: Field "email" expected email, got "user@" {"field":"email","exp
All these properties are available on any error instance:
```typescript
-err.name; // Error name
-err.message; // Formatted message
-err.fields; // User-defined fields
-err.notes; // Array of notes
-err.cause; // Direct cause
-err.causes; // Full cause chain
-err.context; // Injected context
-err.stack; // Stack trace
+err.name; // Error name
+err.message; // Formatted message
+err.fields; // User-defined fields
+err.notes; // Array of notes
+err.cause; // Direct cause
+err.causes; // Full cause chain
+err.context; // Injected context
+err.stack; // Stack trace
```
## JSON Serialization
@@ -81,9 +81,7 @@ const AppError = error({
},
});
-const err = AppError({ code: 42 })
- .addNote('User action')
- .addNote('Retry attempt 3');
+const err = AppError({ code: 42 }).addNote('User action').addNote('Retry attempt 3');
JSON.stringify(err);
// {
@@ -112,7 +110,7 @@ JSON.stringify(err, null, 2);
// Selective fields
JSON.stringify(err, (key, value) => {
- if (key === 'stack') return undefined; // Exclude stack
+ if (key === 'stack') return undefined; // Exclude stack
return value;
});
```
@@ -129,9 +127,9 @@ import { error, formatError } from '@deessejs/errors';
const err = AppError({ code: 42 });
// Get formatted string in specific mode
-formatError(err, { mode: 'development' }); // Pretty
-formatError(err, { mode: 'production' }); // Compact
-formatError(err, { mode: 'json' }); // JSON object
+formatError(err, { mode: 'development' }); // Pretty
+formatError(err, { mode: 'production' }); // Compact
+formatError(err, { mode: 'json' }); // JSON object
```
### Global Configuration (Optional)
@@ -140,12 +138,13 @@ formatError(err, { mode: 'json' }); // JSON object
import { setOutputMode } from '@deessejs/errors';
// Change default mode
-setOutputMode('development'); // Always pretty
-setOutputMode('production'); // Always compact
-setOutputMode('auto'); // Follow NODE_ENV (default)
+setOutputMode('development'); // Always pretty
+setOutputMode('production'); // Always compact
+setOutputMode('auto'); // Follow NODE_ENV (default)
```
**Warning:** `setOutputMode()` modifies global state. Be aware of:
+
- Test pollution: mode set in one test may affect another
- Serverless cold starts: mode persists across invocations
- Async: no thread safety concerns in Node.js
@@ -187,18 +186,21 @@ const logger = pino();
const err = catchError();
-logger.error({
- err: {
- name: err.name,
- message: err.message,
- stack: err.stack,
- fields: err.fields,
- notes: err.notes,
- cause: err.cause ? { name: err.cause.name, message: err.cause.message } : null,
+logger.error(
+ {
+ err: {
+ name: err.name,
+ message: err.message,
+ stack: err.stack,
+ fields: err.fields,
+ notes: err.notes,
+ cause: err.cause ? { name: err.cause.name, message: err.cause.message } : null,
+ },
+ userId: err.context?.userId,
+ requestId: err.context?.requestId,
},
- userId: err.context?.userId,
- requestId: err.context?.requestId,
-}, err.message);
+ err.message
+);
```
### Error Aggregation Services
@@ -230,6 +232,7 @@ datadogLogger.error(err.message, {
**Why auto-detect?**
Defaulting to development mode in development and production mode in production:
+
1. **Zero config** — Works out of the box
2. **Correct defaults** — Pretty for dev, efficient for prod
3. **Forgettable** — You don't think about it until needed
@@ -237,6 +240,7 @@ Defaulting to development mode in development and production mode in production:
**Why per-call formatting?**
Per-call `formatError(err, { mode })` is preferred over global `setOutputMode()` because:
+
- No test pollution
- Works in serverless environments
- Explicit intent in code
diff --git a/docs/internal/product/features/predefined-errors.md b/docs/internal/product/features/predefined-errors.md
index 9558358..77e66aa 100644
--- a/docs/internal/product/features/predefined-errors.md
+++ b/docs/internal/product/features/predefined-errors.md
@@ -16,21 +16,21 @@ errors.NotFoundError({ path: '/users/123' });
// Type guards (destructured for convenience)
const { isValidationError, isNotFoundError, isTypeError } = errors;
-isValidationError(err); // Type guard
+isValidationError(err); // Type guard
```
## Available Errors
### Core Errors
-| Export | Name | Fields | HTTP Status | Description |
-|--------|------|--------|-------------|-------------|
-| `errors.ValidationError` | `ValidationError` | `field: string, message: string` | 400 | Validation failure |
-| `errors.TypeError` | `TypeError` | `expected: string, actual: string` | 400 | Type mismatch |
-| `errors.NotFoundError` | `NotFoundError` | `path: string` | 404 | Resource not found |
-| `errors.TimeoutError` | `TimeoutError` | `ms: number` | 504 | Operation timed out |
-| `errors.UnauthorizedError` | `UnauthorizedError` | — | 401 | Authentication required |
-| `errors.ForbiddenError` | `ForbiddenError` | — | 403 | Permission denied |
+| Export | Name | Fields | HTTP Status | Description |
+| -------------------------- | ------------------- | ---------------------------------- | ----------- | ----------------------- |
+| `errors.ValidationError` | `ValidationError` | `field: string, message: string` | 400 | Validation failure |
+| `errors.TypeError` | `TypeError` | `expected: string, actual: string` | 400 | Type mismatch |
+| `errors.NotFoundError` | `NotFoundError` | `path: string` | 404 | Resource not found |
+| `errors.TimeoutError` | `TimeoutError` | `ms: number` | 504 | Operation timed out |
+| `errors.UnauthorizedError` | `UnauthorizedError` | — | 401 | Authentication required |
+| `errors.ForbiddenError` | `ForbiddenError` | — | 403 | Permission denied |
### Usage Examples
@@ -103,7 +103,7 @@ import { errors, error, is } from '@deessejs/errors';
// Create a custom error that inherits from ValidationError
const EmailValidationError = error({
name: 'EmailValidationError',
- inherits: errors.ValidationError, // Pass the factory function
+ inherits: errors.ValidationError, // Pass the factory function
fields: {
value: { type: 'string' },
},
@@ -142,11 +142,7 @@ import { errors, raise } from '@deessejs/errors';
try {
fetchData(url);
} catch (err) {
- raise(
- errors.TimeoutError({ ms: 5000 })
- .from(err)
- .addNote('Failed to fetch user data')
- );
+ raise(errors.TimeoutError({ ms: 5000 }).from(err).addNote('Failed to fetch user data'));
}
```
@@ -180,10 +176,8 @@ export const RateLimitError = error({
});
// Type guards
-export const isConfigError = (err: unknown): err is ConfigError =>
- is(err, ConfigError);
-export const isRateLimitError = (err: unknown): err is RateLimitError =>
- is(err, RateLimitError);
+export const isConfigError = (err: unknown): err is ConfigError => is(err, ConfigError);
+export const isRateLimitError = (err: unknown): err is RateLimitError => is(err, RateLimitError);
```
### Usage
@@ -221,16 +215,17 @@ Both work; destructuring is preferred for cleaner code.
**Why not prefix (`DeesseTypeError`)?**
Prefixes are verbose and harder to read. Namespace is cleaner:
+
- `errors.TypeError` vs `DeesseTypeError`
- Same collision protection, better ergonomics
**Why fields vary by error?**
-| Error | Fields | HTTP Status | Rationale |
-|-------|--------|-------------|-----------|
-| `NotFoundError` | `path` | 404 | The missing resource is key info |
-| `TimeoutError` | `ms` | 504 | How long we waited is useful |
-| `UnauthorizedError` | none | 401 | Why often varies by auth strategy |
+| Error | Fields | HTTP Status | Rationale |
+| ------------------- | ------ | ----------- | --------------------------------- |
+| `NotFoundError` | `path` | 404 | The missing resource is key info |
+| `TimeoutError` | `ms` | 504 | How long we waited is useful |
+| `UnauthorizedError` | none | 401 | Why often varies by auth strategy |
Error-specific fields capture the most useful information for that error type.
diff --git a/docs/internal/product/features/raise-function.md b/docs/internal/product/features/raise-function.md
index 7e73028..efe96a2 100644
--- a/docs/internal/product/features/raise-function.md
+++ b/docs/internal/product/features/raise-function.md
@@ -7,14 +7,14 @@ The `raise()` function is the primary mechanism for throwing errors in `@deessej
## API
```typescript
-function raise(errorInstance: ErrorInstance): never
+function raise(errorInstance: ErrorInstance): never;
```
### Parameters
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `errorInstance` | `ErrorInstance` | Yes | An error created by an error factory |
+| Parameter | Type | Required | Description |
+| --------------- | --------------- | -------- | ------------------------------------ |
+| `errorInstance` | `ErrorInstance` | Yes | An error created by an error factory |
### Returns
@@ -94,21 +94,21 @@ Use `raise()` in library code and internal application code for consistency. Use
## Comparison
-| Aspect | `raise()` | `throw` |
-|--------|-----------|---------|
-| Syntax | `raise(Error())` | `throw Error()` |
-| Middleware | Supported | Not supported |
-| Ecosystem | Library-specific | Universal |
-| IDE support | Good | Excellent |
+| Aspect | `raise()` | `throw` |
+| ----------- | ---------------- | --------------- |
+| Syntax | `raise(Error())` | `throw Error()` |
+| Middleware | Supported | Not supported |
+| Ecosystem | Library-specific | Universal |
+| IDE support | Good | Excellent |
## Comparison with Native JS
-| Aspect | Native JS | @deessejs/errors |
-|--------|----------|------------------|
-| Syntax | `throw new Error('msg')` | `raise(ErrorFactory())` or `throw ErrorFactory()` |
-| Custom errors | `class X extends Error` | `error({ name: 'X', fields: z.object(...) })` |
-| Chaining | `err.cause = cause` | `.from(cause)` |
-| Consistency | Mix of `throw` + built-ins | Both supported |
+| Aspect | Native JS | @deessejs/errors |
+| ------------- | -------------------------- | ------------------------------------------------- |
+| Syntax | `throw new Error('msg')` | `raise(ErrorFactory())` or `throw ErrorFactory()` |
+| Custom errors | `class X extends Error` | `error({ name: 'X', fields: z.object(...) })` |
+| Chaining | `err.cause = cause` | `.from(cause)` |
+| Consistency | Mix of `throw` + built-ins | Both supported |
## Design Rationale
@@ -117,6 +117,7 @@ See [Design Philosophy](../design-philosophy.md) for core principles.
**Why support both `raise()` and `throw`?**
Not forcing a paradigm shift reduces adoption friction:
+
- Start with familiar `throw` syntax
- Migrate to `raise()` when you need middleware support
- Mix both based on context
@@ -124,4 +125,4 @@ Not forcing a paradigm shift reduces adoption friction:
## Related Features
- [error-function.md](./error-function.md) — How to create errors
-- [chaining.md](./chaining.md) — Exception chaining with `.from()`
\ No newline at end of file
+- [chaining.md](./chaining.md) — Exception chaining with `.from()`
diff --git a/docs/internal/product/features/stack-cleaning.md b/docs/internal/product/features/stack-cleaning.md
index e11b6b9..0a15077 100644
--- a/docs/internal/product/features/stack-cleaning.md
+++ b/docs/internal/product/features/stack-cleaning.md
@@ -7,13 +7,13 @@ The `stripLibraryFrames()` function removes internal `@deessejs/errors` frames f
## API
```typescript
-function stripLibraryFrames(errorInstance: ErrorInstance): ErrorInstance
+function stripLibraryFrames(errorInstance: ErrorInstance): ErrorInstance;
```
### Parameters
-| Parameter | Type | Description |
-|-----------|------|-------------|
+| Parameter | Type | Description |
+| --------------- | --------------- | ------------------ |
| `errorInstance` | `ErrorInstance` | The error to clean |
### Returns
@@ -47,7 +47,7 @@ const AppError = error({ name: 'AppError' });
const err = stripLibraryFrames(AppError());
-err.stack; // Stack trace without @deessejs/errors frames
+err.stack; // Stack trace without @deessejs/errors frames
```
### In Defensive Wrappers
@@ -122,7 +122,7 @@ Stack traces are valuable but often include internal library frames that clutter
stripLibraryFrames(err);
// Not this
-sanitizeStack(err, ['lodash', 'express']); // Too aggressive
+sanitizeStack(err, ['lodash', 'express']); // Too aggressive
```
The library is opt-in; users explicitly choose to clean library frames. Removing all dependency frames would hide potentially useful debugging info.
diff --git a/docs/internal/product/features/type-guards.md b/docs/internal/product/features/type-guards.md
index 6402ef0..a6919d2 100644
--- a/docs/internal/product/features/type-guards.md
+++ b/docs/internal/product/features/type-guards.md
@@ -8,8 +8,8 @@ Type guards allow TypeScript to narrow error types within conditional blocks, en
```typescript
// Generated for each error type
-function isValidationError(err: unknown): err is ValidationError
-function isNotFoundError(err: unknown): err is NotFoundError
+function isValidationError(err: unknown): err is ValidationError;
+function isNotFoundError(err: unknown): err is NotFoundError;
// ... for all predefined and custom errors
```
@@ -25,7 +25,7 @@ try {
} catch (err) {
if (errors.isValidationError(err)) {
// TypeScript knows err is ValidationError here
- console.error(err.fields.field); // Safe access!
+ console.error(err.fields.field); // Safe access!
}
}
```
@@ -40,7 +40,7 @@ try {
} catch (err) {
if (is(err, errors.ValidationError)) {
// TypeScript doesn't narrow - err is still unknown
- console.error(err.fields.field); // Error! Can't access fields
+ console.error(err.fields.field); // Error! Can't access fields
}
}
```
@@ -66,7 +66,7 @@ try {
} catch (err) {
if (isValidationError(err)) {
// TypeScript narrows to ValidationError
- console.error(err.fields.field); // Works!
+ console.error(err.fields.field); // Works!
}
}
```
@@ -101,18 +101,19 @@ TypeScript doesn't automatically narrow types when functions return `boolean`. A
```typescript
// is() returns boolean - TypeScript doesn't narrow
if (is(err, ValidationError)) {
- err.fields; // Error: Property 'fields' does not exist on unknown
+ err.fields; // Error: Property 'fields' does not exist on unknown
}
// Type guard function narrows the type
if (isValidationError(err)) {
- err.fields; // Works!
+ err.fields; // Works!
}
```
**Naming convention**
Type guards follow the pattern `isXxxError` where `Xxx` is the error name:
+
- `isValidationError` for `ValidationError`
- `isNotFoundError` for `NotFoundError`
- `isNetworkError` for `NetworkError`
@@ -125,7 +126,7 @@ import { errors } from '@deessejs/errors';
// Predefined type guards
errors.isValidationError(err);
errors.isNotFoundError(err);
-errors.isTypeError(err); // Avoids collision with native TypeError
+errors.isTypeError(err); // Avoids collision with native TypeError
```
## Related Features
diff --git a/docs/internal/product/guides/migration.md b/docs/internal/product/guides/migration.md
index d480133..4f9dc11 100644
--- a/docs/internal/product/guides/migration.md
+++ b/docs/internal/product/guides/migration.md
@@ -7,6 +7,7 @@ This guide walks you through migrating from native JavaScript error handling to
### Basic Error Classes
**Before (Native JS)**
+
```typescript
class ValidationError extends Error {
constructor(field: string, message: string) {
@@ -21,6 +22,7 @@ throw new ValidationError('email', 'Invalid format');
```
**After (@deessejs/errors)**
+
```typescript
import { error, raise } from '@deessejs/errors';
@@ -41,6 +43,7 @@ throw ValidationError({ field: 'email', message: 'Invalid format' });
### Error Inheritance
**Before (Native JS)**
+
```typescript
class AppError extends Error {
constructor(message: string) {
@@ -59,6 +62,7 @@ class ValidationError extends AppError {
```
**After (@deessejs/errors)**
+
```typescript
import { error, raise } from '@deessejs/errors';
@@ -76,17 +80,19 @@ const ValidationError = error({
### Exception Chaining
**Before (Native JS)**
+
```typescript
try {
lowLevelOperation();
} catch (err) {
const newError = new HighLevelError('Failed');
- newError.cause = err; // Manual
+ newError.cause = err; // Manual
throw newError;
}
```
**After (@deessejs/errors)**
+
```typescript
try {
lowLevelOperation();
@@ -98,6 +104,7 @@ try {
### Catching Errors
**Before (Native JS)**
+
```typescript
try {
doSomething();
@@ -111,6 +118,7 @@ try {
```
**After (@deessejs/errors)**
+
```typescript
import { is, errors } from '@deessejs/errors';
const { isValidationError } = errors;
@@ -136,6 +144,7 @@ try {
### Pattern 1: Not Found
**Before**
+
```typescript
class NotFoundError extends Error {
constructor(resource: string, id: string) {
@@ -150,6 +159,7 @@ throw new NotFoundError('User', userId);
```
**After**
+
```typescript
import { errors, raise } from '@deessejs/errors';
@@ -168,6 +178,7 @@ raise(NotFoundError({ resource: 'User', id: userId }));
### Pattern 2: Validation
**Before**
+
```typescript
class ValidationError extends Error {
constructor(errors: Array<{ field: string; message: string }>) {
@@ -184,6 +195,7 @@ throw new ValidationError([
```
**After**
+
```typescript
import { errors, raise } from '@deessejs/errors';
@@ -199,17 +211,20 @@ const ValidationError = error({
},
});
-raise(ValidationError({
- errors: [
- { field: 'email', message: 'Invalid format' },
- { field: 'password', message: 'Too short' },
- ],
-}));
+raise(
+ ValidationError({
+ errors: [
+ { field: 'email', message: 'Invalid format' },
+ { field: 'password', message: 'Too short' },
+ ],
+ })
+);
```
### Pattern 3: Error with Context
**Before**
+
```typescript
async function handleRequest(req: Request) {
try {
@@ -224,6 +239,7 @@ async function handleRequest(req: Request) {
```
**After**
+
```typescript
import { errors, raise, withContext } from '@deessejs/errors';
@@ -239,6 +255,7 @@ async function handleRequest(req: Request) {
### Pattern 4: Third-Party Error Wrapping
**Before**
+
```typescript
try {
JSON.parse(input);
@@ -250,16 +267,19 @@ try {
```
**After**
+
```typescript
import { errors, raise } from '@deessejs/errors';
try {
JSON.parse(input);
} catch (err) {
- throw errors.ValidationError({
- field: 'input',
- message: 'Invalid JSON',
- }).from(err);
+ throw errors
+ .ValidationError({
+ field: 'input',
+ message: 'Invalid JSON',
+ })
+ .from(err);
}
```
@@ -300,10 +320,14 @@ export { errors };
### Step 3: Replace Error Classes
**Before**
+
```typescript
// src/errors.ts
export class ValidationError extends Error {
- constructor(public field: string, public message: string) {
+ constructor(
+ public field: string,
+ public message: string
+ ) {
super(message);
this.name = 'ValidationError';
}
@@ -311,6 +335,7 @@ export class ValidationError extends Error {
```
**After**
+
```typescript
// src/errors.ts
import { error } from '@deessejs/errors';
@@ -327,6 +352,7 @@ export const ValidationError = error({
### Step 4: Update Imports
**Before**
+
```typescript
import { ValidationError } from './errors';
@@ -334,6 +360,7 @@ throw new ValidationError('email', 'Invalid');
```
**After**
+
```typescript
import { ValidationError, raise } from './errors';
@@ -345,6 +372,7 @@ throw ValidationError({ field: 'email', message: 'Invalid' });
### Step 5: Update Catch Blocks
**Before**
+
```typescript
try {
doSomething();
@@ -358,6 +386,7 @@ try {
```
**After**
+
```typescript
import { is, errors, causes } from '@deessejs/errors';
const { isValidationError } = errors;
@@ -389,9 +418,11 @@ import { errors, raise } from '@deessejs/errors';
try {
await db.query(sql);
} catch (err) {
- throw errors.DatabaseError({
- message: 'Query failed',
- }).from(err);
+ throw errors
+ .DatabaseError({
+ message: 'Query failed',
+ })
+ .from(err);
}
```
@@ -414,6 +445,7 @@ if (isValidationError(err)) { ... }
### Custom Error Properties
**Before**
+
```typescript
class CustomError extends Error {
customProp: string;
@@ -427,6 +459,7 @@ err.customProp; // TypeScript knows this exists
```
**After**
+
```typescript
const CustomError = error({
name: 'CustomError',
diff --git a/docs/internal/product/guides/testing.md b/docs/internal/product/guides/testing.md
index 9ccb61d..6a0d894 100644
--- a/docs/internal/product/guides/testing.md
+++ b/docs/internal/product/guides/testing.md
@@ -16,7 +16,6 @@ npm install -D @deessejs/errors
// errors/index.ts
import { error } from '@deessejs/errors';
-
export const ValidationError = error({
name: 'ValidationError',
fields: { field: { type: 'string' } },
@@ -276,8 +275,7 @@ describe('fetchUser', () => {
it('has correct HTTP status', async () => {
try {
await fetchUser('nonexistent');
- } catch (err) {
- }
+ } catch (err) {}
});
});
```
@@ -297,12 +295,10 @@ describe('fetchUser', () => {
});
it('rejects with path field', async () => {
- await expect(fetchUser('123'))
- .rejects
- .toMatchObject({
- name: 'NotFoundError',
- fields: { path: '/users/123' },
- });
+ await expect(fetchUser('123')).rejects.toMatchObject({
+ name: 'NotFoundError',
+ fields: { path: '/users/123' },
+ });
});
});
```
@@ -326,14 +322,14 @@ describe('error chain', () => {
// Check chain
const chain = causes(caughtError);
expect(chain).toHaveLength(3);
- expect(chain[0].name).toBe('HighLevelError'); // Most recent
+ expect(chain[0].name).toBe('HighLevelError'); // Most recent
expect(chain[1].name).toBe('MidLevelError');
- expect(chain[2].name).toBe('LowLevelError'); // Original
+ expect(chain[2].name).toBe('LowLevelError'); // Original
});
it('first cause is most recent', () => {
const chain = causes(caughtError);
- expect(chain[0]).toBe(caughtError); // First is the caught error
+ expect(chain[0]).toBe(caughtError); // First is the caught error
});
});
```
@@ -348,8 +344,7 @@ import { formatError } from '@deessejs/errors';
describe('error formatting', () => {
it('produces consistent output', () => {
- const err = ValidationError({ field: 'email' })
- .addNote('Test note');
+ const err = ValidationError({ field: 'email' }).addNote('Test note');
const formatted = formatError(err, { mode: 'production' });
diff --git a/docs/internal/product/ideas/testing-utilities.md b/docs/internal/product/ideas/testing-utilities.md
index f96243d..4a7896f 100644
--- a/docs/internal/product/ideas/testing-utilities.md
+++ b/docs/internal/product/ideas/testing-utilities.md
@@ -69,9 +69,7 @@ expect(err).toHaveCauseChain(['HighError', 'MidError', 'LowError']);
import { errorSnapshot } from '@deessejs/errors/testing';
test('error formats correctly', () => {
- const err = ValidationError({ field: 'email' })
- .addNote('Test note')
- .from(NetworkError());
+ const err = ValidationError({ field: 'email' }).addNote('Test note').from(NetworkError());
// Returns a formatted string suitable for snapshots
expect(errorSnapshot(err)).toMatchSnapshot();
@@ -79,6 +77,7 @@ test('error formats correctly', () => {
```
Output:
+
```
ValidationError: Field "email" is invalid
field: "email"
@@ -128,8 +127,8 @@ test('aggregates failures correctly', async () => {
const errors = assertAllSettled(results);
// Returns typed errors array
- errors[0].name; // 'ValidationError'
- errors[0].fields.field; // 'email'
+ errors[0].name; // 'ValidationError'
+ errors[0].fields.field; // 'email'
// Can filter by type
const validationErrors = errors.ofType(ValidationError);
@@ -167,12 +166,12 @@ import { testTypeGuards } from '@deessejs/errors/testing';
// Ensure type guards work correctly
testTypeGuards({
- 'ValidationError': {
+ ValidationError: {
instance: ValidationError({ field: 'email' }),
shouldMatch: [EmailValidationError({ value: 'x' })],
shouldNotMatch: [NetworkError(), NotFoundError({ path: '/' })],
},
- 'NotFoundError': {
+ NotFoundError: {
instance: NotFoundError({ path: '/users' }),
shouldMatch: [UserNotFoundError({ userId: '123' })],
shouldNotMatch: [ValidationError({ field: 'x' })],
@@ -248,12 +247,10 @@ export function errorSnapshot(err: ErrorInstance): string {
```typescript
// async.ts
-export function assertAllSettled(
- results: PromiseSettledResult[]
-): ErrorInstance[] {
+export function assertAllSettled(results: PromiseSettledResult[]): ErrorInstance[] {
const errors = results
.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
- .map(r => r.reason as ErrorInstance);
+ .map((r) => r.reason as ErrorInstance);
if (errors.length === 0) {
throw new Error('No errors found in settled results');
@@ -263,26 +260,23 @@ export function assertAllSettled(
}
// Extend with type filtering
-export function filterByType(
- errors: ErrorInstance[],
- type: T
-): T[] {
- return errors.filter(err => is(err, type)) as T[];
+export function filterByType(errors: ErrorInstance[], type: T): T[] {
+ return errors.filter((err) => is(err, type)) as T[];
}
```
## Comparison with Alternatives
-| Feature | Vitest Built-in | @deessejs/errors/testing |
-|---------|-----------------|--------------------------|
-| toThrow | ✓ | ✓ (with full type narrowing) |
-| toHaveHttpStatus | ✗ | ✓ |
-| toHaveField | ✗ | ✓ |
-| toHaveCauseChain | ✗ | ✓ |
-| toHaveNote | ✗ | ✓ |
-| Context mocking | ✗ | ✓ |
-| Async aggregation | ✗ | ✓ |
-| Type guard testing | ✗ | ✓ |
+| Feature | Vitest Built-in | @deessejs/errors/testing |
+| ------------------ | --------------- | ---------------------------- |
+| toThrow | ✓ | ✓ (with full type narrowing) |
+| toHaveHttpStatus | ✗ | ✓ |
+| toHaveField | ✗ | ✓ |
+| toHaveCauseChain | ✗ | ✓ |
+| toHaveNote | ✗ | ✓ |
+| Context mocking | ✗ | ✓ |
+| Async aggregation | ✗ | ✓ |
+| Type guard testing | ✗ | ✓ |
## Related Features
@@ -300,4 +294,4 @@ export function filterByType(
- Consider whether to ship this as part of the main package or as `@deessejs/errors/testing`
- Needs compatibility with both Jest and Vitest
-- Consider adding `expect().toThrowLike(ErrorFactory)` for more flexible matching
\ No newline at end of file
+- Consider adding `expect().toThrowLike(ErrorFactory)` for more flexible matching
diff --git a/docs/internal/releases/README.md b/docs/internal/releases/README.md
index 575b666..c367453 100644
--- a/docs/internal/releases/README.md
+++ b/docs/internal/releases/README.md
@@ -4,17 +4,18 @@ This document summarizes the release strategy for `@deessejs/errors` and provide
## Version Timeline
-| Version | Focus | Features | Target |
-|---------|-------|----------|--------|
-| **v1.0.0** | Core Foundation | error(), raise(), is(), inherits, .from(), causes, message formatting | MVP |
-| **v1.1.0** | Enhanced DX | .addNote(), context property documentation | After v1.0.0 |
-| **v1.2.0** | Type Safety | Type guards, predefined errors, errors namespace | After v1.1.0 |
-| **v1.3.0** | Production Ready
-| **v2.0.0** | Advanced Context | withContext(), async patterns, AsyncLocalStorage | After v1.3.0 |
+| Version | Focus | Features | Target |
+| ---------- | ---------------- | --------------------------------------------------------------------- | ------------ |
+| **v1.0.0** | Core Foundation | error(), raise(), is(), inherits, .from(), causes, message formatting | MVP |
+| **v1.1.0** | Enhanced DX | .addNote(), context property documentation | After v1.0.0 |
+| **v1.2.0** | Type Safety | Type guards, predefined errors, errors namespace | After v1.1.0 |
+| **v1.3.0** | Production Ready |
+| **v2.0.0** | Advanced Context | withContext(), async patterns, AsyncLocalStorage | After v1.3.0 |
## Feature Coverage by Version
### v1.0.0 — Core Foundation
+
- [x] `error()` function for defining error types
- [x] `raise()` function for throwing errors
- [x] Native `throw` syntax support
@@ -26,16 +27,19 @@ This document summarizes the release strategy for `@deessejs/errors` and provide
- [x] Message templates with `{field}` placeholders
### v1.1.0 — Enhanced Developer Experience
+
- [x] `.addNote()` method for enriching errors with notes
- [x] Multiple notes support with order preservation
- [x] Notes display in development and production output
### v1.2.0 — Type Safety & Utilities
+
- [x] `errors` namespace with predefined error types
- [x] ValidationError, TypeError, NotFoundError, TimeoutError, UnauthorizedError, ForbiddenError
- [x] Type guards: `isValidationError()`, `isTypeError()`, etc.
### v1.3.0 — Production Ready
+
- [x] `formatError()` function for per-call formatting
- [x] `setOutputMode()` for global configuration
- [x] `stripLibraryFrames()` for cleaning stack traces
@@ -43,6 +47,7 @@ This document summarizes the release strategy for `@deessejs/errors` and provide
- [x] JSON serialization
### v2.0.0 — Advanced Context
+
- [x] `withContext()` for AsyncLocalStorage-based context injection
- [x] Nested context support with automatic merging
- [x] `errors.supportsContext()` for compatibility detection
@@ -74,6 +79,7 @@ v1.0.0 (Foundation)
### For v1.0.0 (MVP)
Additional requirements:
+
- [ ] Core API is stable (no planned breaking changes)
- [ ] Basic migration path from native JS errors documented
- [ ] Common patterns covered
@@ -81,6 +87,7 @@ Additional requirements:
### For v2.0.0 (Major)
Additional requirements:
+
- [ ] Migration guide for breaking changes
- [ ] Compatibility matrix documented
- [ ] Graceful degradation tested
@@ -135,4 +142,4 @@ type Callback = (err: T) => void;
- [v1.3.0 Production Ready](./v1.3.0-production-ready/README.md)
- [v2.0.0 Advanced Context](./v2.0.0-advanced-context/README.md)
- [Design Philosophy](../product/design-philosophy.md)
-- [Product README](../product/README.md)
\ No newline at end of file
+- [Product README](../product/README.md)
diff --git a/docs/internal/releases/v1.0.0-core-foundation/README.md b/docs/internal/releases/v1.0.0-core-foundation/README.md
index 8ba6386..59a730f 100644
--- a/docs/internal/releases/v1.0.0-core-foundation/README.md
+++ b/docs/internal/releases/v1.0.0-core-foundation/README.md
@@ -38,16 +38,16 @@ raise(ValidationError({ field: 'email' }));
Every error instance has these guaranteed properties:
-| Property | Type | Always Exists | Description |
-|----------|------|---------------|-------------|
-| `name` | `string` | Yes | Error name |
-| `message` | `string` | Yes | Error message |
-| `stack` | `string` | Yes | Stack trace |
-| `fields` | `Record` | Yes | User-defined fields (empty object if none) |
-| `notes` | `string[]` | Yes | Notes (empty array if none) |
-| `cause` | `Error \| null` | Yes | Direct cause (null if none) |
-| `causes` | `Error[]` | Yes | Full cause chain (may be empty) |
-| `context` | `Record \| null` | Yes | Injected context (null if none) |
+| Property | Type | Always Exists | Description |
+| --------- | --------------------------------- | ------------- | ------------------------------------------ |
+| `name` | `string` | Yes | Error name |
+| `message` | `string` | Yes | Error message |
+| `stack` | `string` | Yes | Stack trace |
+| `fields` | `Record` | Yes | User-defined fields (empty object if none) |
+| `notes` | `string[]` | Yes | Notes (empty array if none) |
+| `cause` | `Error \| null` | Yes | Direct cause (null if none) |
+| `causes` | `Error[]` | Yes | Full cause chain (may be empty) |
+| `context` | `Record \| null` | Yes | Injected context (null if none) |
### Exception Chaining: `.from()`
@@ -227,7 +227,7 @@ export type ErrorFactory = Record = Record> = {
name: string;
- fields?: StandardSchemaV1; // Standard Schema only
+ fields?: StandardSchemaV1; // Standard Schema only
inherits?: ErrorFactory | ErrorFactory[];
message?: string;
};
@@ -258,6 +258,7 @@ None — this is a standalone library.
## v1.0.0 — Core Foundation (YYYY-MM-DD)
### Added
+
- `error()` function for defining error types
- `raise()` function for throwing errors
- Native `throw` syntax support
@@ -278,4 +279,4 @@ None — this is a standalone library.
- [Chaining](../product/features/chaining.md)
- [Inheritance](../product/features/inheritance.md)
- [Chain Traversal](../product/features/chain-traversal.md)
-- [Message Formatting](../product/features/message-formatting.md)
\ No newline at end of file
+- [Message Formatting](../product/features/message-formatting.md)
diff --git a/docs/internal/releases/v1.1.0-enhanced-dx/README.md b/docs/internal/releases/v1.1.0-enhanced-dx/README.md
index e8ae306..84d46b2 100644
--- a/docs/internal/releases/v1.1.0-enhanced-dx/README.md
+++ b/docs/internal/releases/v1.1.0-enhanced-dx/README.md
@@ -26,23 +26,14 @@ const AppError = error({ name: 'AppError' });
try {
processData(input);
} catch (err) {
- raise(
- AppError().addNote('Processing failed at line 42')
- );
+ raise(AppError().addNote('Processing failed at line 42'));
}
// Multiple notes supported
-raise(
- AppError()
- .addNote('Attempt 1 failed')
- .addNote('Retrying...')
- .addNote('Attempt 2 failed')
-);
+raise(AppError().addNote('Attempt 1 failed').addNote('Retrying...').addNote('Attempt 2 failed'));
// Notes are preserved through chaining
-raise(
- AppError().from(err).addNote('Wrapped with context')
-);
+raise(AppError().from(err).addNote('Wrapped with context'));
```
### Notes in Output
@@ -138,12 +129,14 @@ No migration required — this is purely additive. Existing code continues to wo
## v1.1.0 — Enhanced Developer Experience (YYYY-MM-DD)
### Added
+
- `.addNote()` method for enriching errors with notes
- Multiple notes support with order preservation
- Notes display in development and production output
- Enhanced documentation for `context` property
### Changed
+
- ErrorInstance type documentation improved
```
@@ -152,4 +145,4 @@ No migration required — this is purely additive. Existing code continues to wo
- [Notes Feature](../product/features/notes.md)
- [Chaining Feature](../product/features/chaining.md)
- [Context Injection Feature](../product/features/context-injection.md)
-- [Output Formatting Feature](../product/features/output-formatting.md)
\ No newline at end of file
+- [Output Formatting Feature](../product/features/output-formatting.md)
diff --git a/docs/internal/releases/v1.2.0-type-safety/README.md b/docs/internal/releases/v1.2.0-type-safety/README.md
index ac3920e..087baaf 100644
--- a/docs/internal/releases/v1.2.0-type-safety/README.md
+++ b/docs/internal/releases/v1.2.0-type-safety/README.md
@@ -11,6 +11,7 @@ Target: TBD (after v1.1.0)
## Motivation
While v1.0.0 provides the core API, users need:
+
1. **Type guards** — TypeScript can't narrow types from functions returning `boolean`
2. **Predefined errors** — Common errors shouldn't require definition
3. **Namespace** — Avoid collisions with native JavaScript `TypeError`
@@ -63,14 +64,14 @@ errors.ForbiddenError();
### Complete Predefined Error List
-| Export | Name | Fields | HTTP Status | Description |
-|--------|------|--------|-------------|-------------|
-| `errors.ValidationError` | `ValidationError` | `field: string, message: string` | 400 | Validation failure |
-| `errors.TypeError` | `TypeError` | `expected: string, actual: string` | 400 | Type mismatch |
-| `errors.NotFoundError` | `NotFoundError` | `path: string` | 404 | Resource not found |
-| `errors.TimeoutError` | `TimeoutError` | `ms: number` | 504 | Operation timed out |
-| `errors.UnauthorizedError` | `UnauthorizedError` | — | 401 | Authentication required |
-| `errors.ForbiddenError` | `ForbiddenError` | — | 403 | Permission denied |
+| Export | Name | Fields | HTTP Status | Description |
+| -------------------------- | ------------------- | ---------------------------------- | ----------- | ----------------------- |
+| `errors.ValidationError` | `ValidationError` | `field: string, message: string` | 400 | Validation failure |
+| `errors.TypeError` | `TypeError` | `expected: string, actual: string` | 400 | Type mismatch |
+| `errors.NotFoundError` | `NotFoundError` | `path: string` | 404 | Resource not found |
+| `errors.TimeoutError` | `TimeoutError` | `ms: number` | 504 | Operation timed out |
+| `errors.UnauthorizedError` | `UnauthorizedError` | — | 401 | Authentication required |
+| `errors.ForbiddenError` | `ForbiddenError` | — | 403 | Permission denied |
### Namespace Pattern
@@ -177,6 +178,7 @@ No migration required — this is purely additive. Existing code continues to wo
## v1.2.0 — Type Safety & Utilities (YYYY-MM-DD)
### Added
+
- `errors` namespace with predefined error types
- `errors.ValidationError` with `field` and `message` fields
- `errors.TypeError` (namespaced to avoid native collision)
@@ -188,6 +190,7 @@ No migration required — this is purely additive. Existing code continues to wo
- Inheritance support for predefined errors
### Changed
+
- Enhanced `is()` function documentation
```
@@ -196,4 +199,4 @@ No migration required — this is purely additive. Existing code continues to wo
- [Type Guards Feature](../product/features/type-guards.md)
- [Predefined Errors Feature](../product/features/predefined-errors.md)
- [is() Function Feature](../product/features/is-function.md)
-- [Inheritance Feature](../product/features/inheritance.md)
\ No newline at end of file
+- [Inheritance Feature](../product/features/inheritance.md)
diff --git a/docs/internal/releases/v1.3.0-production-ready/README.md b/docs/internal/releases/v1.3.0-production-ready/README.md
index 60d3577..79b33d9 100644
--- a/docs/internal/releases/v1.3.0-production-ready/README.md
+++ b/docs/internal/releases/v1.3.0-production-ready/README.md
@@ -11,6 +11,7 @@ Target: TBD (after v1.2.0)
## Motivation
For production use, errors need to:
+
1. Output appropriately for different environments (pretty in dev, compact in prod)
2. Provide clean, actionable stack traces
@@ -20,10 +21,10 @@ For production use, errors need to:
The library automatically adapts output based on `NODE_ENV`:
-| Environment | `NODE_ENV` | Output Style |
-|-------------|------------|--------------|
-| Development | `development` or unset | Pretty, colored |
-| Production | `production` | Compact, efficient |
+| Environment | `NODE_ENV` | Output Style |
+| ----------- | ---------------------- | ------------------ |
+| Development | `development` or unset | Pretty, colored |
+| Production | `production` | Compact, efficient |
#### Development Mode (Pretty)
@@ -38,6 +39,7 @@ The library automatically adapts output based on `NODE_ENV`:
```
Features:
+
- Color coding (red for errors, yellow for notes, gray for fields)
- Tree structure for nested data
- Full context visible
@@ -49,6 +51,7 @@ ValidationError: Field "email" expected email, got "user@" {"field":"email","not
```
Features:
+
- Single line for log systems
- JSON suffix for fields
- Minimal colors
@@ -63,8 +66,8 @@ import { formatError } from '@deessejs/errors';
const err = AppError({ code: 42 });
formatError(err, { mode: 'development' }); // Pretty string
-formatError(err, { mode: 'production' }); // Compact string
-formatError(err, { mode: 'json' }); // JSON object
+formatError(err, { mode: 'production' }); // Compact string
+formatError(err, { mode: 'json' }); // JSON object
```
### setOutputMode() (Optional Global Configuration)
@@ -73,9 +76,9 @@ formatError(err, { mode: 'json' }); // JSON object
import { setOutputMode } from '@deessejs/errors';
// Change default mode
-setOutputMode('development'); // Always pretty
-setOutputMode('production'); // Always compact
-setOutputMode('auto'); // Follow NODE_ENV (default)
+setOutputMode('development'); // Always pretty
+setOutputMode('production'); // Always compact
+setOutputMode('auto'); // Follow NODE_ENV (default)
```
**Warning:** `setOutputMode()` modifies global state. Use per-call `formatError()` in tests to avoid pollution.
@@ -119,8 +122,7 @@ Error: AppError
All errors serialize to JSON automatically:
```typescript
-const err = AppError({ code: 42 })
- .addNote('User action');
+const err = AppError({ code: 42 }).addNote('User action');
JSON.stringify(err);
// {
@@ -146,7 +148,7 @@ JSON.stringify(err, null, 2);
// Custom filter
JSON.stringify(err, (key, value) => {
- if (key === 'stack') return undefined; // Exclude stack
+ if (key === 'stack') return undefined; // Exclude stack
return value;
});
```
@@ -168,9 +170,7 @@ export const formatError: = Record string | object;
-export const setOutputMode: (
- mode: 'development' | 'production' | 'auto'
-) => void;
+export const setOutputMode: (mode: 'development' | 'production' | 'auto') => void;
export const stripLibraryFrames: >(
errorInstance: ErrorInstance
@@ -197,6 +197,7 @@ No migration required — this is purely additive. Existing code continues to wo
## v1.3.0 — Production Ready (YYYY-MM-DD)
### Added
+
- Automatic environment detection (dev vs prod)
- Pretty colored output in development mode
- Compact single-line output in production mode
@@ -206,10 +207,11 @@ No migration required — this is purely additive. Existing code continues to wo
- JSON serialization with `JSON.stringify()`
### Changed
+
- Enhanced output formatting docs
```
## Related Documents
- [Output Formatting Feature](../product/features/output-formatting.md)
-- [Stack Cleaning Feature](../product/features/stack-cleaning.md)
\ No newline at end of file
+- [Stack Cleaning Feature](../product/features/stack-cleaning.md)
diff --git a/docs/internal/releases/v2.0.0-advanced-context/README.md b/docs/internal/releases/v2.0.0-advanced-context/README.md
index 14e17a6..b1e4011 100644
--- a/docs/internal/releases/v2.0.0-advanced-context/README.md
+++ b/docs/internal/releases/v2.0.0-advanced-context/README.md
@@ -27,13 +27,10 @@ This is a **major version** release with potential breaking changes:
import { withContext, raise } from '@deessejs/errors';
async function handleRequest(req: Request) {
- return withContext(
- { requestId: req.id, userId: req.userId },
- async () => {
- const user = await fetchUser(req.userId);
- return processUser(user);
- }
- );
+ return withContext({ requestId: req.id, userId: req.userId }, async () => {
+ const user = await fetchUser(req.userId);
+ return processUser(user);
+ });
// Any error raised here automatically has context
}
```
@@ -87,18 +84,18 @@ if (errors.supportsContext()) {
### Runtime Compatibility
-| Environment | Support | Notes |
-|-------------|---------|-------|
-| Node.js 16+ | Full | Native AsyncLocalStorage |
-| Node.js 12-14 | Polyfill | Requires manual AsyncLocalStorage polyfill |
-| Deno | Full | AsyncLocalStorage available |
-| Bun | Full | AsyncLocalStorage available |
-| Browsers | Partial | Works in modern browsers, not IE11 |
-| Next.js | Full | Server-side rendering supported |
-| Express/Koa | Full | Works in request handlers |
-| Cloudflare Workers | Limited | No AsyncLocalStorage |
-| AWS Lambda | Varies | Depends on runtime |
-| Vercel Edge | Limited | No AsyncLocalStorage |
+| Environment | Support | Notes |
+| ------------------ | -------- | ------------------------------------------ |
+| Node.js 16+ | Full | Native AsyncLocalStorage |
+| Node.js 12-14 | Polyfill | Requires manual AsyncLocalStorage polyfill |
+| Deno | Full | AsyncLocalStorage available |
+| Bun | Full | AsyncLocalStorage available |
+| Browsers | Partial | Works in modern browsers, not IE11 |
+| Next.js | Full | Server-side rendering supported |
+| Express/Koa | Full | Works in request handlers |
+| Cloudflare Workers | Limited | No AsyncLocalStorage |
+| AWS Lambda | Varies | Depends on runtime |
+| Vercel Edge | Limited | No AsyncLocalStorage |
### Error Handling Best Practices
@@ -107,6 +104,7 @@ See [Async Support Feature](../product/features/async-support.md) for detailed p
### Graceful Degradation
In unsupported environments:
+
1. `withContext()` runs the function normally
2. No error is thrown
3. `err.context` will be `null`
@@ -125,10 +123,7 @@ This release does not include:
### New Exports
```typescript
-export const withContext: (
- context: Record,
- fn: () => T
-) => T;
+export const withContext: (context: Record, fn: () => T) => T;
export const errors: {
// ... from v1.x ...
@@ -157,12 +152,14 @@ err.context; // Record | null
### From v1.x
1. Update import if using `withContext()`:
+
```typescript
// No import changes needed
import { withContext } from '@deessejs/errors';
```
2. Check for compatibility if critical:
+
```typescript
import { errors } from '@deessejs/errors';
@@ -180,9 +177,9 @@ err.context; // Record | null
### Breaking Changes Summary
-| Change | Type | Mitigation |
-|--------|------|------------|
-| `context` may now be populated | Behavioral | Check `err.context ?? {}` |
+| Change | Type | Mitigation |
+| -------------------------------------------------- | ---------- | ------------------------------ |
+| `context` may now be populated | Behavioral | Check `err.context ?? {}` |
| `withContext()` silently fails in unsupported envs | Behavioral | Use `errors.supportsContext()` |
## Testing Requirements
@@ -203,20 +200,24 @@ err.context; // Record | null
## v2.0.0 — Advanced Context (YYYY-MM-DD)
### Added
+
- `withContext()` for AsyncLocalStorage-based context injection
- Nested context support with automatic merging
- `errors.supportsContext()` for compatibility detection
- Context propagation through async call stacks
### Changed
+
- ErrorInstance.context is now populated in supported environments
- Error fields override context values (breaking)
### Breaking Changes
+
- Context behavior may differ in unsupported environments
- See migration guide for details
### Removed
+
- (None)
```
@@ -224,4 +225,4 @@ err.context; // Record | null
- [Context Injection Feature](../product/features/context-injection.md)
- [Async Support Feature](../product/features/async-support.md)
-- [Testing Guide](../product/guides/testing.md) (updated for async patterns)
\ No newline at end of file
+- [Testing Guide](../product/guides/testing.md) (updated for async patterns)
diff --git a/docs/internal/tasks/implementation/README.md b/docs/internal/tasks/implementation/README.md
index a475979..7bc7719 100644
--- a/docs/internal/tasks/implementation/README.md
+++ b/docs/internal/tasks/implementation/README.md
@@ -6,16 +6,16 @@ This folder contains all implementation tasks for v1.0.0 Core Foundation.
## Task List
-| ID | Task | Status |
-|----|------|--------|
-| 01 | error() factory | ✅ Complete |
-| 02 | raise() function | ✅ Complete |
-| 03 | is() function | ✅ Complete |
-| 04 | inherits option | ✅ Complete |
-| 05 | .from() method | ✅ Complete |
-| 06 | causes() function | ✅ Complete |
-| 07 | message templates | ✅ Complete |
-| 08 | ErrorInstance properties | ✅ Complete |
+| ID | Task | Status |
+| --- | ------------------------ | ----------- |
+| 01 | error() factory | ✅ Complete |
+| 02 | raise() function | ✅ Complete |
+| 03 | is() function | ✅ Complete |
+| 04 | inherits option | ✅ Complete |
+| 05 | .from() method | ✅ Complete |
+| 06 | causes() function | ✅ Complete |
+| 07 | message templates | ✅ Complete |
+| 08 | ErrorInstance properties | ✅ Complete |
## Execution Order
@@ -42,4 +42,4 @@ export { is } from './is';
export { causes } from './causes';
export type { ErrorInstance, ErrorFactory, ErrorConfig } from './types';
-```
\ No newline at end of file
+```
diff --git a/docs/internal/tasks/implementation/task-01-error-factory.md b/docs/internal/tasks/implementation/task-01-error-factory.md
index d7889aa..20db392 100644
--- a/docs/internal/tasks/implementation/task-01-error-factory.md
+++ b/docs/internal/tasks/implementation/task-01-error-factory.md
@@ -60,11 +60,11 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
function error = Record>(
config: ErrorConfig
-): ErrorFactory
+): ErrorFactory;
type ErrorConfig = {
name: string;
- fields?: StandardSchemaV1; // Standard Schema only
+ fields?: StandardSchemaV1; // Standard Schema only
inherits?: ErrorFactory | ErrorFactory[];
message?: string;
};
@@ -93,4 +93,4 @@ None
## Notes
-See [error-function.md](../../product/features/error-function.md) for design rationale.
\ No newline at end of file
+See [error-function.md](../../product/features/error-function.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-02-raise-function.md b/docs/internal/tasks/implementation/task-02-raise-function.md
index c170c02..34af6a3 100644
--- a/docs/internal/tasks/implementation/task-02-raise-function.md
+++ b/docs/internal/tasks/implementation/task-02-raise-function.md
@@ -17,7 +17,7 @@ Implement `raise()` function that throws errors. Must also support native `throw
## API
```typescript
-function raise(errorInstance: ErrorInstance): never
+function raise(errorInstance: ErrorInstance): never;
```
## Acceptance Criteria
@@ -39,4 +39,4 @@ function raise(errorInstance: ErrorInstance): never
## Notes
-See [raise-function.md](../../product/features/raise-function.md) for design rationale.
\ No newline at end of file
+See [raise-function.md](../../product/features/raise-function.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-03-is-function.md b/docs/internal/tasks/implementation/task-03-is-function.md
index bb2fd62..4473720 100644
--- a/docs/internal/tasks/implementation/task-03-is-function.md
+++ b/docs/internal/tasks/implementation/task-03-is-function.md
@@ -18,7 +18,7 @@ Implement `is(err, ErrorType)` function for type checking. Must work with inheri
## API
```typescript
-function is(err: unknown, ErrorType: T): boolean
+function is(err: unknown, ErrorType: T): boolean;
```
## Acceptance Criteria
@@ -41,4 +41,4 @@ function is(err: unknown, ErrorType: T): boolean
## Notes
-See [is-function.md](../../product/features/is-function.md) for design rationale.
\ No newline at end of file
+See [is-function.md](../../product/features/is-function.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-04-inherits-option.md b/docs/internal/tasks/implementation/task-04-inherits-option.md
index 00d497a..51d7cad 100644
--- a/docs/internal/tasks/implementation/task-04-inherits-option.md
+++ b/docs/internal/tasks/implementation/task-04-inherits-option.md
@@ -11,6 +11,7 @@ Add inheritance support to error() config. Support both single parent and multip
## Implementation
Implemented in:
+
- `src/error/error.ts` - Factory creation with inherits support
- `src/error/types.ts` - ErrorFactory type with inherits property
- `src/is/index.ts` - DFS traversal for inheritance chain checking
@@ -58,4 +59,4 @@ const CombinedError = error({
## Notes
-See [inheritance.md](../../product/features/inheritance.md) for design rationale.
\ No newline at end of file
+See [inheritance.md](../../product/features/inheritance.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-05-from-method.md b/docs/internal/tasks/implementation/task-05-from-method.md
index f0e9af2..0c59fc0 100644
--- a/docs/internal/tasks/implementation/task-05-from-method.md
+++ b/docs/internal/tasks/implementation/task-05-from-method.md
@@ -43,4 +43,4 @@ interface ErrorInstance {
## Notes
-See [chaining.md](../../product/features/chaining.md) for design rationale.
\ No newline at end of file
+See [chaining.md](../../product/features/chaining.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-06-causes-function.md b/docs/internal/tasks/implementation/task-06-causes-function.md
index 821a194..df7945e 100644
--- a/docs/internal/tasks/implementation/task-06-causes-function.md
+++ b/docs/internal/tasks/implementation/task-06-causes-function.md
@@ -11,6 +11,7 @@ Implement causes(err) function that returns array of all errors in the cause cha
## Implementation
Implemented in:
+
- `src/causes/index.ts` - causes() function
- `src/index.ts` - exported for public API
@@ -50,4 +51,4 @@ err.causes; // ErrorInstance[]
## Notes
-See [chain-traversal.md](../../product/features/chain-traversal.md) for design rationale.
\ No newline at end of file
+See [chain-traversal.md](../../product/features/chain-traversal.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-07-message-templates.md b/docs/internal/tasks/implementation/task-07-message-templates.md
index 8840a2d..cdbf2ab 100644
--- a/docs/internal/tasks/implementation/task-07-message-templates.md
+++ b/docs/internal/tasks/implementation/task-07-message-templates.md
@@ -11,6 +11,7 @@ Implement message template parsing with {field} placeholders and modifiers.
## Implementation
Implemented in:
+
- `src/error/format.ts` - formatTemplate and hasTemplatePlaceholders functions
- Integrated into `src/error/error.ts` during error factory invocation
@@ -54,4 +55,4 @@ err.message; // 'Field "email" is invalid'
## Notes
-See [message-formatting.md](../../product/features/message-formatting.md) for design rationale.
\ No newline at end of file
+See [message-formatting.md](../../product/features/message-formatting.md) for design rationale.
diff --git a/docs/internal/tasks/implementation/task-08-instance-properties.md b/docs/internal/tasks/implementation/task-08-instance-properties.md
index 2e62a65..9014faa 100644
--- a/docs/internal/tasks/implementation/task-08-instance-properties.md
+++ b/docs/internal/tasks/implementation/task-08-instance-properties.md
@@ -16,19 +16,18 @@ Implemented in `src/error/error.ts` - all properties are initialized with defaul
Every error instance has these properties, all with defined values:
-| Property | Type | Default if not specified | Status |
-|----------|------|---------------------------|--------|
-| name | string | ✓ (required) | ✅ |
-| message | string | ✓ (required) | ✅ |
-| stack | string | ✓ (auto-generated) | ✅ |
-| fields | Record | {} | ✅ |
-| notes | string[] | [] | ✅ |
-| cause | Error \| null | null | ✅ |
-| causes | Error[] | [] | ✅ |
-| context | Record \| null | null | ✅ |
-| inherits | ErrorFactory \| ErrorFactory[] \| undefined | undefined | ✅ |
-| from() | method | ✓ | ✅ |
-
+| Property | Type | Default if not specified | Status |
+| -------- | ------------------------------------------- | ------------------------ | ------ |
+| name | string | ✓ (required) | ✅ |
+| message | string | ✓ (required) | ✅ |
+| stack | string | ✓ (auto-generated) | ✅ |
+| fields | Record | {} | ✅ |
+| notes | string[] | [] | ✅ |
+| cause | Error \| null | null | ✅ |
+| causes | Error[] | [] | ✅ |
+| context | Record \| null | null | ✅ |
+| inherits | ErrorFactory \| ErrorFactory[] \| undefined | undefined | ✅ |
+| from() | method | ✓ | ✅ |
## Acceptance Criteria
@@ -47,4 +46,4 @@ Every error instance has these properties, all with defined values:
## Notes
-See [error-function.md](../../product/features/error-function.md) for full property list.
\ No newline at end of file
+See [error-function.md](../../product/features/error-function.md) for full property list.
diff --git a/docs/internal/tasks/testing/README.md b/docs/internal/tasks/testing/README.md
index 681c8be..16892f9 100644
--- a/docs/internal/tasks/testing/README.md
+++ b/docs/internal/tasks/testing/README.md
@@ -6,16 +6,16 @@ This folder contains all testing-related tasks for v1.0.0 Core Foundation.
## Task List
-| ID | Task | Status |
-|----|------|--------|
-| 09 | Unit tests for error() factory | ✅ Complete |
-| 10 | Unit tests for raise() | ✅ Complete |
-| 11 | Unit tests for is() | ✅ Complete |
-| 12 | Unit tests for .from() | ✅ Complete |
-| 13 | Unit tests for causes() | ✅ Complete |
-| 14 | Unit tests for messages | ✅ Complete |
-| 15 | Type tests for TypeScript | ✅ Complete |
-| 18 | Integration tests | ✅ Complete |
+| ID | Task | Status |
+| --- | ------------------------------ | ----------- |
+| 09 | Unit tests for error() factory | ✅ Complete |
+| 10 | Unit tests for raise() | ✅ Complete |
+| 11 | Unit tests for is() | ✅ Complete |
+| 12 | Unit tests for .from() | ✅ Complete |
+| 13 | Unit tests for causes() | ✅ Complete |
+| 14 | Unit tests for messages | ✅ Complete |
+| 15 | Type tests for TypeScript | ✅ Complete |
+| 18 | Integration tests | ✅ Complete |
## Test Framework
@@ -52,4 +52,4 @@ src/
integration.test.ts
types/
error.test-d.ts # Type tests
-```
\ No newline at end of file
+```
diff --git a/docs/internal/tasks/testing/task-09-unit-tests-error-factory.md b/docs/internal/tasks/testing/task-09-unit-tests-error-factory.md
index e162d6b..41d1716 100644
--- a/docs/internal/tasks/testing/task-09-unit-tests-error-factory.md
+++ b/docs/internal/tasks/testing/task-09-unit-tests-error-factory.md
@@ -70,4 +70,4 @@ expect(msgErr.message).toBe('Value: test');
## Notes
-Use Vitest for testing. See project test setup in `vitest.config.ts`.
\ No newline at end of file
+Use Vitest for testing. See project test setup in `vitest.config.ts`.
diff --git a/docs/internal/tasks/testing/task-10-unit-tests-raise.md b/docs/internal/tasks/testing/task-10-unit-tests-raise.md
index 4c63b54..0516f9c 100644
--- a/docs/internal/tasks/testing/task-10-unit-tests-raise.md
+++ b/docs/internal/tasks/testing/task-10-unit-tests-raise.md
@@ -51,4 +51,4 @@ expect(() => raise(err)).toThrow();
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/testing/task-11-unit-tests-is.md b/docs/internal/tasks/testing/task-11-unit-tests-is.md
index ebf2d12..98d41cb 100644
--- a/docs/internal/tasks/testing/task-11-unit-tests-is.md
+++ b/docs/internal/tasks/testing/task-11-unit-tests-is.md
@@ -59,4 +59,4 @@ expect(is(ValidationError(), NetworkError)).toBe(false);
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/testing/task-12-unit-tests-from.md b/docs/internal/tasks/testing/task-12-unit-tests-from.md
index 7cc34f7..5b1af8b 100644
--- a/docs/internal/tasks/testing/task-12-unit-tests-from.md
+++ b/docs/internal/tasks/testing/task-12-unit-tests-from.md
@@ -44,7 +44,7 @@ expect((chain.cause as any).cause).toBe(cause);
// Chaining returns the error instance for further chaining
const instance = HighError();
const result = instance.from(cause);
-expect(result).toBe(instance); // Same instance, enables chaining
+expect(result).toBe(instance); // Same instance, enables chaining
```
## Dependencies
@@ -58,4 +58,4 @@ expect(result).toBe(instance); // Same instance, enables chaining
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/testing/task-13-unit-tests-causes.md b/docs/internal/tasks/testing/task-13-unit-tests-causes.md
index a800998..65d5e1b 100644
--- a/docs/internal/tasks/testing/task-13-unit-tests-causes.md
+++ b/docs/internal/tasks/testing/task-13-unit-tests-causes.md
@@ -57,4 +57,4 @@ expect(chain.causes).toEqual(causes(chain));
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/testing/task-14-unit-tests-messages.md b/docs/internal/tasks/testing/task-14-unit-tests-messages.md
index 9b4ac33..0519f4e 100644
--- a/docs/internal/tasks/testing/task-14-unit-tests-messages.md
+++ b/docs/internal/tasks/testing/task-14-unit-tests-messages.md
@@ -78,4 +78,4 @@ expect(EscapeError().message).toBe('Enter {field} here');
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/testing/task-15-type-tests.md b/docs/internal/tasks/testing/task-15-type-tests.md
index 8f27f89..b06d340 100644
--- a/docs/internal/tasks/testing/task-15-type-tests.md
+++ b/docs/internal/tasks/testing/task-15-type-tests.md
@@ -34,7 +34,7 @@ const FieldError = error({
const err = FieldError({ field: 'test' }); // count is optional
// Output type should include fields
-err.fields.field; // string
+err.fields.field; // string
err.fields.count; // number
// Inheritance types
@@ -48,14 +48,14 @@ if (is(err, Child)) {
// All properties exist
const basic = Parent();
-basic.name; // string
-basic.message; // string
-basic.stack; // string
-basic.fields; // Record
-basic.notes; // string[]
-basic.cause; // Error | null
-basic.causes; // Error[]
-basic.context; // Record | null
+basic.name; // string
+basic.message; // string
+basic.stack; // string
+basic.fields; // Record
+basic.notes; // string[]
+basic.cause; // Error | null
+basic.causes; // Error[]
+basic.context; // Record | null
```
## Dependencies
@@ -68,4 +68,4 @@ basic.context; // Record | null
## Notes
-Use Vitest with `@tsd` or plain TypeScript compilation tests.
\ No newline at end of file
+Use Vitest with `@tsd` or plain TypeScript compilation tests.
diff --git a/docs/internal/tasks/testing/task-18-integration-tests.md b/docs/internal/tasks/testing/task-18-integration-tests.md
index ef9b58a..9135e91 100644
--- a/docs/internal/tasks/testing/task-18-integration-tests.md
+++ b/docs/internal/tasks/testing/task-18-integration-tests.md
@@ -71,4 +71,4 @@ try {
## Notes
-Use Vitest for testing.
\ No newline at end of file
+Use Vitest for testing.
diff --git a/docs/internal/tasks/v1.0.0-core-foundation.md b/docs/internal/tasks/v1.0.0-core-foundation.md
index 7ef9869..2eb17b0 100644
--- a/docs/internal/tasks/v1.0.0-core-foundation.md
+++ b/docs/internal/tasks/v1.0.0-core-foundation.md
@@ -9,28 +9,28 @@ Tasks are organized in two categories:
## Task List
-| ID | Task | Category | Status |
-|----|------|----------|--------|
-| 01 | Implement error() factory | Implementation | ✅ Complete |
-| 02 | Implement raise() function | Implementation | ✅ Complete |
-| 03 | Implement is() function | Implementation | ✅ Complete |
-| 04 | Implement inherits option | Implementation | ✅ Complete |
-| 05 | Implement .from() method | Implementation | ✅ Complete |
-| 06 | Implement causes() function | Implementation | ✅ Complete |
-| 07 | Implement message templates | Implementation | ✅ Complete |
-| 08 | ErrorInstance properties | Implementation | ✅ Complete |
-| 09 | Unit tests: error() | Testing | ✅ Complete |
-| 10 | Unit tests: raise() | Testing | ✅ Complete |
-| 11 | Unit tests: is() | Testing | ✅ Complete |
-| 12 | Unit tests: .from() | Testing | ✅ Complete |
-| 13 | Unit tests: causes() | Testing | ✅ Complete |
-| 14 | Unit tests: messages | Testing | ✅ Complete |
-| 15 | Type tests | Testing | ✅ Complete |
-| 16 | Package setup | Infrastructure | ✅ Complete |
-| 17 | JSDoc documentation | Infrastructure | ✅ Complete |
-| 18 | Integration tests | Testing | ✅ Complete |
-| 19 | CHANGELOG.md | Infrastructure | 🟡 Pending |
-| 20 | Migration guide | Documentation | 🟡 Pending |
+| ID | Task | Category | Status |
+| --- | --------------------------- | -------------- | ----------- |
+| 01 | Implement error() factory | Implementation | ✅ Complete |
+| 02 | Implement raise() function | Implementation | ✅ Complete |
+| 03 | Implement is() function | Implementation | ✅ Complete |
+| 04 | Implement inherits option | Implementation | ✅ Complete |
+| 05 | Implement .from() method | Implementation | ✅ Complete |
+| 06 | Implement causes() function | Implementation | ✅ Complete |
+| 07 | Implement message templates | Implementation | ✅ Complete |
+| 08 | ErrorInstance properties | Implementation | ✅ Complete |
+| 09 | Unit tests: error() | Testing | ✅ Complete |
+| 10 | Unit tests: raise() | Testing | ✅ Complete |
+| 11 | Unit tests: is() | Testing | ✅ Complete |
+| 12 | Unit tests: .from() | Testing | ✅ Complete |
+| 13 | Unit tests: causes() | Testing | ✅ Complete |
+| 14 | Unit tests: messages | Testing | ✅ Complete |
+| 15 | Type tests | Testing | ✅ Complete |
+| 16 | Package setup | Infrastructure | ✅ Complete |
+| 17 | JSDoc documentation | Infrastructure | ✅ Complete |
+| 18 | Integration tests | Testing | ✅ Complete |
+| 19 | CHANGELOG.md | Infrastructure | 🟡 Pending |
+| 20 | Migration guide | Documentation | 🟡 Pending |
## Task Dependencies
@@ -48,4 +48,4 @@ Tasks are organized in two categories:
17 (JSDoc) ── 01-08
19 (CHANGELOG) ── all
20 (migration guide) ── 01-08
-```
\ No newline at end of file
+```
diff --git a/package.json b/package.json
index 0f94e74..00ea9a4 100644
--- a/package.json
+++ b/package.json
@@ -12,15 +12,17 @@
"clean": "turbo run clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,js,json,css,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,json,css,md}\"",
- "prepare": "husky",
+ "prepare": "husky || true",
" changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && pnpm test && changeset publish"
},
- "packageManager": "pnpm@10.30.3",
+ "packageManager": "pnpm@10.34.5",
"devDependencies": {
"@changesets/cli": "^2.31.0",
"husky": "^9.1.7",
+ "lint-staged": "^17.0.0",
+ "prettier": "^3.9.5",
"turbo": "^2.9.15"
},
"dependencies": {
@@ -30,4 +32,4 @@
"packages/*",
"apps/*"
]
-}
\ No newline at end of file
+}
diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md
index 5699edc..d8c1b39 100644
--- a/packages/errors/CHANGELOG.md
+++ b/packages/errors/CHANGELOG.md
@@ -7,12 +7,10 @@
- Release v1.1.1: infrastructure improvements and SEO enhancements
### Fixed
-
- Add changeset version step and tag push to release workflow
- Add display flex to all OG image divs for Satori compatibility
### Changed
-
- Comprehensive SEO optimization for @deessejs/errors website
- Add banner image for OG social sharing
- Add homepage URL to package.json for npm SEO
@@ -33,7 +31,6 @@
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
@@ -47,7 +44,6 @@
- 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
diff --git a/packages/errors/README.md b/packages/errors/README.md
index b3d3f54..0de1045 100644
--- a/packages/errors/README.md
+++ b/packages/errors/README.md
@@ -58,9 +58,9 @@ 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]
+console.log(processingErr.message); // "ProcessingError"
+console.log(processingErr.cause); // validationErr
+console.log(processingErr.causes); // [validationErr]
```
### Hierarchical Inheritance
@@ -97,8 +97,8 @@ const ValidationError = error({
const err = ValidationError();
-is(err, ValidationError); // true
-is(err, AppError); // true (inherits from AppError)
+is(err, ValidationError); // true
+is(err, AppError); // true (inherits from AppError)
```
### Traverse Cause Chain
@@ -125,13 +125,13 @@ for (const cause of causes(err3)) {
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 | ❌ | ✅ |
+| Feature | Built-in `Error` | @deessejs/errors |
+| ------------------------ | ---------------- | ---------------- |
+| Exception chaining | ❌ | ✅ |
+| Hierarchical inheritance | ❌ | ✅ |
+| Message templates | ❌ | ✅ |
+| Type-safe fields | ❌ | ✅ |
+| Standard Schema support | ❌ | ✅ |
```typescript
// Traditional approach — limited context
@@ -181,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/packages/errors/eslint.config.js b/packages/errors/eslint.config.js
index cb77328..37ab426 100644
--- a/packages/errors/eslint.config.js
+++ b/packages/errors/eslint.config.js
@@ -7,13 +7,16 @@ export default tseslint.config(
{
files: ['src/**/*.ts'],
rules: {
- '@typescript-eslint/no-unused-vars': ['error', {
- argsIgnorePattern: '^_',
- varsIgnorePattern: '^_',
- }],
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ },
+ ],
},
},
{
ignores: ['dist/**', 'node_modules/**'],
}
-);
\ No newline at end of file
+);
diff --git a/packages/errors/examples/chaining/02-chain.ts b/packages/errors/examples/chaining/02-chain.ts
index a714d62..d0b9765 100644
--- a/packages/errors/examples/chaining/02-chain.ts
+++ b/packages/errors/examples/chaining/02-chain.ts
@@ -56,7 +56,9 @@ function logError(err: unknown) {
if (chain.length > 0) {
console.log(' Causes:');
for (const cause of chain) {
- console.log(` - ${(cause as { name: string }).name}: ${(cause as { message: string }).message}`);
+ console.log(
+ ` - ${(cause as { name: string }).name}: ${(cause as { message: string }).message}`
+ );
}
}
console.log();
diff --git a/packages/errors/package.json b/packages/errors/package.json
index 48fec91..c03e491 100644
--- a/packages/errors/package.json
+++ b/packages/errors/package.json
@@ -45,4 +45,4 @@
"dependencies": {
"@standard-schema/spec": "^1.1.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/errors/src/causes/index.ts b/packages/errors/src/causes/index.ts
index 693c126..a2c51fd 100644
--- a/packages/errors/src/causes/index.ts
+++ b/packages/errors/src/causes/index.ts
@@ -27,15 +27,15 @@ import type { ErrorInstance } from '../error/types.js';
* // (err.cause is NetworkError, err.cause.cause is Error)
* ```
*/
-const causes = ( error: unknown ): Error[] => {
- if ( error == null ) {
+const causes = (error: unknown): Error[] => {
+ if (error == null) {
return [];
}
// Get the causes array from the error
const instance = error as ErrorInstance;
- if ( Array.isArray( instance.causes ) ) {
+ if (Array.isArray(instance.causes)) {
return instance.causes;
}
diff --git a/packages/errors/src/error/capture.ts b/packages/errors/src/error/capture.ts
index 0fbf211..0175200 100644
--- a/packages/errors/src/error/capture.ts
+++ b/packages/errors/src/error/capture.ts
@@ -12,29 +12,29 @@ import { STACK_FRAME_PATTERN } from './constants.js';
*
* @internal
*/
-const captureStack = ( message: string ): string => {
+const captureStack = (message: string): string => {
const stack = new Error().stack || '';
- const lines = stack.split( '\n' );
- const cleanedLines: string[] = [ `Error: ${message}` ];
+ const lines = stack.split('\n');
+ const cleanedLines: string[] = [`Error: ${message}`];
// Find start index (skip "Error: message" line)
let startIndex = 0;
- for ( let i = 0; i < lines.length; i = i + 1 ) {
- if ( STACK_FRAME_PATTERN.test( lines[i] ) ) {
+ for (let i = 0; i < lines.length; i = i + 1) {
+ if (STACK_FRAME_PATTERN.test(lines[i])) {
startIndex = i;
break;
}
}
// Filter internal frames
- for ( let i = startIndex; i < lines.length; i = i + 1 ) {
+ for (let i = startIndex; i < lines.length; i = i + 1) {
const line = lines[i];
- if ( line.includes( 'node_modules/@deessejs' ) ) continue;
- if ( line.includes( '__vite' ) ) continue;
- cleanedLines.push( line );
+ if (line.includes('node_modules/@deessejs')) continue;
+ if (line.includes('__vite')) continue;
+ cleanedLines.push(line);
}
- return cleanedLines.join( '\n' );
+ return cleanedLines.join('\n');
};
export { captureStack };
diff --git a/packages/errors/src/error/error.ts b/packages/errors/src/error/error.ts
index 8345fe0..45abf37 100644
--- a/packages/errors/src/error/error.ts
+++ b/packages/errors/src/error/error.ts
@@ -20,7 +20,7 @@ import { formatTemplate, hasTemplatePlaceholders } from './format.js';
*
* @internal
*/
-const FACTORY_SYMBOL = Symbol.for( '@deessejs/errors/factory' );
+const FACTORY_SYMBOL = Symbol.for('@deessejs/errors/factory');
// ============================================================================
// Error Factory
@@ -73,35 +73,33 @@ const FACTORY_SYMBOL = Symbol.for( '@deessejs/errors/factory' );
* });
* ```
*/
-export const error = = Record>(
- config: {
- name: string;
- fields?: StandardSchemaV1;
- inherits?: ErrorFactory | ErrorFactory[];
- message?: string;
- }
-): ErrorFactory => {
+export const error = = Record>(config: {
+ name: string;
+ fields?: StandardSchemaV1;
+ inherits?: ErrorFactory | ErrorFactory[];
+ message?: string;
+}): ErrorFactory => {
const { name, fields, inherits, message } = config;
/**
* Error factory function - creates error instances.
*/
- const ErrorFactoryInstance = ( input?: Partial ): ErrorInstance => {
- const fieldsData = ( input || {} ) as T;
+ const ErrorFactoryInstance = (input?: Partial): ErrorInstance => {
+ const fieldsData = (input || {}) as T;
// Format message if template has placeholders
let errorMessage = name;
- if ( message && hasTemplatePlaceholders( message ) ) {
- errorMessage = formatTemplate( message, fieldsData );
- } else if ( message ) {
+ if (message && hasTemplatePlaceholders(message)) {
+ errorMessage = formatTemplate(message, fieldsData);
+ } else if (message) {
errorMessage = message;
}
// Capture stack trace
- const stack = captureStack( errorMessage );
+ const stack = captureStack(errorMessage);
// Create error instance using native Error
- const instance = new Error( errorMessage ) as ErrorInstance;
+ const instance = new Error(errorMessage) as ErrorInstance;
instance.name = name;
instance.fields = fieldsData;
instance.notes = [];
@@ -112,12 +110,10 @@ export const error = = Record => {
+ instance.from = (cause: Error): ErrorInstance => {
// Build new causes array: [new cause] + [cause's causes] + [existing causes of instance]
// This maintains chronological order: newest first
- const causeCauses = 'causes' in cause && Array.isArray( cause.causes )
- ? cause.causes
- : [];
+ const causeCauses = 'causes' in cause && Array.isArray(cause.causes) ? cause.causes : [];
instance.causes = [cause, ...causeCauses, ...instance.causes];
instance.cause = cause;
return instance;
@@ -125,29 +121,30 @@ export const error = = Record unknown> )[FACTORY_SYMBOL] = ErrorFactoryInstance;
+ (instance as unknown as Record unknown>)[FACTORY_SYMBOL] =
+ ErrorFactoryInstance;
return instance;
};
// Attach metadata to the factory function
- Object.defineProperty( ErrorFactoryInstance, 'name', {
+ Object.defineProperty(ErrorFactoryInstance, 'name', {
value: name,
writable: false,
enumerable: false,
configurable: false,
- } );
+ });
- if ( inherits !== undefined ) {
- ( ErrorFactoryInstance as ErrorFactory ).inherits = inherits;
+ if (inherits !== undefined) {
+ (ErrorFactoryInstance as ErrorFactory).inherits = inherits;
}
- if ( fields !== undefined ) {
- ( ErrorFactoryInstance as ErrorFactory ).schema = fields;
+ if (fields !== undefined) {
+ (ErrorFactoryInstance as ErrorFactory).schema = fields;
}
- if ( message !== undefined ) {
- ( ErrorFactoryInstance as ErrorFactory ).rawMessage = message;
+ if (message !== undefined) {
+ (ErrorFactoryInstance as ErrorFactory).rawMessage = message;
}
return ErrorFactoryInstance as ErrorFactory;
diff --git a/packages/errors/src/error/format.ts b/packages/errors/src/error/format.ts
index 6f54026..81cc810 100644
--- a/packages/errors/src/error/format.ts
+++ b/packages/errors/src/error/format.ts
@@ -13,10 +13,9 @@ import { TEMPLATE_PLACEHOLDER_REGEX } from './constants.js';
* // => 'name' | 'age'
* ```
*/
-type ExtractKeys =
- S extends `${string}{${infer Key}}${infer Rest}`
- ? (Key extends `${infer RealKey}:${string}` ? RealKey : Key) | ExtractKeys
- : never;
+type ExtractKeys = S extends `${string}{${infer Key}}${infer Rest}`
+ ? (Key extends `${infer RealKey}:${string}` ? RealKey : Key) | ExtractKeys
+ : never;
/**
* Formats a message template by replacing {field} placeholders with values.
@@ -28,24 +27,24 @@ const formatTemplate = (
template: S,
data: Record, unknown>
): string => {
- return template.replace( /\{(\w+)(?::(\w+))?\}/g, ( fullMatch, fieldName, modifier ) => {
+ return template.replace(/\{(\w+)(?::(\w+))?\}/g, (fullMatch, fieldName, modifier) => {
const value = data[fieldName as keyof typeof data];
- if ( value === undefined ) {
+ if (value === undefined) {
return fullMatch;
}
- if ( modifier === 'upper' ) {
- return String( value ).toUpperCase();
+ if (modifier === 'upper') {
+ return String(value).toUpperCase();
}
- if ( modifier === 'lower' ) {
- return String( value ).toLowerCase();
+ if (modifier === 'lower') {
+ return String(value).toLowerCase();
}
- if ( modifier === 'json' ) {
- return JSON.stringify( value );
+ if (modifier === 'json') {
+ return JSON.stringify(value);
}
- return String( value );
- } );
+ return String(value);
+ });
};
/**
@@ -53,9 +52,9 @@ const formatTemplate = (
*
* @internal
*/
-const hasTemplatePlaceholders = ( message: string ): boolean => {
+const hasTemplatePlaceholders = (message: string): boolean => {
TEMPLATE_PLACEHOLDER_REGEX.lastIndex = 0;
- return TEMPLATE_PLACEHOLDER_REGEX.test( message );
+ return TEMPLATE_PLACEHOLDER_REGEX.test(message);
};
export { formatTemplate, hasTemplatePlaceholders };
diff --git a/packages/errors/src/error/types.ts b/packages/errors/src/error/types.ts
index bd2d5ad..d024dd0 100644
--- a/packages/errors/src/error/types.ts
+++ b/packages/errors/src/error/types.ts
@@ -26,7 +26,7 @@ export type ErrorInstanceCore = {
* Creates typed, structured errors with optional field definitions.
*/
export type ErrorFactory = Record> = {
- ( fields?: Partial ): ErrorInstance;
+ (fields?: Partial): ErrorInstance;
name: string;
inherits?: ErrorFactory | ErrorFactory[];
schema?: StandardSchemaV1;
@@ -58,7 +58,7 @@ export type ErrorInstance = Record;
+ from(cause: Error | ErrorInstance): ErrorInstance;
/** Direct cause of this error (from .from()) */
cause: Error | null;
/** Full cause chain from .from() calls */
diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts
index e4a0ed5..c6aae91 100644
--- a/packages/errors/src/index.ts
+++ b/packages/errors/src/index.ts
@@ -6,11 +6,7 @@
// Types
export type { StandardSchemaV1 } from '@standard-schema/spec';
-export type {
- ErrorFactory,
- ErrorInstance,
- ErrorInstanceCore,
-} from './error/types.js';
+export type { ErrorFactory, ErrorInstance, ErrorInstanceCore } from './error/types.js';
// Error factory function
export { error } from './error/error.js';
diff --git a/packages/errors/src/is/index.ts b/packages/errors/src/is/index.ts
index c63e9c9..5c1aef8 100644
--- a/packages/errors/src/is/index.ts
+++ b/packages/errors/src/is/index.ts
@@ -10,13 +10,14 @@ import { FACTORY_SYMBOL } from '../error/error.js';
*
* @internal
*/
-type ExtractFields = T extends ErrorFactory
- ? F
- : T extends new ( ...args: unknown[] ) => infer E
- ? E extends ErrorInstance
- ? F
- : Record
- : Record;
+type ExtractFields =
+ T extends ErrorFactory
+ ? F
+ : T extends new (...args: unknown[]) => infer E
+ ? E extends ErrorInstance
+ ? F
+ : Record
+ : Record;
/**
* Checks if an error is an instance of a specific error type.
@@ -52,19 +53,19 @@ type ExtractFields = T extends ErrorFactory
* }
* ```
*/
-const is = Error )>(
+const is = Error)>(
error: unknown,
ErrorType: T
): error is ErrorInstance> => {
// Handle null/undefined
- if ( error == null ) {
+ if (error == null) {
return false;
}
// Handle native errors - check prototype chain ends in Error
- if ( typeof ErrorType === 'function' && 'prototype' in ErrorType ) {
+ if (typeof ErrorType === 'function' && 'prototype' in ErrorType) {
try {
- if ( error instanceof ErrorType ) {
+ if (error instanceof ErrorType) {
return true;
}
} catch {
@@ -73,38 +74,38 @@ const is = Error )>(
}
// Handle our ErrorFactory instances using Symbol-based reference
- if ( typeof error === 'object' && error !== null ) {
+ if (typeof error === 'object' && error !== null) {
const marker = error as Record;
const factory = marker[FACTORY_SYMBOL];
- if ( factory !== undefined ) {
+ if (factory !== undefined) {
// DFS walk of inheritance tree using stack (prevents GC pressure)
const stack: ErrorFactory[] = [factory as ErrorFactory];
const seen = new Set();
- while ( stack.length > 0 ) {
+ while (stack.length > 0) {
const current = stack.pop()!;
// Prevent infinite loops in cyclic inheritance
- if ( seen.has( current ) ) {
+ if (seen.has(current)) {
continue;
}
- seen.add( current );
+ seen.add(current);
// Direct match
- if ( current === ErrorType ) {
+ if (current === ErrorType) {
return true;
}
// Add parents to stack
- const inherits = ( current as ErrorFactory ).inherits;
- if ( inherits !== undefined ) {
- if ( Array.isArray( inherits ) ) {
- for ( let i = 0; i < inherits.length; i++ ) {
- stack.push( inherits[i] );
+ const inherits = (current as ErrorFactory).inherits;
+ if (inherits !== undefined) {
+ if (Array.isArray(inherits)) {
+ for (let i = 0; i < inherits.length; i++) {
+ stack.push(inherits[i]);
}
} else {
- stack.push( inherits );
+ stack.push(inherits);
}
}
}
@@ -114,4 +115,4 @@ const is = Error )>(
return false;
};
-export { is };
\ No newline at end of file
+export { is };
diff --git a/packages/errors/src/raise/index.ts b/packages/errors/src/raise/index.ts
index 456bba9..62c57f3 100644
--- a/packages/errors/src/raise/index.ts
+++ b/packages/errors/src/raise/index.ts
@@ -37,7 +37,7 @@ import type { ErrorInstance } from '../error/types.js';
* raise(AppError().from(err).addNote('Context here'));
* ```
*/
-const raise = ( error: ErrorInstance ): never => {
+const raise = (error: ErrorInstance): never => {
throw error;
};
diff --git a/packages/errors/tests/causes.test.ts b/packages/errors/tests/causes.test.ts
index 7e4469a..87f5f06 100644
--- a/packages/errors/tests/causes.test.ts
+++ b/packages/errors/tests/causes.test.ts
@@ -5,123 +5,123 @@
import { describe, it, expect } from 'vitest';
import { error, causes } from '../src/index.js';
-describe( 'causes() function', () => {
- describe( 'basic usage', () => {
- it( 'should return causes array from error instance', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( { name: 'ValidationError' } );
+describe('causes() function', () => {
+ describe('basic usage', () => {
+ it('should return causes array from error instance', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({ name: 'ValidationError' });
const cause = AppError();
const instance = ValidationError();
- instance.from( cause );
+ instance.from(cause);
- const result = causes( instance );
+ const result = causes(instance);
- expect( result ).toHaveLength( 1 );
- expect( result[0] ).toBe( cause );
- } );
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBe(cause);
+ });
- it( 'should return empty array for error with no cause', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should return empty array for error with no cause', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- const result = causes( instance );
+ const result = causes(instance);
- expect( result ).toEqual( [] );
- } );
- } );
+ expect(result).toEqual([]);
+ });
+ });
- describe( 'ordering', () => {
- it( 'should return array ordered from most recent to root cause', () => {
- const AppError = error( { name: 'AppError' } );
+ describe('ordering', () => {
+ it('should return array ordered from most recent to root cause', () => {
+ const AppError = error({ name: 'AppError' });
const cause1 = AppError();
const cause2 = AppError();
const cause3 = AppError();
- cause3.from( cause2 ).from( cause1 );
+ cause3.from(cause2).from(cause1);
const instance = AppError();
- instance.from( cause3 );
+ instance.from(cause3);
- const result = causes( instance );
+ const result = causes(instance);
// result contains cause3, cause2, cause1 (newest to oldest)
- expect( result ).toContain( cause3 );
- expect( result ).toContain( cause2 );
- expect( result ).toContain( cause1 );
- } );
+ expect(result).toContain(cause3);
+ expect(result).toContain(cause2);
+ expect(result).toContain(cause1);
+ });
- it( 'should work with single level chaining', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should work with single level chaining', () => {
+ const AppError = error({ name: 'AppError' });
const cause = AppError();
const instance = AppError();
- instance.from( cause );
+ instance.from(cause);
- const result = causes( instance );
+ const result = causes(instance);
- expect( result ).toHaveLength( 1 );
- expect( result[0] ).toBe( cause );
- } );
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBe(cause);
+ });
- it( 'should work with multiple level chaining', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should work with multiple level chaining', () => {
+ const AppError = error({ name: 'AppError' });
const err1 = AppError();
const err2 = AppError();
const err3 = AppError();
- err2.from( err1 );
- err3.from( err2 );
+ err2.from(err1);
+ err3.from(err2);
- const result = causes( err3 );
+ const result = causes(err3);
- expect( result ).toHaveLength( 2 );
- expect( result[0] ).toBe( err2 );
- expect( result[1] ).toBe( err1 );
- } );
- } );
+ expect(result).toHaveLength(2);
+ expect(result[0]).toBe(err2);
+ expect(result[1]).toBe(err1);
+ });
+ });
- describe( 'native errors', () => {
- it( 'should handle native errors in chain', () => {
- const AppError = error( { name: 'AppError' } );
+ describe('native errors', () => {
+ it('should handle native errors in chain', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- instance.from( new Error( 'native cause' ) );
+ instance.from(new Error('native cause'));
- const result = causes( instance );
+ const result = causes(instance);
- expect( result ).toHaveLength( 1 );
- expect( result[0] ).toBeInstanceOf( Error );
- expect( result[0].message ).toBe( 'native cause' );
- } );
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBeInstanceOf(Error);
+ expect(result[0].message).toBe('native cause');
+ });
- it( 'should return empty array for native error without causes', () => {
- const result = causes( new Error( 'test' ) );
+ it('should return empty array for native error without causes', () => {
+ const result = causes(new Error('test'));
- expect( result ).toEqual( [] );
- } );
- } );
+ expect(result).toEqual([]);
+ });
+ });
- describe( 'edge cases', () => {
- it( 'should return empty array for null', () => {
- expect( causes( null ) ).toEqual( [] );
- } );
+ describe('edge cases', () => {
+ it('should return empty array for null', () => {
+ expect(causes(null)).toEqual([]);
+ });
- it( 'should return empty array for undefined', () => {
- expect( causes( undefined ) ).toEqual( [] );
- } );
+ it('should return empty array for undefined', () => {
+ expect(causes(undefined)).toEqual([]);
+ });
- it( 'should return empty array for non-error values', () => {
- expect( causes( 'string' ) ).toEqual( [] );
- expect( causes( 123 ) ).toEqual( [] );
- expect( causes( {} ) ).toEqual( [] );
- } );
+ it('should return empty array for non-error values', () => {
+ expect(causes('string')).toEqual([]);
+ expect(causes(123)).toEqual([]);
+ expect(causes({})).toEqual([]);
+ });
- it( 'should return causes property directly', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should return causes property directly', () => {
+ const AppError = error({ name: 'AppError' });
const cause = AppError();
const instance = AppError();
- instance.from( cause );
+ instance.from(cause);
// The causes() function returns the same as err.causes property
- expect( causes( instance ) ).toBe( instance.causes );
- } );
- } );
-} );
+ expect(causes(instance)).toBe(instance.causes);
+ });
+ });
+});
diff --git a/packages/errors/tests/error.test.ts b/packages/errors/tests/error.test.ts
index 7937bf4..864c921 100644
--- a/packages/errors/tests/error.test.ts
+++ b/packages/errors/tests/error.test.ts
@@ -7,337 +7,337 @@ import { error } from '../src/error/error.js';
import type { ErrorFactory, ErrorInstance, StandardSchemaV1 } from '../src/index.js';
// Mock Standard Schema interface for testing (simplified StandardSchemaV1)
-const createMockSchema = ( name = 'mock' ): StandardSchemaV1 => {
+const createMockSchema = (name = 'mock'): StandardSchemaV1 => {
return {
'~standard': {
version: 1,
vendor: name,
- validate: () => ( { value: undefined as unknown as T } ),
+ validate: () => ({ value: undefined as unknown as T }),
},
};
};
-describe( 'error() factory function', () => {
- describe( 'basic usage', () => {
- it( 'should create an error factory with only a name', () => {
- const NotFoundError = error( {
+describe('error() factory function', () => {
+ describe('basic usage', () => {
+ it('should create an error factory with only a name', () => {
+ const NotFoundError = error({
name: 'NotFoundError',
- } );
+ });
- expect( typeof NotFoundError ).toBe( 'function' );
- expect( NotFoundError.name ).toBe( 'NotFoundError' );
- } );
+ expect(typeof NotFoundError).toBe('function');
+ expect(NotFoundError.name).toBe('NotFoundError');
+ });
- it( 'should create an error factory with name property', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should create an error factory with name property', () => {
+ const AppError = error({ name: 'AppError' });
- expect( AppError.name ).toBe( 'AppError' );
- } );
+ expect(AppError.name).toBe('AppError');
+ });
- it( 'should create error instance with name as message when no template', () => {
- const BasicError = error( { name: 'BasicError' } );
+ it('should create error instance with name as message when no template', () => {
+ const BasicError = error({ name: 'BasicError' });
const instance = BasicError();
- expect( instance ).toBeDefined();
- expect( instance.name ).toBe( 'BasicError' );
- expect( instance.message ).toBe( 'BasicError' );
- expect( instance.stack ).toBeDefined();
- expect( instance.stack ).toContain( 'Error: BasicError' );
- } );
- } );
-
- describe( 'ErrorInstance properties', () => {
- it( 'should create error instance with all required properties', () => {
- const TestError = error( { name: 'TestError' } );
+ expect(instance).toBeDefined();
+ expect(instance.name).toBe('BasicError');
+ expect(instance.message).toBe('BasicError');
+ expect(instance.stack).toBeDefined();
+ expect(instance.stack).toContain('Error: BasicError');
+ });
+ });
+
+ describe('ErrorInstance properties', () => {
+ it('should create error instance with all required properties', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
// Core properties
- expect( typeof instance.name ).toBe( 'string' );
- expect( typeof instance.message ).toBe( 'string' );
- expect( typeof instance.stack ).toBe( 'string' );
+ expect(typeof instance.name).toBe('string');
+ expect(typeof instance.message).toBe('string');
+ expect(typeof instance.stack).toBe('string');
// Additional properties
- expect( instance.fields ).toBeDefined();
- expect( typeof instance.fields ).toBe( 'object' );
- expect( Array.isArray( instance.notes ) ).toBe( true );
- expect( instance.notes ).toEqual( [] );
- expect( instance.cause ).toBeNull();
- expect( Array.isArray( instance.causes ) ).toBe( true );
- expect( instance.causes ).toEqual( [] );
- expect( instance.context ).toBeNull();
- } );
-
- it( 'should be an instance of Error', () => {
- const TestError = error( { name: 'TestError' } );
+ expect(instance.fields).toBeDefined();
+ expect(typeof instance.fields).toBe('object');
+ expect(Array.isArray(instance.notes)).toBe(true);
+ expect(instance.notes).toEqual([]);
+ expect(instance.cause).toBeNull();
+ expect(Array.isArray(instance.causes)).toBe(true);
+ expect(instance.causes).toEqual([]);
+ expect(instance.context).toBeNull();
+ });
+
+ it('should be an instance of Error', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( instance instanceof Error ).toBe( true );
- } );
+ expect(instance instanceof Error).toBe(true);
+ });
- it( 'should have inherits reference when inheriting', () => {
- const ParentError = error( { name: 'ParentError' } );
- const ChildError = error( {
+ it('should have inherits reference when inheriting', () => {
+ const ParentError = error({ name: 'ParentError' });
+ const ChildError = error({
name: 'ChildError',
inherits: ParentError,
- } );
+ });
const instance = ChildError();
- expect( instance.inherits ).toBe( ParentError );
- } );
- } );
+ expect(instance.inherits).toBe(ParentError);
+ });
+ });
- describe( 'inherits option', () => {
- it( 'should support single inheritance', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( {
+ describe('inherits option', () => {
+ it('should support single inheritance', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
- } );
+ });
- expect( ValidationError.inherits ).toBe( AppError );
- } );
+ expect(ValidationError.inherits).toBe(AppError);
+ });
- it( 'should support multiple inheritance', () => {
- const NetworkError = error( { name: 'NetworkError' } );
- const StorageError = error( { name: 'StorageError' } );
- const CombinedError = error( {
+ it('should support multiple inheritance', () => {
+ const NetworkError = error({ name: 'NetworkError' });
+ const StorageError = error({ name: 'StorageError' });
+ const CombinedError = error({
name: 'CombinedError',
inherits: [NetworkError, StorageError],
- } );
+ });
- expect( Array.isArray( CombinedError.inherits ) ).toBe( true );
- expect( ( CombinedError.inherits as ErrorFactory[] ).length ).toBe( 2 );
- } );
+ expect(Array.isArray(CombinedError.inherits)).toBe(true);
+ expect((CombinedError.inherits as ErrorFactory[]).length).toBe(2);
+ });
- it( 'should not have inherits property when not specified', () => {
- const SimpleError = error( { name: 'SimpleError' } );
+ it('should not have inherits property when not specified', () => {
+ const SimpleError = error({ name: 'SimpleError' });
- expect( 'inherits' in SimpleError ).toBe( false );
- } );
+ expect('inherits' in SimpleError).toBe(false);
+ });
- it( 'should store inherits on factory for later type checking', () => {
- const ParentA = error( { name: 'ParentA' } );
- const ParentB = error( { name: 'ParentB' } );
- const Child = error( {
+ it('should store inherits on factory for later type checking', () => {
+ const ParentA = error({ name: 'ParentA' });
+ const ParentB = error({ name: 'ParentB' });
+ const Child = error({
name: 'Child',
inherits: [ParentA, ParentB],
- } );
+ });
const instance = Child();
- expect( instance.inherits ).toBeDefined();
- expect( Array.isArray( instance.inherits ) ).toBe( true );
- } );
- } );
-
- describe( 'message template', () => {
- it( 'should format message with field placeholders', () => {
- const ValidationError = error<{ field: string }>( {
+ expect(instance.inherits).toBeDefined();
+ expect(Array.isArray(instance.inherits)).toBe(true);
+ });
+ });
+
+ describe('message template', () => {
+ it('should format message with field placeholders', () => {
+ const ValidationError = error<{ field: string }>({
name: 'ValidationError',
message: 'Field "{field}" is invalid',
- } );
+ });
- const instance = ValidationError( { field: 'email' } );
- expect( instance.message ).toBe( 'Field "email" is invalid' );
- } );
+ const instance = ValidationError({ field: 'email' });
+ expect(instance.message).toBe('Field "email" is invalid');
+ });
- it( 'should handle multiple placeholders', () => {
- const ValidationError = error<{ field: string; expected: string; actual: string }>( {
+ it('should handle multiple placeholders', () => {
+ const ValidationError = error<{ field: string; expected: string; actual: string }>({
name: 'ValidationError',
message: 'Field "{field}" expected {expected}, got {actual}',
- } );
+ });
- const instance = ValidationError( {
+ const instance = ValidationError({
field: 'age',
expected: 'number',
actual: 'string',
- } );
- expect( instance.message ).toBe( 'Field "age" expected number, got string' );
- } );
+ });
+ expect(instance.message).toBe('Field "age" expected number, got string');
+ });
- it( 'should use name as default message when no template', () => {
- const InternalError = error( { name: 'InternalError' } );
+ it('should use name as default message when no template', () => {
+ const InternalError = error({ name: 'InternalError' });
const instance = InternalError();
- expect( instance.message ).toBe( 'InternalError' );
- } );
+ expect(instance.message).toBe('InternalError');
+ });
- it( 'should support :upper modifier', () => {
- const ErrorWithModifier = error<{ userId: string }>( {
+ it('should support :upper modifier', () => {
+ const ErrorWithModifier = error<{ userId: string }>({
name: 'ErrorWithModifier',
message: 'User ID: {userId:upper}',
- } );
+ });
- const instance = ErrorWithModifier( { userId: 'abc123' } );
- expect( instance.message ).toBe( 'User ID: ABC123' );
- } );
+ const instance = ErrorWithModifier({ userId: 'abc123' });
+ expect(instance.message).toBe('User ID: ABC123');
+ });
- it( 'should support :lower modifier', () => {
- const ErrorWithModifier = error<{ msg: string }>( {
+ it('should support :lower modifier', () => {
+ const ErrorWithModifier = error<{ msg: string }>({
name: 'ErrorWithModifier',
message: 'Message: {msg:lower}',
- } );
+ });
- const instance = ErrorWithModifier( { msg: 'HELLO WORLD' } );
- expect( instance.message ).toBe( 'Message: hello world' );
- } );
+ const instance = ErrorWithModifier({ msg: 'HELLO WORLD' });
+ expect(instance.message).toBe('Message: hello world');
+ });
- it( 'should support :json modifier', () => {
- const DataError = error<{ data: { id: number; name: string } }>( {
+ it('should support :json modifier', () => {
+ const DataError = error<{ data: { id: number; name: string } }>({
name: 'DataError',
message: 'Invalid data: {data:json}',
- } );
+ });
- const instance = DataError( { data: { id: 1, name: 'test' } } );
- expect( instance.message ).toBe( 'Invalid data: {"id":1,"name":"test"}' );
- } );
+ const instance = DataError({ data: { id: 1, name: 'test' } });
+ expect(instance.message).toBe('Invalid data: {"id":1,"name":"test"}');
+ });
- it( 'should leave placeholder unchanged if field not found', () => {
- const PartialError = error<{ field: string }>( {
+ it('should leave placeholder unchanged if field not found', () => {
+ const PartialError = error<{ field: string }>({
name: 'PartialError',
message: 'Field "{field}" is invalid',
- } );
+ });
- const instance = PartialError( { field: '' } );
- expect( instance.message ).toBe( 'Field "" is invalid' );
- } );
+ const instance = PartialError({ field: '' });
+ expect(instance.message).toBe('Field "" is invalid');
+ });
- it( 'should format template even with no fields provided', () => {
- const TemplateError = error<{ field: string }>( {
+ it('should format template even with no fields provided', () => {
+ const TemplateError = error<{ field: string }>({
name: 'TemplateError',
message: 'Field "{field}" is invalid',
- } );
+ });
const instance = TemplateError();
- expect( instance.message ).toBe( 'Field "{field}" is invalid' );
- } );
+ expect(instance.message).toBe('Field "{field}" is invalid');
+ });
- it( 'should not format message without placeholders', () => {
- const FixedError = error( {
+ it('should not format message without placeholders', () => {
+ const FixedError = error({
name: 'FixedError',
message: 'Something went wrong',
- } );
+ });
const instance = FixedError();
- expect( instance.message ).toBe( 'Something went wrong' );
- } );
- } );
+ expect(instance.message).toBe('Something went wrong');
+ });
+ });
- describe( 'fields with Standard Schema', () => {
- it( 'should accept Standard Schema fields', () => {
+ describe('fields with Standard Schema', () => {
+ it('should accept Standard Schema fields', () => {
const mockSchema = createMockSchema<{ field: string; reason: string }>();
- const ValidationError = error( {
+ const ValidationError = error({
name: 'ValidationError',
fields: mockSchema,
- } );
+ });
- expect( ValidationError.schema ).toBeDefined();
- } );
+ expect(ValidationError.schema).toBeDefined();
+ });
- it( 'should store fields schema for runtime validation', () => {
+ it('should store fields schema for runtime validation', () => {
const mockSchema = createMockSchema<{ field: string }>();
- const FieldError = error( {
+ const FieldError = error({
name: 'FieldError',
fields: mockSchema,
- } );
+ });
- expect( FieldError.schema ).toBeDefined();
- } );
+ expect(FieldError.schema).toBeDefined();
+ });
- it( 'should return empty fields object by default', () => {
- const NoFieldsError = error( { name: 'NoFieldsError' } );
+ it('should return empty fields object by default', () => {
+ const NoFieldsError = error({ name: 'NoFieldsError' });
const instance = NoFieldsError();
- expect( instance.fields ).toEqual( {} );
- } );
- } );
+ expect(instance.fields).toEqual({});
+ });
+ });
- describe( 'type inference', () => {
- it( 'should infer proper types for ErrorFactory', () => {
- const AppError = error( { name: 'AppError' } );
+ describe('type inference', () => {
+ it('should infer proper types for ErrorFactory', () => {
+ const AppError = error({ name: 'AppError' });
// Type checks - these compile if types are correct
const instance: ErrorInstance = AppError();
- expect( instance.name ).toBe( 'AppError' );
- } );
+ expect(instance.name).toBe('AppError');
+ });
- it( 'should allow passing fields to factory with typed error', () => {
- const FieldError = error<{ field: string }>( {
+ it('should allow passing fields to factory with typed error', () => {
+ const FieldError = error<{ field: string }>({
name: 'FieldError',
message: 'Field "{field}" is invalid',
- } );
+ });
// Should accept partial fields
- const instance = FieldError( { field: 'test' } );
- expect( instance.fields.field ).toBe( 'test' );
- } );
+ const instance = FieldError({ field: 'test' });
+ expect(instance.fields.field).toBe('test');
+ });
- it( 'should work with typed config', () => {
+ it('should work with typed config', () => {
type Config = { field: string };
- const TypedError = error( {
+ const TypedError = error({
name: 'TypedError',
message: 'Field "{field}" is missing',
- } );
+ });
// Instance should have field
- const instance = TypedError( { field: 'email' } );
- expect( instance.fields.field ).toBe( 'email' );
- } );
+ const instance = TypedError({ field: 'email' });
+ expect(instance.fields.field).toBe('email');
+ });
- it( 'should infer empty fields when no type provided', () => {
- const NoFieldsError = error( { name: 'NoFieldsError' } );
+ it('should infer empty fields when no type provided', () => {
+ const NoFieldsError = error({ name: 'NoFieldsError' });
const instance = NoFieldsError();
// fields should be Record which is empty
- expect( instance.fields ).toEqual( {} );
- } );
- } );
+ expect(instance.fields).toEqual({});
+ });
+ });
- describe( 'factory identity', () => {
- it( 'should create unique factory instances', () => {
- const ErrorA = error( { name: 'ErrorA' } );
- const ErrorB = error( { name: 'ErrorB' } );
+ describe('factory identity', () => {
+ it('should create unique factory instances', () => {
+ const ErrorA = error({ name: 'ErrorA' });
+ const ErrorB = error({ name: 'ErrorB' });
- expect( ErrorA ).not.toBe( ErrorB );
- expect( ErrorA.name ).not.toBe( ErrorB.name );
- } );
+ expect(ErrorA).not.toBe(ErrorB);
+ expect(ErrorA.name).not.toBe(ErrorB.name);
+ });
- it( 'should create errors that are instances of Error', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should create errors that are instances of Error', () => {
+ const TestError = error({ name: 'TestError' });
const instance1 = TestError();
const instance2 = TestError();
- expect( instance1 instanceof Error ).toBe( true );
- expect( instance2 instanceof Error ).toBe( true );
- } );
- } );
+ expect(instance1 instanceof Error).toBe(true);
+ expect(instance2 instanceof Error).toBe(true);
+ });
+ });
- describe( 'stack trace', () => {
- it( 'should generate a stack trace', () => {
- const TestError = error( { name: 'TestError' } );
+ describe('stack trace', () => {
+ it('should generate a stack trace', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( instance.stack ).toBeDefined();
- expect( instance.stack.length ).toBeGreaterThan( 0 );
- } );
+ expect(instance.stack).toBeDefined();
+ expect(instance.stack.length).toBeGreaterThan(0);
+ });
- it( 'should include error name in stack', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should include error name in stack', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( instance.stack ).not.toBe( '' );
- } );
+ expect(instance.stack).not.toBe('');
+ });
- it( 'should include formatted message in stack', () => {
- const TestError = error<{ field: string }>( {
+ it('should include formatted message in stack', () => {
+ const TestError = error<{ field: string }>({
name: 'TestError',
message: 'Custom message for {field}',
- } );
- const instance = TestError( { field: 'value' } );
+ });
+ const instance = TestError({ field: 'value' });
- expect( instance.stack ).toContain( 'Custom message for value' );
- } );
- } );
-} );
+ expect(instance.stack).toContain('Custom message for value');
+ });
+ });
+});
diff --git a/packages/errors/tests/from.test.ts b/packages/errors/tests/from.test.ts
index 08a855c..ec44afb 100644
--- a/packages/errors/tests/from.test.ts
+++ b/packages/errors/tests/from.test.ts
@@ -5,174 +5,174 @@
import { describe, it, expect } from 'vitest';
import { error } from '../src/index.js';
-describe( '.from() method', () => {
- describe( 'basic usage', () => {
- it( 'should set the cause property', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( { name: 'ValidationError' } );
+describe('.from() method', () => {
+ describe('basic usage', () => {
+ it('should set the cause property', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({ name: 'ValidationError' });
const cause = AppError();
const instance = ValidationError();
- const result = instance.from( cause );
+ const result = instance.from(cause);
- expect( instance.cause ).toBe( cause );
- } );
+ expect(instance.cause).toBe(cause);
+ });
- it( 'should return the instance for chaining', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should return the instance for chaining', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- const result = instance.from( new Error( 'cause' ) );
+ const result = instance.from(new Error('cause'));
- expect( result ).toBe( instance );
- } );
+ expect(result).toBe(instance);
+ });
- it( 'should work with native errors', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should work with native errors', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- instance.from( new TypeError( 'native cause' ) );
+ instance.from(new TypeError('native cause'));
- expect( instance.cause ).toBeInstanceOf( TypeError );
- expect( instance.cause!.message ).toBe( 'native cause' );
- } );
- } );
+ expect(instance.cause).toBeInstanceOf(TypeError);
+ expect(instance.cause!.message).toBe('native cause');
+ });
+ });
- describe( 'cause chain', () => {
- it( 'should add cause to causes array', () => {
- const AppError = error( { name: 'AppError' } );
+ describe('cause chain', () => {
+ it('should add cause to causes array', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- instance.from( new Error( 'cause' ) );
+ instance.from(new Error('cause'));
- expect( instance.causes ).toHaveLength( 1 );
- expect( instance.causes[0].message ).toBe( 'cause' );
- } );
+ expect(instance.causes).toHaveLength(1);
+ expect(instance.causes[0].message).toBe('cause');
+ });
- it( 'should preserve nested cause chain', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should preserve nested cause chain', () => {
+ const AppError = error({ name: 'AppError' });
const cause1 = AppError();
const cause2 = AppError();
- cause2.from( cause1 );
+ cause2.from(cause1);
const instance = AppError();
- instance.from( cause2 );
+ instance.from(cause2);
- expect( instance.causes ).toHaveLength( 2 );
+ expect(instance.causes).toHaveLength(2);
// Direct cause is cause2, then cause1 (from cause2's chain)
- expect( instance.cause ).toBe( cause2 );
- expect( instance.causes ).toContain( cause1 );
- expect( instance.causes ).toContain( cause2 );
- } );
+ expect(instance.cause).toBe(cause2);
+ expect(instance.causes).toContain(cause1);
+ expect(instance.causes).toContain(cause2);
+ });
- it( 'should build complete cause chain', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should build complete cause chain', () => {
+ const AppError = error({ name: 'AppError' });
const cause1 = AppError();
const cause2 = AppError();
const cause3 = AppError();
- cause3.from( cause2 ).from( cause1 );
+ cause3.from(cause2).from(cause1);
const instance = AppError();
- instance.from( cause3 );
+ instance.from(cause3);
// causes array contains the full chain: newest first
- expect( instance.causes ).toHaveLength( 3 );
- expect( instance.cause ).toBe( cause3 );
+ expect(instance.causes).toHaveLength(3);
+ expect(instance.cause).toBe(cause3);
// Verify all causes are present (order reflects build order)
- expect( instance.causes ).toContain( cause3 );
- expect( instance.causes ).toContain( cause2 );
- expect( instance.causes ).toContain( cause1 );
- } );
- } );
-
- describe( 'method chaining', () => {
- it( 'should support chaining multiple .from() calls', () => {
- const AppError = error( { name: 'AppError' } );
+ expect(instance.causes).toContain(cause3);
+ expect(instance.causes).toContain(cause2);
+ expect(instance.causes).toContain(cause1);
+ });
+ });
+
+ describe('method chaining', () => {
+ it('should support chaining multiple .from() calls', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
const result = instance
- .from( new Error( 'cause 1' ) )
- .from( new Error( 'cause 2' ) )
- .from( new Error( 'cause 3' ) );
+ .from(new Error('cause 1'))
+ .from(new Error('cause 2'))
+ .from(new Error('cause 3'));
- expect( result ).toBe( instance );
- expect( instance.causes ).toHaveLength( 3 );
- } );
+ expect(result).toBe(instance);
+ expect(instance.causes).toHaveLength(3);
+ });
- it( 'should update cause when chaining', () => {
- const AppError = error( { name: 'AppError' } );
+ it('should update cause when chaining', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
- const cause1 = new Error( 'cause 1' );
- const cause2 = new Error( 'cause 2' );
+ const cause1 = new Error('cause 1');
+ const cause2 = new Error('cause 2');
- instance.from( cause1 ).from( cause2 );
+ instance.from(cause1).from(cause2);
// The direct cause should be the last one
- expect( instance.cause ).toBe( cause2 );
+ expect(instance.cause).toBe(cause2);
// But causes array should have both
- expect( instance.causes ).toContain( cause1 );
- expect( instance.causes ).toContain( cause2 );
- } );
- } );
+ expect(instance.causes).toContain(cause1);
+ expect(instance.causes).toContain(cause2);
+ });
+ });
- describe( 'type safety', () => {
- it( 'should work with typed errors', () => {
- const AppError = error<{ code: string }>( { name: 'AppError' } );
- const ValidationError = error<{ field: string }>( { name: 'ValidationError' } );
+ describe('type safety', () => {
+ it('should work with typed errors', () => {
+ const AppError = error<{ code: string }>({ name: 'AppError' });
+ const ValidationError = error<{ field: string }>({ name: 'ValidationError' });
- const cause = AppError( { code: 'ERR001' } );
- const instance = ValidationError( { field: 'email' } );
+ const cause = AppError({ code: 'ERR001' });
+ const instance = ValidationError({ field: 'email' });
- instance.from( cause );
+ instance.from(cause);
- expect( instance.cause ).toBe( cause );
- expect( ( instance.cause as AppError ).fields.code ).toBe( 'ERR001' );
- } );
+ expect(instance.cause).toBe(cause);
+ expect((instance.cause as AppError).fields.code).toBe('ERR001');
+ });
- it( 'should maintain instance fields after .from()', () => {
- const ValidationError = error<{ field: string }>( {
+ it('should maintain instance fields after .from()', () => {
+ const ValidationError = error<{ field: string }>({
name: 'ValidationError',
message: 'Field "{field}" is invalid',
- } );
+ });
- const instance = ValidationError( { field: 'email' } );
- instance.from( new Error( 'network error' ) );
+ const instance = ValidationError({ field: 'email' });
+ instance.from(new Error('network error'));
- expect( instance.fields.field ).toBe( 'email' );
- expect( instance.message ).toBe( 'Field "email" is invalid' );
- } );
- } );
+ expect(instance.fields.field).toBe('email');
+ expect(instance.message).toBe('Field "email" is invalid');
+ });
+ });
- describe( 'edge cases', () => {
- it( 'should work with errors without causes property', () => {
- const AppError = error( { name: 'AppError' } );
+ describe('edge cases', () => {
+ it('should work with errors without causes property', () => {
+ const AppError = error({ name: 'AppError' });
const instance = AppError();
// Native errors don't have causes property
- instance.from( new Error( 'native' ) );
+ instance.from(new Error('native'));
- expect( instance.cause ).toBeInstanceOf( Error );
- expect( instance.causes ).toHaveLength( 1 );
- } );
+ expect(instance.cause).toBeInstanceOf(Error);
+ expect(instance.causes).toHaveLength(1);
+ });
- it( 'should work with custom errors that have causes', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( { name: 'ValidationError' } );
+ it('should work with custom errors that have causes', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({ name: 'ValidationError' });
const innerError = AppError();
- innerError.from( new Error( 'inner cause' ) );
+ innerError.from(new Error('inner cause'));
const outerError = ValidationError();
- outerError.from( innerError );
+ outerError.from(innerError);
- expect( outerError.causes ).toHaveLength( 2 );
+ expect(outerError.causes).toHaveLength(2);
// Direct cause is innerError, then nativeError (from innerError's chain)
- expect( outerError.cause ).toBe( innerError );
- expect( outerError.causes ).toContain( innerError );
+ expect(outerError.cause).toBe(innerError);
+ expect(outerError.causes).toContain(innerError);
// The native error with 'inner cause' is in the causes chain
- const nativeInCauses = outerError.causes.find( c => c.message === 'inner cause' );
- expect( nativeInCauses ).toBeDefined();
- } );
- } );
-} );
+ const nativeInCauses = outerError.causes.find((c) => c.message === 'inner cause');
+ expect(nativeInCauses).toBeDefined();
+ });
+ });
+});
diff --git a/packages/errors/tests/is.test.ts b/packages/errors/tests/is.test.ts
index 5070ad4..7716923 100644
--- a/packages/errors/tests/is.test.ts
+++ b/packages/errors/tests/is.test.ts
@@ -5,160 +5,160 @@
import { describe, it, expect } from 'vitest';
import { error, is } from '../src/index.js';
-describe( 'is() function', () => {
- describe( 'basic usage', () => {
- it( 'should return true for exact match', () => {
- const TestError = error( { name: 'TestError' } );
+describe('is() function', () => {
+ describe('basic usage', () => {
+ it('should return true for exact match', () => {
+ const TestError = error({ name: 'TestError' });
const err = TestError();
- expect( is( err, TestError ) ).toBe( true );
- } );
+ expect(is(err, TestError)).toBe(true);
+ });
- it( 'should return false for non-matching types', () => {
- const ErrorA = error( { name: 'ErrorA' } );
- const ErrorB = error( { name: 'ErrorB' } );
+ it('should return false for non-matching types', () => {
+ const ErrorA = error({ name: 'ErrorA' });
+ const ErrorB = error({ name: 'ErrorB' });
const err = ErrorA();
- expect( is( err, ErrorB ) ).toBe( false );
- } );
+ expect(is(err, ErrorB)).toBe(false);
+ });
- it( 'should return false for null/undefined', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should return false for null/undefined', () => {
+ const TestError = error({ name: 'TestError' });
- expect( is( null, TestError ) ).toBe( false );
- expect( is( undefined, TestError ) ).toBe( false );
- } );
- } );
+ expect(is(null, TestError)).toBe(false);
+ expect(is(undefined, TestError)).toBe(false);
+ });
+ });
- describe( 'single inheritance', () => {
- it( 'should return true for child error types', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( {
+ describe('single inheritance', () => {
+ it('should return true for child error types', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
- } );
+ });
const err = ValidationError();
- expect( is( err, ValidationError ) ).toBe( true );
- expect( is( err, AppError ) ).toBe( true );
- } );
+ expect(is(err, ValidationError)).toBe(true);
+ expect(is(err, AppError)).toBe(true);
+ });
- it( 'should return false for parent when checking child', () => {
- const AppError = error( { name: 'AppError' } );
- const ValidationError = error( {
+ it('should return false for parent when checking child', () => {
+ const AppError = error({ name: 'AppError' });
+ const ValidationError = error({
name: 'ValidationError',
inherits: AppError,
- } );
+ });
const err = AppError();
- expect( is( err, ValidationError ) ).toBe( false );
- expect( is( err, AppError ) ).toBe( true );
- } );
- } );
+ expect(is(err, ValidationError)).toBe(false);
+ expect(is(err, AppError)).toBe(true);
+ });
+ });
- describe( 'multiple inheritance', () => {
- it( 'should return true for errors inheriting from multiple parents', () => {
- const NetworkError = error( { name: 'NetworkError' } );
- const StorageError = error( { name: 'StorageError' } );
- const CombinedError = error( {
+ describe('multiple inheritance', () => {
+ it('should return true for errors inheriting from multiple parents', () => {
+ const NetworkError = error({ name: 'NetworkError' });
+ const StorageError = error({ name: 'StorageError' });
+ const CombinedError = error({
name: 'CombinedError',
inherits: [NetworkError, StorageError],
- } );
+ });
const err = CombinedError();
- expect( is( err, CombinedError ) ).toBe( true );
- expect( is( err, NetworkError ) ).toBe( true );
- expect( is( err, StorageError ) ).toBe( true );
- } );
+ expect(is(err, CombinedError)).toBe(true);
+ expect(is(err, NetworkError)).toBe(true);
+ expect(is(err, StorageError)).toBe(true);
+ });
- it( 'should return true in deep inheritance chains', () => {
- const AppError = error( { name: 'AppError' } );
- const DomainError = error( {
+ it('should return true in deep inheritance chains', () => {
+ const AppError = error({ name: 'AppError' });
+ const DomainError = error({
name: 'DomainError',
inherits: AppError,
- } );
- const ValidationError = error( {
+ });
+ const ValidationError = error({
name: 'ValidationError',
inherits: DomainError,
- } );
+ });
const err = ValidationError();
- expect( is( err, ValidationError ) ).toBe( true );
- expect( is( err, DomainError ) ).toBe( true );
- expect( is( err, AppError ) ).toBe( true );
- } );
- } );
+ expect(is(err, ValidationError)).toBe(true);
+ expect(is(err, DomainError)).toBe(true);
+ expect(is(err, AppError)).toBe(true);
+ });
+ });
- describe( 'native errors', () => {
- it( 'should work with SyntaxError', () => {
+ describe('native errors', () => {
+ it('should work with SyntaxError', () => {
try {
- JSON.parse( 'invalid' );
- } catch ( err ) {
- expect( is( err, SyntaxError ) ).toBe( true );
- expect( is( err, Error ) ).toBe( true );
+ JSON.parse('invalid');
+ } catch (err) {
+ expect(is(err, SyntaxError)).toBe(true);
+ expect(is(err, Error)).toBe(true);
}
- } );
+ });
- it( 'should work with TypeError', () => {
+ it('should work with TypeError', () => {
try {
const fn: unknown = null;
- ( fn as { method: unknown } ).method();
- } catch ( err ) {
- expect( is( err, TypeError ) ).toBe( true );
+ (fn as { method: unknown }).method();
+ } catch (err) {
+ expect(is(err, TypeError)).toBe(true);
}
- } );
+ });
- it( 'should return false for native when checking custom', () => {
- const CustomError = error( { name: 'CustomError' } );
+ it('should return false for native when checking custom', () => {
+ const CustomError = error({ name: 'CustomError' });
try {
- JSON.parse( 'invalid' );
- } catch ( err ) {
- expect( is( err, CustomError ) ).toBe( false );
+ JSON.parse('invalid');
+ } catch (err) {
+ expect(is(err, CustomError)).toBe(false);
}
- } );
- } );
+ });
+ });
- describe( 'edge cases', () => {
- it( 'should handle non-error values', () => {
- const TestError = error( { name: 'TestError' } );
+ describe('edge cases', () => {
+ it('should handle non-error values', () => {
+ const TestError = error({ name: 'TestError' });
- expect( is( 'string', TestError ) ).toBe( false );
- expect( is( 123, TestError ) ).toBe( false );
- expect( is( {}, TestError ) ).toBe( false );
- expect( is( [], TestError ) ).toBe( false );
- } );
+ expect(is('string', TestError)).toBe(false);
+ expect(is(123, TestError)).toBe(false);
+ expect(is({}, TestError)).toBe(false);
+ expect(is([], TestError)).toBe(false);
+ });
- it( 'should handle native errors without factory marker', () => {
- const TestError = error( { name: 'TestError' } );
- const nativeErr = new Error( 'test' );
+ it('should handle native errors without factory marker', () => {
+ const TestError = error({ name: 'TestError' });
+ const nativeErr = new Error('test');
- expect( is( nativeErr, TestError ) ).toBe( false );
- } );
+ expect(is(nativeErr, TestError)).toBe(false);
+ });
- it( 'should return false for native errors when checking factory', () => {
- const TestError = error( { name: 'TestError' } );
- const nativeErr = new Error( 'test' );
+ it('should return false for native errors when checking factory', () => {
+ const TestError = error({ name: 'TestError' });
+ const nativeErr = new Error('test');
- expect( is( nativeErr, TestError ) ).toBe( false );
- } );
- } );
+ expect(is(nativeErr, TestError)).toBe(false);
+ });
+ });
- describe( 'instanceof compatibility', () => {
- it( 'should return true for instanceof Error checks', () => {
- const TestError = error( { name: 'TestError' } );
+ describe('instanceof compatibility', () => {
+ it('should return true for instanceof Error checks', () => {
+ const TestError = error({ name: 'TestError' });
const err = TestError();
- expect( err instanceof Error ).toBe( true );
- } );
+ expect(err instanceof Error).toBe(true);
+ });
- it( 'should work alongside native instanceof', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should work alongside native instanceof', () => {
+ const TestError = error({ name: 'TestError' });
const err = TestError();
// Both should work
- expect( err instanceof Error ).toBe( true );
- expect( is( err, TestError ) ).toBe( true );
- } );
- } );
-} );
\ No newline at end of file
+ expect(err instanceof Error).toBe(true);
+ expect(is(err, TestError)).toBe(true);
+ });
+ });
+});
diff --git a/packages/errors/tests/raise.test.ts b/packages/errors/tests/raise.test.ts
index 4b1db86..11c47d7 100644
--- a/packages/errors/tests/raise.test.ts
+++ b/packages/errors/tests/raise.test.ts
@@ -7,101 +7,101 @@ import { error } from '../src/error/error.js';
import { raise } from '../src/raise/index.js';
import type { ErrorInstance } from '../src/error/types.js';
-describe( 'raise() function', () => {
- describe( 'basic usage', () => {
- it( 'should throw the error instance', () => {
- const TestError = error( { name: 'TestError' } );
+describe('raise() function', () => {
+ describe('basic usage', () => {
+ it('should throw the error instance', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( () => raise( instance ) ).toThrow( instance );
- } );
+ expect(() => raise(instance)).toThrow(instance);
+ });
- it( 'should have never return type (compile-time verification)', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should have never return type (compile-time verification)', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
// TypeScript should infer that this function never returns
// The variable assignment itself should fail at compile time if return type is wrong
- const throwFn: () => never = () => raise( instance );
+ const throwFn: () => never = () => raise(instance);
// If we get here, TypeScript accepted the never return type
- expect( typeof throwFn ).toBe( 'function' );
- } );
+ expect(typeof throwFn).toBe('function');
+ });
- it( 'should preserve error properties on thrown error', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should preserve error properties on thrown error', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
try {
- raise( instance );
- } catch ( err ) {
+ raise(instance);
+ } catch (err) {
const caught = err as ErrorInstance;
- expect( caught.name ).toBe( 'TestError' );
- expect( caught.message ).toBe( 'TestError' );
- expect( caught.stack ).toBeDefined();
+ expect(caught.name).toBe('TestError');
+ expect(caught.message).toBe('TestError');
+ expect(caught.stack).toBeDefined();
}
- } );
- } );
+ });
+ });
- describe( 'with native throw', () => {
- it( 'should work with native throw syntax', () => {
- const TestError = error( { name: 'TestError' } );
+ describe('with native throw', () => {
+ it('should work with native throw syntax', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( () => {
+ expect(() => {
throw instance;
- } ).toThrow( instance );
- } );
- } );
+ }).toThrow(instance);
+ });
+ });
- describe( 'with fields', () => {
- it( 'should throw error with fields', () => {
- const ValidationError = error<{ field: string }>( {
+ describe('with fields', () => {
+ it('should throw error with fields', () => {
+ const ValidationError = error<{ field: string }>({
name: 'ValidationError',
message: 'Field "{field}" is invalid',
- } );
- const instance = ValidationError( { field: 'email' } );
+ });
+ const instance = ValidationError({ field: 'email' });
try {
- raise( instance );
- } catch ( err ) {
+ raise(instance);
+ } catch (err) {
const caught = err as ErrorInstance<{ field: string }>;
- expect( caught.name ).toBe( 'ValidationError' );
- expect( caught.message ).toBe( 'Field "email" is invalid' );
- expect( caught.fields.field ).toBe( 'email' );
+ expect(caught.name).toBe('ValidationError');
+ expect(caught.message).toBe('Field "email" is invalid');
+ expect(caught.fields.field).toBe('email');
}
- } );
- } );
+ });
+ });
- describe( 'chaining support', () => {
- it( 'should work when chained from error factory', () => {
- const TestError = error( { name: 'TestError' } );
+ describe('chaining support', () => {
+ it('should work when chained from error factory', () => {
+ const TestError = error({ name: 'TestError' });
- expect( () => raise( TestError() ) ).toThrow();
- } );
+ expect(() => raise(TestError())).toThrow();
+ });
- it( 'should throw after .from() is implemented', () => {
+ it('should throw after .from() is implemented', () => {
// Note: .from() will be implemented in Task 05
// For now, this test verifies raise() accepts ErrorInstance
- const TestError = error( { name: 'TestError' } );
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
- expect( () => raise( instance ) ).toThrow();
- } );
- } );
+ expect(() => raise(instance)).toThrow();
+ });
+ });
- describe( 'module export', () => {
- it( 'should be exported from the module', () => {
- expect( typeof raise ).toBe( 'function' );
- } );
+ describe('module export', () => {
+ it('should be exported from the module', () => {
+ expect(typeof raise).toBe('function');
+ });
- it( 'should have correct function signature', () => {
- const TestError = error( { name: 'TestError' } );
+ it('should have correct function signature', () => {
+ const TestError = error({ name: 'TestError' });
const instance = TestError();
// Type check: raise should accept ErrorInstance and return never
- const fn: ( error: ErrorInstance ) => never = raise;
- expect( fn ).toBe( raise );
- } );
- } );
-} );
+ const fn: (error: ErrorInstance) => never = raise;
+ expect(fn).toBe(raise);
+ });
+ });
+});
diff --git a/packages/errors/tsconfig.build.json b/packages/errors/tsconfig.build.json
index 9cd3db8..68eb2b6 100644
--- a/packages/errors/tsconfig.build.json
+++ b/packages/errors/tsconfig.build.json
@@ -9,4 +9,4 @@
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "tests/**/*.ts"]
-}
\ No newline at end of file
+}
diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json
index 2de859b..5960410 100644
--- a/packages/errors/tsconfig.json
+++ b/packages/errors/tsconfig.json
@@ -12,4 +12,4 @@
"esModuleInterop": true
},
"include": ["src"]
-}
\ No newline at end of file
+}
diff --git a/packages/errors/vitest.config.ts b/packages/errors/vitest.config.ts
index 903590d..cd31527 100644
--- a/packages/errors/vitest.config.ts
+++ b/packages/errors/vitest.config.ts
@@ -5,5 +5,5 @@ export default defineConfig({
globals: true,
environment: 'node',
include: ['tests/**/*.ts'],
- }
-});
\ No newline at end of file
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 55433dc..bbeda7f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -18,6 +18,12 @@ importers:
husky:
specifier: ^9.1.7
version: 9.1.7
+ lint-staged:
+ specifier: ^17.0.0
+ version: 17.0.8
+ prettier:
+ specifier: ^3.9.5
+ version: 3.9.5
turbo:
specifier: ^2.9.15
version: 2.9.15
@@ -48,10 +54,19 @@ importers:
react-dom:
specifier: ^19.2.6
version: 19.2.6(react@19.2.6)
+ shiki:
+ specifier: ^4.3.1
+ version: 4.3.1
tailwind-merge:
specifier: ^3.6.0
version: 3.6.0
+ zod:
+ specifier: ^4.4.3
+ version: 4.4.3
devDependencies:
+ '@eslint/js':
+ specifier: ^9.0.0
+ version: 9.39.4
'@tailwindcss/postcss':
specifier: ^4.3.0
version: 4.3.0
@@ -67,6 +82,9 @@ importers:
'@types/react-dom':
specifier: ^19.2.3
version: 19.2.3(@types/react@19.2.15)
+ eslint:
+ specifier: ^9.0.0
+ version: 9.39.4(jiti@2.7.0)
eslint-config-next:
specifier: 16.2.6
version: 16.2.6(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
@@ -1226,32 +1244,32 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
- '@shikijs/core@4.1.0':
- resolution: {integrity: sha512-jLJtSJeuFffqX6/inRE1zqU5aFv2hrszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==}
+ '@shikijs/core@4.3.1':
+ resolution: {integrity: sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==}
engines: {node: '>=20'}
- '@shikijs/engine-javascript@4.1.0':
- resolution: {integrity: sha512-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==}
+ '@shikijs/engine-javascript@4.3.1':
+ resolution: {integrity: sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==}
engines: {node: '>=20'}
- '@shikijs/engine-oniguruma@4.1.0':
- resolution: {integrity: sha512-axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw2BMj9GdeMnGxVB9PQb2xXlJYovslt/nz6giedAyOANkfc7hg==}
+ '@shikijs/engine-oniguruma@4.3.1':
+ resolution: {integrity: sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==}
engines: {node: '>=20'}
- '@shikijs/langs@4.1.0':
- resolution: {integrity: sha512-nwOMruEkbgdZfQ/b8CgpNBVOpvG1k0N5tbmgiFeqsan401+x3ILqlzZJowSla4Agmq4hG2Uf2wh5jLTEhR8VSg==}
+ '@shikijs/langs@4.3.1':
+ resolution: {integrity: sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==}
engines: {node: '>=20'}
- '@shikijs/primitive@4.1.0':
- resolution: {integrity: sha512-zx2/2Uwj2q9X3KSyYREEhXO23xBw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOT2bRRs6lAL945+LDvOAGw==}
+ '@shikijs/primitive@4.3.1':
+ resolution: {integrity: sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==}
engines: {node: '>=20'}
- '@shikijs/themes@4.1.0':
- resolution: {integrity: sha512-emCcTnUM7yO2wltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI//R4OFit9ewpg7H2/9tjOuXrT2v/Knw==}
+ '@shikijs/themes@4.3.1':
+ resolution: {integrity: sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==}
engines: {node: '>=20'}
- '@shikijs/types@4.1.0':
- resolution: {integrity: sha512-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska483tbayFYuw47UxwuPknI31bKnySfVQ/QW+jFL4rFdA==}
+ '@shikijs/types@4.3.1':
+ resolution: {integrity: sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==}
engines: {node: '>=20'}
'@shikijs/vscode-textmate@10.0.2':
@@ -1698,14 +1716,26 @@ packages:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
+ ansi-escapes@7.3.0:
+ resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
+ engines: {node: '>=18'}
+
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
+ engines: {node: '>=12'}
+
argparse@1.0.10:
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
@@ -1870,6 +1900,14 @@ packages:
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
+ cli-truncate@5.2.0:
+ resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
+ engines: {node: '>=20'}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -1985,6 +2023,9 @@ packages:
electron-to-chromium@1.5.362:
resolution: {integrity: sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==}
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@@ -2000,6 +2041,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ environment@1.1.0:
+ resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
+ engines: {node: '>=18'}
+
es-abstract@1.24.2:
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
engines: {node: '>= 0.4'}
@@ -2207,6 +2252,9 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ eventemitter3@5.0.4:
+ resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
+
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
@@ -2428,6 +2476,10 @@ packages:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
+ get-east-asian-width@1.6.0:
+ resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
+ engines: {node: '>=18'}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -2632,6 +2684,10 @@ packages:
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
+ is-fullwidth-code-point@5.1.0:
+ resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
+ engines: {node: '>=18'}
+
is-generator-function@1.1.2:
resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
engines: {node: '>= 0.4'}
@@ -2850,6 +2906,15 @@ packages:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
+ lint-staged@17.0.8:
+ resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==}
+ engines: {node: '>=22.22.1'}
+ hasBin: true
+
+ listr2@10.2.2:
+ resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==}
+ engines: {node: '>=22.13.0'}
+
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -2864,6 +2929,10 @@ packages:
lodash.startcase@4.4.0:
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
+ log-update@6.1.0:
+ resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
+ engines: {node: '>=18'}
+
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -3054,6 +3123,10 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
@@ -3174,6 +3247,10 @@ packages:
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
+
oniguruma-parser@0.12.2:
resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==}
@@ -3286,6 +3363,11 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
+ prettier@3.9.5:
+ resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -3423,10 +3505,17 @@ packages:
engines: {node: '>= 0.4'}
hasBin: true
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
rolldown@1.0.2:
resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -3493,8 +3582,8 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- shiki@4.1.0:
- resolution: {integrity: sha512-l/ABZPUR5v70jI10EzqfMS/I96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q==}
+ shiki@4.3.1:
+ resolution: {integrity: sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==}
engines: {node: '>=20'}
side-channel-list@1.0.1:
@@ -3524,6 +3613,14 @@ packages:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
+ slice-ansi@7.1.2:
+ resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
+ engines: {node: '>=18'}
+
+ slice-ansi@8.0.0:
+ resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
+ engines: {node: '>=20'}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -3554,6 +3651,18 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
+
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ string-width@8.2.2:
+ resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==}
+ engines: {node: '>=20'}
+
string.prototype.includes@2.0.1:
resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
engines: {node: '>= 0.4'}
@@ -3584,6 +3693,10 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
+ engines: {node: '>=12'}
+
strip-bom@3.0.0:
resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
engines: {node: '>=4'}
@@ -3640,6 +3753,10 @@ packages:
resolution: {integrity: sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==}
engines: {node: '>=18'}
+ tinyexec@1.2.4:
+ resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
+ engines: {node: '>=18'}
+
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
@@ -3899,6 +4016,14 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@10.0.0:
+ resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==}
+ engines: {node: '>=20'}
+
+ wrap-ansi@9.0.2:
+ resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
+ engines: {node: '>=18'}
+
xml-js@1.6.11:
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
hasBin: true
@@ -4987,40 +5112,40 @@ snapshots:
'@rtsao/scc@1.1.0': {}
- '@shikijs/core@4.1.0':
+ '@shikijs/core@4.3.1':
dependencies:
- '@shikijs/primitive': 4.1.0
- '@shikijs/types': 4.1.0
+ '@shikijs/primitive': 4.3.1
+ '@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
- '@shikijs/engine-javascript@4.1.0':
+ '@shikijs/engine-javascript@4.3.1':
dependencies:
- '@shikijs/types': 4.1.0
+ '@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.6
- '@shikijs/engine-oniguruma@4.1.0':
+ '@shikijs/engine-oniguruma@4.3.1':
dependencies:
- '@shikijs/types': 4.1.0
+ '@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
- '@shikijs/langs@4.1.0':
+ '@shikijs/langs@4.3.1':
dependencies:
- '@shikijs/types': 4.1.0
+ '@shikijs/types': 4.3.1
- '@shikijs/primitive@4.1.0':
+ '@shikijs/primitive@4.3.1':
dependencies:
- '@shikijs/types': 4.1.0
+ '@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
- '@shikijs/themes@4.1.0':
+ '@shikijs/themes@4.3.1':
dependencies:
- '@shikijs/types': 4.1.0
+ '@shikijs/types': 4.3.1
- '@shikijs/types@4.1.0':
+ '@shikijs/types@4.3.1':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -5400,12 +5525,20 @@ snapshots:
ansi-colors@4.1.3: {}
+ ansi-escapes@7.3.0:
+ dependencies:
+ environment: 1.1.0
+
ansi-regex@5.0.1: {}
+ ansi-regex@6.2.2: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
+ ansi-styles@6.2.3: {}
+
argparse@1.0.10:
dependencies:
sprintf-js: 1.0.3
@@ -5584,6 +5717,15 @@ snapshots:
dependencies:
clsx: 2.1.1
+ cli-cursor@5.0.0:
+ dependencies:
+ restore-cursor: 5.1.0
+
+ cli-truncate@5.2.0:
+ dependencies:
+ slice-ansi: 8.0.0
+ string-width: 8.2.2
+
client-only@0.0.1: {}
clsx@2.1.1: {}
@@ -5686,6 +5828,8 @@ snapshots:
electron-to-chromium@1.5.362: {}
+ emoji-regex@10.6.0: {}
+
emoji-regex@9.2.2: {}
enhanced-resolve@5.22.0:
@@ -5700,6 +5844,8 @@ snapshots:
entities@6.0.1: {}
+ environment@1.1.0: {}
+
es-abstract@1.24.2:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -6093,6 +6239,8 @@ snapshots:
esutils@2.0.3: {}
+ eventemitter3@5.0.4: {}
+
expect-type@1.3.0: {}
extend@3.0.2: {}
@@ -6192,7 +6340,7 @@ snapshots:
remark-gfm: 4.0.1
remark-rehype: 11.1.2
scroll-into-view-if-needed: 3.1.0
- shiki: 4.1.0
+ shiki: 4.3.1
tinyglobby: 0.2.16
unified: 11.0.5
unist-util-visit: 5.1.0
@@ -6264,7 +6412,7 @@ snapshots:
react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6)
rehype-raw: 7.0.0
scroll-into-view-if-needed: 3.1.0
- shiki: 4.1.0
+ shiki: 4.3.1
tailwind-merge: 3.6.0
unist-util-visit: 5.1.0
optionalDependencies:
@@ -6294,6 +6442,8 @@ snapshots:
gensync@1.0.0-beta.2: {}
+ get-east-asian-width@1.6.0: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -6580,6 +6730,10 @@ snapshots:
dependencies:
call-bound: 1.0.4
+ is-fullwidth-code-point@5.1.0:
+ dependencies:
+ get-east-asian-width: 1.6.0
+
is-generator-function@1.1.2:
dependencies:
call-bound: 1.0.4
@@ -6767,6 +6921,23 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
+ lint-staged@17.0.8:
+ dependencies:
+ listr2: 10.2.2
+ picomatch: 4.0.4
+ string-argv: 0.3.2
+ tinyexec: 1.2.4
+ optionalDependencies:
+ yaml: 2.9.0
+
+ listr2@10.2.2:
+ dependencies:
+ cli-truncate: 5.2.0
+ eventemitter3: 5.0.4
+ log-update: 6.1.0
+ rfdc: 1.4.1
+ wrap-ansi: 10.0.0
+
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@@ -6779,6 +6950,14 @@ snapshots:
lodash.startcase@4.4.0: {}
+ log-update@6.1.0:
+ dependencies:
+ ansi-escapes: 7.3.0
+ cli-cursor: 5.0.0
+ slice-ansi: 7.1.2
+ strip-ansi: 7.2.0
+ wrap-ansi: 9.0.2
+
longest-streak@3.1.0: {}
loose-envify@1.4.0:
@@ -7237,6 +7416,8 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.2
+ mimic-function@5.0.1: {}
+
minimatch@10.2.5:
dependencies:
brace-expansion: 5.0.6
@@ -7353,6 +7534,10 @@ snapshots:
obug@2.1.1: {}
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
oniguruma-parser@0.12.2: {}
oniguruma-to-es@4.3.6:
@@ -7460,6 +7645,8 @@ snapshots:
prettier@2.8.8: {}
+ prettier@3.9.5: {}
+
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -7657,8 +7844,15 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
reusify@1.1.0: {}
+ rfdc@1.4.1: {}
+
rolldown@1.0.2:
dependencies:
'@oxc-project/types': 0.132.0
@@ -7777,14 +7971,14 @@ snapshots:
shebang-regex@3.0.0: {}
- shiki@4.1.0:
+ shiki@4.3.1:
dependencies:
- '@shikijs/core': 4.1.0
- '@shikijs/engine-javascript': 4.1.0
- '@shikijs/engine-oniguruma': 4.1.0
- '@shikijs/langs': 4.1.0
- '@shikijs/themes': 4.1.0
- '@shikijs/types': 4.1.0
+ '@shikijs/core': 4.3.1
+ '@shikijs/engine-javascript': 4.3.1
+ '@shikijs/engine-oniguruma': 4.3.1
+ '@shikijs/langs': 4.3.1
+ '@shikijs/themes': 4.3.1
+ '@shikijs/types': 4.3.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.4
@@ -7822,6 +8016,16 @@ snapshots:
slash@3.0.0: {}
+ slice-ansi@7.1.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
+ slice-ansi@8.0.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
source-map-js@1.2.1: {}
source-map@0.7.6: {}
@@ -7846,6 +8050,19 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
+ string-argv@0.3.2: {}
+
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.6.0
+ strip-ansi: 7.2.0
+
+ string-width@8.2.2:
+ dependencies:
+ get-east-asian-width: 1.6.0
+ strip-ansi: 7.2.0
+
string.prototype.includes@2.0.1:
dependencies:
call-bind: 1.0.9
@@ -7905,6 +8122,10 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.2.2
+
strip-bom@3.0.0: {}
strip-json-comments@3.1.1: {}
@@ -7942,6 +8163,8 @@ snapshots:
tinyexec@1.2.2: {}
+ tinyexec@1.2.4: {}
+
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
@@ -8244,6 +8467,18 @@ snapshots:
word-wrap@1.2.5: {}
+ wrap-ansi@10.0.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 8.2.2
+ strip-ansi: 7.2.0
+
+ wrap-ansi@9.0.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
xml-js@1.6.11:
dependencies:
sax: 1.6.0
diff --git a/turbo.json b/turbo.json
index 7e70834..fe26bb6 100644
--- a/turbo.json
+++ b/turbo.json
@@ -20,4 +20,4 @@
"dependsOn": ["^build"]
}
}
-}
\ No newline at end of file
+}
diff --git a/vercel.json b/vercel.json
index c11e633..43a7ed3 100644
--- a/vercel.json
+++ b/vercel.json
@@ -2,4 +2,4 @@
"buildCommand": "pnpm --filter web build",
"outputDirectory": "apps/web/.next",
"installCommand": "pnpm install"
-}
\ No newline at end of file
+}