Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/add-addnote-method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@deessejs/errors": minor
---

Add `ErrorInstance.addNote(note)` for attaching runtime context to errors (PEP 678, mirrors Python 3.11). Returns the instance for chaining. The `notes: string[]` property was already implemented; the method was missing despite being documented. Closes #29.
6 changes: 6 additions & 0 deletions packages/errors/src/error/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ export const error = <const T extends Record<string, unknown> = Record<string, n
return instance;
};

// Add .addNote() method for runtime context (PEP 678)
instance.addNote = (note: string): ErrorInstance<T> => {
instance.notes.push(note);
return instance;
};

// Mark this instance as created by this factory (for is() checks)
// Use callable to avoid generic parameter conflicts
(instance as unknown as Record<typeof FACTORY_SYMBOL, () => unknown>)[FACTORY_SYMBOL] =
Expand Down
19 changes: 16 additions & 3 deletions packages/errors/src/error/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,29 @@ export type ErrorFactory<TFields extends Record<string, unknown> = Record<string
/**
* Error instance returned by an ErrorFactory.
* Contains all standard Error properties plus additional domain-specific fields.
*
* Note: .addNote() is implemented in a separate task.
*/
export type ErrorInstance<TFields extends Record<string, unknown> = Record<string, never>> =
ErrorInstanceCore & {
/** User-defined fields from Standard Schema */
fields: TFields;
// TODO: Implement .addNote() method (Task XX)
/** Additional notes added via .addNote() */
notes: string[];
/**
* Adds a note to this error instance.
*
* Notes provide runtime context that complements the structured fields.
* Patterned after Python 3.11's `BaseException.add_note()` (PEP 678).
*
* @param note - The note text to attach
* @returns This error instance for chaining
*
* @example
* ```typescript
* const err = AppError().addNote('Attempt 1 failed').addNote('Retrying...');
* // err.notes === ['Attempt 1 failed', 'Retrying...']
* ```
*/
addNote(note: string): ErrorInstance<TFields>;
/**
* Chains a cause error to this error.
*
Expand Down
40 changes: 40 additions & 0 deletions packages/errors/tests/error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,46 @@ describe('error() factory function', () => {
});
});

describe('.addNote()', () => {
it('should attach a single note to a fresh instance', () => {
const AppError = error({ name: 'AppError' });
const instance = AppError();

const returned = instance.addNote('first attempt failed');

expect(returned).toBe(instance);
expect(instance.notes).toEqual(['first attempt failed']);
});

it('should append multiple notes in order', () => {
const AppError = error({ name: 'AppError' });
const instance = AppError()
.addNote('Attempt 1 failed')
.addNote('Retrying...')
.addNote('Attempt 2 failed');

expect(instance.notes).toEqual(['Attempt 1 failed', 'Retrying...', 'Attempt 2 failed']);
});

it('should preserve notes through .from() chaining', () => {
const AppError = error({ name: 'AppError' });
const cause = new Error('underlying failure');
const instance = AppError().addNote('context A').from(cause).addNote('context B');

expect(instance.notes).toEqual(['context A', 'context B']);
expect(instance.cause).toBe(cause);
});

it('should isolate notes between sibling instances', () => {
const AppError = error({ name: 'AppError' });
const a = AppError().addNote('only on a');
const b = AppError();

expect(a.notes).toEqual(['only on a']);
expect(b.notes).toEqual([]);
});
});

describe('inherits option', () => {
it('should support single inheritance', () => {
const AppError = error({ name: 'AppError' });
Expand Down
Loading