Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
45972af
feat: implement error() factory function
martyy-code May 29, 2026
03ef927
refactor: separate types, code, and tests into distinct folders
martyy-code May 29, 2026
da781e4
fix: address PR review comments
martyy-code May 29, 2026
66d6ab3
style: prefer const over function, type over interface
martyy-code May 29, 2026
e396244
refactor: remove httpStatus feature
martyy-code May 29, 2026
f5ac6ae
refactor: reorganize source into src/error folder
martyy-code May 29, 2026
16069bb
refactor: flatten types to single file
martyy-code May 29, 2026
3aaab20
refactor: extract captureStack to separate capture.ts file
martyy-code May 29, 2026
b838f94
refactor: extract format utilities and create constants file
martyy-code May 29, 2026
6d53e3d
refactor: rename template to rawMessage
martyy-code May 29, 2026
8565e70
refactor: use generic T extends Record<string, unknown> in format.ts
martyy-code May 29, 2026
2a5a492
refactor: use shared constants in formatTemplate
martyy-code May 29, 2026
8021b2a
feat: implement Template Literal Types for compile-time key validation
martyy-code May 29, 2026
0894207
style: clean up capture.ts
martyy-code May 29, 2026
1d4d87b
fix: update lockfile after moving @types/node to devDependencies
martyy-code May 29, 2026
eea65ac
Merge pull request #1 from nesalia-inc/task/01-error-factory
codewizdave May 29, 2026
d82a9cd
feat: implement raise() function
martyy-code May 29, 2026
6a66b4e
refactor: reorganize raise into own folder
martyy-code May 29, 2026
371972d
refactor: move raise to src/raise/index.ts
martyy-code May 29, 2026
b32eaad
Merge pull request #2 from nesalia-inc/task/02-raise-function
codewizdave May 29, 2026
83aceee
feat: implement is() type checking function
martyy-code May 29, 2026
7b86409
refactor: improve is() implementation based on review
martyy-code May 29, 2026
7623027
Merge pull request #3 from nesalia-inc/task/03-is-function
codewizdave May 29, 2026
f19cbee
docs: mark task-04 as complete
martyy-code May 29, 2026
8314474
feat: implement .from() method for exception chaining
martyy-code May 29, 2026
cb30c76
Merge pull request #4 from nesalia-inc/task/05-from-method
codewizdave May 29, 2026
20e3fda
feat: implement causes() function for cause chain traversal
martyy-code May 29, 2026
fa6f4e3
docs: mark task-06 as complete
martyy-code May 29, 2026
a08b237
Merge pull request #5 from nesalia-inc/task/06-causes-function
codewizdave May 29, 2026
784dd91
docs: mark task-07 as complete
martyy-code May 29, 2026
1e266dc
docs: mark tasks 04, 07, 08 as complete
martyy-code May 29, 2026
6cc6ce0
feat: add examples directory with runnable TypeScript examples
martyy-code May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/agent-memory/typescript-expert/MEMORY.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
# TypeScript Expert Memory Index

## Reference Documents

- [record-string-unknown-pattern](record-string-unknown-pattern.md) — Senior to Principal TypeScript patterns
- [stack-capture-patterns](stack-capture-patterns.md) — Stack trace handling patterns
- [principal-level-error-system](principal-level-error-system.md) — Error factory system design patterns
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
---
name: principal-level-error-system
description: Principal Level patterns for error factory - type inference, runtime validation, polymorphism, branding
type: reference
---

# Principal Level: Error Factory System

## 1. Automatic Type Inference from Schema

Automatically extract output type from Standard Schema.

```typescript
export const error = <
const TSchema extends StandardSchemaV1 | undefined = undefined,
TData = TSchema extends StandardSchemaV1
? StandardSchemaV1.InferOutput<TSchema>
: Record<string, never>
>(config: {
name: string;
fields?: TSchema;
inherits?: ErrorFactory | ErrorFactory[];
message?: string;
}): ErrorFactory<TData> => { ... }

// Usage - types inferred automatically
const ValidationError = error({
name: 'ValidationError',
fields: z.object({ field: z.string() }),
});

// err.fields.field is automatically typed as string
```

