From 172e81c0415c591639591a67939e2751d93bc82d Mon Sep 17 00:00:00 2001 From: martyy-code Date: Mon, 3 Aug 2026 15:00:22 +0200 Subject: [PATCH 1/2] feat(errors): implement ErrorInstance.addNote() Closes #29. Mirrors Python 3.11 PEP 678 (BaseException.add_note()). Was documented but never implemented; consumers got a TypeScript error when following the JSDoc examples. The notes: string[] storage was already wired up; only the method was missing. - types.ts: declare addNote(note: string): ErrorInstance; drop the stale 'TODO: Implement .addNote()' comment and the 'implemented in a separate task' notice. - error.ts: implement addNote in the factory closure. Pushes to notes and returns this for chaining. - tests/error.test.ts: cover single note, chained notes, preservation through .from(), and isolation between siblings. Adds a changeset (minor bump) for the new API. --- .changeset/add-addnote-method.md | 5 +++ packages/errors/src/error/error.ts | 6 ++++ packages/errors/src/error/types.ts | 19 ++++++++++-- packages/errors/tests/error.test.ts | 47 +++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 .changeset/add-addnote-method.md diff --git a/.changeset/add-addnote-method.md b/.changeset/add-addnote-method.md new file mode 100644 index 0000000..3bfbab7 --- /dev/null +++ b/.changeset/add-addnote-method.md @@ -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. diff --git a/packages/errors/src/error/error.ts b/packages/errors/src/error/error.ts index 45abf37..9f420a8 100644 --- a/packages/errors/src/error/error.ts +++ b/packages/errors/src/error/error.ts @@ -119,6 +119,12 @@ export const error = = Record => { + 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 unknown>)[FACTORY_SYMBOL] = diff --git a/packages/errors/src/error/types.ts b/packages/errors/src/error/types.ts index d024dd0..41ab0f6 100644 --- a/packages/errors/src/error/types.ts +++ b/packages/errors/src/error/types.ts @@ -36,16 +36,29 @@ export type ErrorFactory = Record = Record> = 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; /** * Chains a cause error to this error. * diff --git a/packages/errors/tests/error.test.ts b/packages/errors/tests/error.test.ts index 864c921..f3f1f98 100644 --- a/packages/errors/tests/error.test.ts +++ b/packages/errors/tests/error.test.ts @@ -86,6 +86,53 @@ 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' }); From 4e2bfa139496d99cc69a8f0162f4196d745c0480 Mon Sep 17 00:00:00 2001 From: martyy-code Date: Mon, 3 Aug 2026 15:01:36 +0200 Subject: [PATCH 2/2] style: apply prettier to error.test.ts --- packages/errors/tests/error.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/errors/tests/error.test.ts b/packages/errors/tests/error.test.ts index f3f1f98..c885ea5 100644 --- a/packages/errors/tests/error.test.ts +++ b/packages/errors/tests/error.test.ts @@ -104,20 +104,13 @@ describe('error() factory function', () => { .addNote('Retrying...') .addNote('Attempt 2 failed'); - expect(instance.notes).toEqual([ - 'Attempt 1 failed', - 'Retrying...', - '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'); + const instance = AppError().addNote('context A').from(cause).addNote('context B'); expect(instance.notes).toEqual(['context A', 'context B']); expect(instance.cause).toBe(cause);