From be6a9a38f115d016a39bb998cfa5b5089817b017 Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Wed, 15 Apr 2026 20:01:08 +0100 Subject: [PATCH] feat: concatenate arrays in log.set merge --- .changeset/concatenate-arrays-set.md | 7 ++++++ packages/evlog/src/logger.ts | 2 ++ packages/evlog/src/types.ts | 5 +++- packages/evlog/test/logger.test.ts | 36 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 .changeset/concatenate-arrays-set.md diff --git a/.changeset/concatenate-arrays-set.md b/.changeset/concatenate-arrays-set.md new file mode 100644 index 00000000..37c98b1f --- /dev/null +++ b/.changeset/concatenate-arrays-set.md @@ -0,0 +1,7 @@ +--- +'evlog': minor +--- + +`log.set()` concatenates arrays when merging context for the same key. For example, `set({ items: [1, 2] })` followed by `set({ items: [3] })` yields `{ items: [1, 2, 3] }` instead of replacing with `[3]`. Plain objects are still deep-merged recursively; if either the existing or incoming value is not an array, the new value replaces the old one. + +**Breaking change:** Call sites that relied on the last `set` overwriting an array now accumulate elements. To replace a value at emit time, use `emit({ ... })` overrides or a different field name. diff --git a/packages/evlog/src/logger.ts b/packages/evlog/src/logger.ts index 5e921b8c..2b68b3a8 100644 --- a/packages/evlog/src/logger.ts +++ b/packages/evlog/src/logger.ts @@ -19,6 +19,8 @@ function mergeInto(target: Record, source: Record> = */ export interface RequestLogger> { /** - * Add context to the wide event (deep merge via defu) + * Add context to the wide event. Plain objects are merged recursively. + * When both the existing and incoming values for a key are arrays, elements are + * concatenated (existing order preserved, new elements appended). Otherwise the + * new value replaces the old one (including when only one side is an array). */ set: (context: FieldContext) => void diff --git a/packages/evlog/test/logger.test.ts b/packages/evlog/test/logger.test.ts index 82a55042..44865e39 100644 --- a/packages/evlog/test/logger.test.ts +++ b/packages/evlog/test/logger.test.ts @@ -252,6 +252,42 @@ describe('createRequestLogger', () => { expect(context.cart).toEqual({ items: ['item1'], total: 50 }) }) + it('concatenates arrays on the same key with set()', () => { + const logger = createRequestLogger({}) + + logger.set({ array: [1, 2] }) + logger.set({ array: [3] }) + + expect(logger.getContext().array).toEqual([1, 2, 3]) + }) + + it('concatenates nested arrays on the same key with set()', () => { + const logger = createRequestLogger({}) + + logger.set({ job: { steps: ['a'] } }) + logger.set({ job: { steps: ['b', 'c'] } }) + + expect(logger.getContext().job).toEqual({ steps: ['a', 'b', 'c'] }) + }) + + it('replaces array with non-array on the same key with set()', () => { + const logger = createRequestLogger({}) + + logger.set({ tags: ['a', 'b'] }) + logger.set({ tags: 'done' }) + + expect(logger.getContext().tags).toBe('done') + }) + + it('does not drop prior array elements when appending an empty array', () => { + const logger = createRequestLogger({}) + + logger.set({ ids: [1, 2] }) + logger.set({ ids: [] }) + + expect(logger.getContext().ids).toEqual([1, 2]) + }) + it('records error with error()', () => { const logger = createRequestLogger({}) const error = new Error('Payment failed')