### Why This Matters

- No manual generic needed
- Schema drives the entire type system
- Compile-time validation of required fields

---

## 2. Runtime Contract Enforcement

Always validate input against schema before creating instance.

```typescript
const ErrorFactoryInstance = (input?: Partial<TData>): ErrorInstance<TData> => {
if (fields) {
const result = fields['~standard'].validate(input ?? {});

if (result instanceof Promise) {
// Note: async validation not supported in sync factory
}

if (result.issues) {
// Throw if provided fields don't match schema
// Ensures ErrorInstance never contains invalid data
}
}
// ... rest
};
```

### Why This Matters

- `ErrorInstance` is always valid
- Catch errors early
- Schema is a true contract

---

## 3. Polymorphism: The `is` Utility

Check inheritance relationships across the chain.

```typescript
export const is = (err: unknown, factory: ErrorFactory): boolean => {
if (!err || typeof err !== 'object' || !('_factory' in err)) {
return false;
}

let current: ErrorFactory | ErrorFactory[] | undefined =
(err as ErrorInstance)._factory;

const check = (f: ErrorFactory): boolean => {
if (f === factory) return true;
if (Array.isArray(f.inherits)) return f.inherits.some(check);
if (f.inherits) return check(f.inherits);
return false;
};

return check(current as ErrorFactory);
};

// Usage
const AppError = error({ name: 'AppError' });
const ValidationError = error({ name: 'ValidationError', inherits: AppError });

const err = createValidationError();
is(err, AppError); // ✅ true
is(err, ValidationError); // ✅ true
is(err, NetworkError); // ✅ false
```

### Why This Matters

- `instanceof` doesn't work with plain objects
- Inheritance is functional, not just metadata
- Type-safe error checking

---

## 4. Nominal Typing via Branding

Prevent structural type collisions.

```typescript
export type ErrorInstance<
TFields extends Record<string, unknown>,
Name extends string
> = {
readonly __brand: Name;
name: Name;
fields: TFields;
// ... other properties
};

// Usage - errors are nominally typed
const ValidationError = error({ name: 'ValidationError' });
const NetworkError = error({ name: 'NetworkError' });

// These are different types even with same shape
declare function processValidation(err: ErrorInstance<{}, 'ValidationError'>);
declare function processNetwork(err: ErrorInstance<{}, 'NetworkError'>);

processValidation(NetworkError()); // ❌ Type error
```

### Why This Matters

- TypeScript uses structural typing by default
- Branding creates nominal typing
- Prevents passing wrong error type to functions

---

## Summary Table

| Level | Focus | Key Feature |
|:---|:---|:---|
| Senior | Clean utilities | Regex, JSDoc, constants |
| Staff | Abstraction | StandardSchemaV1, inherits |
| Principal | Integrity | Auto-inference, runtime validation, is(), branding |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
---
name: record-string-unknown-pattern
description: TypeScript patterns from Senior to Principal/Staff level
type: reference
---

# TypeScript Patterns: Senior to Principal/Staff Level

## Senior Pattern: `T extends Record<string, unknown>`

Use for generic functions accepting dictionary-like objects.

### Why This Pattern

| Pattern | Level | Why |
|:---|:---|:---|
| `T extends any` | Junior | No constraint |
| `T extends object` | Intermediate | Too broad |
| `T extends Record<string, any>` | Intermediate | Unsafe values |
| **`T extends Record<string, unknown>`** | **Senior** | Safe + explicit |

### Key Points

1. **`unknown` vs `any`**: Forces type narrowing before use
2. **`Record` vs `object`**: Explicitly dictionary-like (not arrays/functions)
3. **Interface Gotcha**: Interfaces lack implicit index signatures

```typescript
interface UserInterface { name: string }
type UserType = { name: string }

process<T extends Record<string, unknown>>(obj: T) {}
process(UserType) // ✅ Works
process(UserInterface) // ❌ Error
```

---

## Principal/Staff Pattern: Template Literal Types

Extract keys from template string at compile-time for type-safe data.

