diff --git a/package-lock.json b/package-lock.json index f896898..fafc551 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@makerx/node-winston", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@makerx/node-winston", - "version": "2.0.0", + "version": "2.0.1", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", diff --git a/package.json b/package.json index c271e17..01d3ead 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@makerx/node-winston", - "version": "2.0.0", + "version": "2.0.1", "private": false, "description": "A set of winston formats, console transport and logger creation functions", "author": "MakerX", diff --git a/src/omit-nil-format.spec.ts b/src/omit-nil-format.spec.ts new file mode 100644 index 0000000..c56c57e --- /dev/null +++ b/src/omit-nil-format.spec.ts @@ -0,0 +1,37 @@ +import { TransformableInfo } from 'logform' +import { LEVEL, MESSAGE } from 'triple-beam' +import { describe, expect, it } from 'vitest' +import { omitNilFormat } from './omit-nil-format' + +const runOmitNil = (info: Record) => { + const transform = omitNilFormat() + const input = { [LEVEL]: 'info', [MESSAGE]: 'msg', level: 'info', message: 'msg', ...info } as TransformableInfo + return transform.transform(input, {}) as Record +} + +describe('omitNilFormat', () => { + it('removes null and undefined values', () => { + const result = runOmitNil({ a: 1, b: null, c: undefined, d: 'x' }) + + expect(result).toMatchObject({ a: 1, d: 'x' }) + expect(result).not.toHaveProperty('b') + expect(result).not.toHaveProperty('c') + }) + + it('keeps falsy-but-not-nil values', () => { + const result = runOmitNil({ zero: 0, empty: '', no: false }) + + expect(result).toMatchObject({ zero: 0, empty: '', no: false }) + }) + + it('preserves the LEVEL and MESSAGE symbols (regression: pg error length splat)', () => { + // pg errors carry a numeric `length` property; older es-toolkit/compat + // omitBy treated this object as array-like and stripped triple-beam symbols, + // breaking downstream colorize. + const result = runOmitNil({ length: 4, name: 'error', code: '23505' }) + + expect(result[LEVEL]).toBe('info') + expect(result[MESSAGE]).toBe('msg') + expect(result).toMatchObject({ length: 4, name: 'error', code: '23505' }) + }) +}) diff --git a/src/omit-nil-format.ts b/src/omit-nil-format.ts index a9a2ddc..f2322dc 100644 --- a/src/omit-nil-format.ts +++ b/src/omit-nil-format.ts @@ -1,5 +1,17 @@ -import { isNil, omitBy } from 'es-toolkit/compat' import { TransformableInfo } from 'logform' import { format } from 'winston' -export const omitNilFormat = format((info) => omitBy(info, isNil) as TransformableInfo) +// Hand-rolled rather than using es-toolkit/compat's omitBy: that treats any +// object with a numeric `length` property (e.g. a pg error spread via splat) +// as array-like, returns {}, and drops winston's LEVEL/MESSAGE symbols — +// breaking downstream formats like colorize. See toss/es-toolkit#1706. +export const omitNilFormat = format((info) => { + const out: Record = {} + for (const key of Object.keys(info)) { + if (info[key] != null) out[key] = info[key] + } + for (const sym of Object.getOwnPropertySymbols(info)) { + if (info[sym] != null) out[sym] = info[sym] + } + return out as TransformableInfo +})