```typescript
type ExtractKeys<S extends string> =
S extends `${string}{${infer Key}}${infer Rest}`
? (Key extends `${infer RealKey}:${string}` ? RealKey : Key) | ExtractKeys<Rest>
: never;

const formatTemplate = <S extends string>(
template: S,
data: Record<ExtractKeys<S>, unknown>
): string => { ... }

// Usage:
formatTemplate("Hello {name}", { name: "Alice" }); // ✅ Works
formatTemplate("Hello {name}", { age: 30 }); // ❌ Error: missing 'name'
```

### Why This Matters

- **Compile-time validation**: Missing keys are caught at compile time
- **No runtime surprises**: API forces correct usage
- **Self-documenting**: Template string defines required keys

---

## Senior-Level Regex State Management

Global regex with `/g` flag is stateful. Always reset `lastIndex`:

```typescript
const REGEX = /\{(\w+)(?::(\w+))?\}/g;

const hasTemplatePlaceholders = (message: string): boolean => {
REGEX.lastIndex = 0; // Reset before each use
return REGEX.test(message);
};
```

**Without reset**: Second call might return `false` even when pattern exists.

---

## Summary of Levels

| Level | Technique | Benefit |
|:---|:---|:---|
| Junior | `any`, no constraints | Works, but unsafe |
| Intermediate | `object`, `Record<string, any>` | Shape correct |
| Senior | `Record<string, unknown>` | Type-safe |
| Principal/Staff | Template Literal Types | Compile-time key validation |
105 changes: 105 additions & 0 deletions .claude/agent-memory/typescript-expert/stack-capture-patterns.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
---
name: stack-capture-patterns
description: Senior to Expert level patterns for stack trace handling
type: reference
---

# Stack Capture Patterns: Senior to Expert Level

## Senior Pattern: String-Based Stack Filtering

Clean up stack traces by filtering internal frames.

```typescript
const STACK_FRAME_PATTERN = /^\s+at\s+/i;

const captureStack = (message: string): string => {
const stack = new Error().stack || '';

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++) {
if (STACK_FRAME_PATTERN.test(lines[i])) {
startIndex = i;
break;
}
}

// Filter internal frames
for (let i = startIndex; i < lines.length; i++) {
const line = lines[i];
if (line.includes('node_modules')) continue;
if (line.includes('__vite')) continue;
cleanedLines.push(line);
}

return cleanedLines.join('\n');
};
```

### Key Senior Points

1. **DX Focus**: Hide `node_modules/@deessejs` and `__vite` to show user-relevant frames
2. **Environmental Awareness**: Document V8-specific nature of `Error.stack`
3. **Defensive Programming**: Fallback to `|| ''` when stack is undefined
4. **Maintainable**: Use constants for regex patterns

---

## Expert Pattern: `Error.captureStackTrace` (V8 Only)

Use V8's built-in mechanism for faster, cleaner stack capture.

```typescript
const captureStack = (message: string): string => {
// Check for V8 environment
if (typeof Error.captureStackTrace === 'function') {
const container: { stack: string } = { stack: '' };

// Tells V8 to capture stack, stopping at captureStack function
Error.captureStackTrace(container, captureStack);

const cleanedStack = `Error: ${message}\n` + container.stack
.split('\n')
.slice(1) // Remove captureStack frame
.filter(line =>
!line.includes('node_modules') &&
!line.includes('__vite')
)
.join('\n');

return cleanedStack;
}

// Fallback for non-V8 environments
return `Error: ${message}`;
};
```

### Why Expert Level

- **Performance**: Native V8 handling vs string manipulation
- **Cleaner output**: V8 controls frame inclusion precisely
- **Same filtering**: Still provides DX-focused output

---

## Summary: When to Use Which

| Level | Method | When |
|:---|:---|:---|
| Senior | String manipulation | Cross-environment compatibility |
| Expert | `Error.captureStackTrace` | V8-only, performance-critical |
| Always | Filter internal frames | End-user DX priority |

---

## Key Takeaways

1. **DX First**: Stack traces should point to user code, not library internals
2. **Document Boundaries**: `Error.stack` is V8-specific, document limitations
3. **Defensive**: Always handle `undefined` stack cases
4. **Expert Option**: Use native APIs when available for better performance
Loading
Loading