From c075c12b8c04771b9b520a3f79bf1d932570181c Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Mon, 8 Sep 2025 19:18:06 +0200 Subject: [PATCH 1/4] feat(guards): add isDefined function --- packages/utilities/src/index.ts | 1 + .../lib/guards/is-defined.function.spec.ts | 25 +++++++++++++++++++ .../src/lib/guards/is-defined.function.ts | 6 +++++ 3 files changed, 32 insertions(+) create mode 100644 packages/utilities/src/lib/guards/is-defined.function.spec.ts create mode 100644 packages/utilities/src/lib/guards/is-defined.function.ts diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 417e7f5..371f122 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -4,6 +4,7 @@ export * from './lib/compare-with/compare-with.js' export * from './lib/cookie/parse-cookies.function.js' export * from './lib/enum/enum.js' export * from './lib/group-by/group-by.js' +export * from './lib/guards/is-defined.function.js' export * from './lib/http/http-constants.js' export * from './lib/json-stringify-replacer/json-stringify-replacer.function.js' export * from './lib/map-values-deep/map-values-deep.js' diff --git a/packages/utilities/src/lib/guards/is-defined.function.spec.ts b/packages/utilities/src/lib/guards/is-defined.function.spec.ts new file mode 100644 index 0000000..44ed2ff --- /dev/null +++ b/packages/utilities/src/lib/guards/is-defined.function.spec.ts @@ -0,0 +1,25 @@ +import { isDefined } from './is-defined.function.js' + +describe('isDefined()', () => { + test('returns false for undefined|null', () => { + expect(isDefined(null)).toBe(false) + expect(isDefined(undefined)).toBe(false) + }) + test(`when 0|''|NaN|emptyString`, () => { + expect(isDefined(0)).toBe(true) + expect(isDefined(-0)).toBe(true) + expect(isDefined('')).toBe(true) + expect(isDefined(NaN)).toBe(true) + expect(isDefined(1 / 0)).toBe(true) + }) + test('when empty objects|arrays', () => { + expect(isDefined([])).toBe(true) + expect(isDefined({})).toBe(true) + }) + test('when truthy values', () => { + expect(isDefined(5)).toBe(true) + expect(isDefined('ok')).toBe(true) + expect(isDefined('undefined')).toBe(true) + expect(isDefined('null')).toBe(true) + }) +}) diff --git a/packages/utilities/src/lib/guards/is-defined.function.ts b/packages/utilities/src/lib/guards/is-defined.function.ts new file mode 100644 index 0000000..8ccf63b --- /dev/null +++ b/packages/utilities/src/lib/guards/is-defined.function.ts @@ -0,0 +1,6 @@ +/** + * Checks if a value is defined (not undefined or null). + */ +export function isDefined(value: T | undefined | null): value is T { + return value !== undefined && value !== null +} From 50338cc6201c47e719455028033b318a9fb908c0 Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Mon, 8 Sep 2025 19:19:40 +0200 Subject: [PATCH 2/4] feat(object-utilities): add fn to pick props + assert defined from object --- packages/utilities/src/index.ts | 1 + .../pick-props-assert-defined.function.spec.ts | 12 ++++++++++++ .../pick-props-assert-defined.function.ts | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 packages/utilities/src/lib/object/pick-props-assert-defined.function.spec.ts create mode 100644 packages/utilities/src/lib/object/pick-props-assert-defined.function.ts diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 371f122..0160eb3 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -10,6 +10,7 @@ export * from './lib/json-stringify-replacer/json-stringify-replacer.function.js export * from './lib/map-values-deep/map-values-deep.js' export * from './lib/object/omit-props.function.js' export * from './lib/object/pick-props.function.js' +export * from './lib/object/pick-props-assert-defined.function.js' export * from './lib/promise/make-deferred.function.js' export * from './lib/math/clamp.function.js' export * from './lib/math/random-int.js' diff --git a/packages/utilities/src/lib/object/pick-props-assert-defined.function.spec.ts b/packages/utilities/src/lib/object/pick-props-assert-defined.function.spec.ts new file mode 100644 index 0000000..1caa104 --- /dev/null +++ b/packages/utilities/src/lib/object/pick-props-assert-defined.function.spec.ts @@ -0,0 +1,12 @@ +import { pickPropsAssertDefined } from './pick-props-assert-defined.function.js' + +describe('pickPropsAssertDefined', () => { + test('returns object with picked props', () => { + const obj = { a: true, b: 'foo', c: 42 } + expect(pickPropsAssertDefined(obj, ['a', 'c'])).toEqual({ a: true, c: 42 }) + }) + test('throws when picked prop values are null or undefined', () => { + const obj = { a: 'ok', b: null } + expect(() => pickPropsAssertDefined(obj, ['a', 'b'])).toThrow(Error) + }) +}) diff --git a/packages/utilities/src/lib/object/pick-props-assert-defined.function.ts b/packages/utilities/src/lib/object/pick-props-assert-defined.function.ts new file mode 100644 index 0000000..90f263f --- /dev/null +++ b/packages/utilities/src/lib/object/pick-props-assert-defined.function.ts @@ -0,0 +1,18 @@ +import { isDefined } from '../guards/is-defined.function.js' + +export type PickedPropsDefined = { + [key in K]-?: NonNullable +} + +/** + * returns an object containing the provided props with their respective value. throws if their value is null or undefined + */ +export function pickPropsAssertDefined(obj: T, props: readonly TProp[]): PickedPropsDefined { + const entries = props.map((p) => { + if (!isDefined(obj[p])) { + throw new Error(`Expected property "${String(p)}" to be defined. Was "${String(obj[p])}" instead`) + } + return [p, obj[p]] + }) + return Object.fromEntries(entries) +} \ No newline at end of file From 9451a1cf20555ea64c6aeab57e6db67bb75ac906 Mon Sep 17 00:00:00 2001 From: Simon Mumenthaler Date: Mon, 8 Sep 2025 19:20:07 +0200 Subject: [PATCH 3/4] feat(object-utilities): add getValueAssertDefined function --- packages/utilities/src/index.ts | 1 + .../get-value-assert-defined.function.spec.ts | 50 +++++++++++++++++++ .../get-value-assert-defined.function.ts | 14 ++++++ 3 files changed, 65 insertions(+) create mode 100644 packages/utilities/src/lib/object/get-value-assert-defined.function.spec.ts create mode 100644 packages/utilities/src/lib/object/get-value-assert-defined.function.ts diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts index 0160eb3..50ba67f 100644 --- a/packages/utilities/src/index.ts +++ b/packages/utilities/src/index.ts @@ -11,6 +11,7 @@ export * from './lib/map-values-deep/map-values-deep.js' export * from './lib/object/omit-props.function.js' export * from './lib/object/pick-props.function.js' export * from './lib/object/pick-props-assert-defined.function.js' +export * from './lib/object/get-value-assert-defined.function.js' export * from './lib/promise/make-deferred.function.js' export * from './lib/math/clamp.function.js' export * from './lib/math/random-int.js' diff --git a/packages/utilities/src/lib/object/get-value-assert-defined.function.spec.ts b/packages/utilities/src/lib/object/get-value-assert-defined.function.spec.ts new file mode 100644 index 0000000..2874d96 --- /dev/null +++ b/packages/utilities/src/lib/object/get-value-assert-defined.function.spec.ts @@ -0,0 +1,50 @@ +import { getValueAssertDefined } from './get-value-assert-defined.function.js' + +describe('getValueAssertDefined', () => { + interface Test { + x: string | null + y?: boolean + z: number | undefined + } + + describe('throws when not defined', () => { + const empty: Test = { x: null, z: undefined } + + test('when null', () => { + expect(() => getValueAssertDefined(empty, 'x')).toThrow() + }) + + test('when undefined', () => { + expect(() => getValueAssertDefined(empty, 'z')).toThrow() + }) + + test('when optional', () => { + expect(() => getValueAssertDefined(empty, 'y')).toThrow() + }) + }) + + describe('returns value when defined', () => { + const obj: Test = { + x: '', + y: false, + z: 0, + } + + test('when empty string', () => { + expect(getValueAssertDefined(obj, 'x')).toEqual('') + }) + test('when false', () => { + expect(getValueAssertDefined(obj, 'y')).toEqual(false) + }) + test('when zero', () => { + expect(getValueAssertDefined(obj, 'z')).toEqual(0) + }) + + test('makes it type safe', () => { + const tDefined: { num: number | null } = { num: 42 } + // assign to variable to ensure type safety + const res: number = getValueAssertDefined(tDefined, 'num') + expect(res).toEqual(42) + }) + }) +}) diff --git a/packages/utilities/src/lib/object/get-value-assert-defined.function.ts b/packages/utilities/src/lib/object/get-value-assert-defined.function.ts new file mode 100644 index 0000000..7afb76a --- /dev/null +++ b/packages/utilities/src/lib/object/get-value-assert-defined.function.ts @@ -0,0 +1,14 @@ +import { isDefined } from '../guards/is-defined.function.js' + +/** + * returns the value of the provided key on given object. throws if the value is null or undefined + */ +export function getValueAssertDefined(obj: T, key: K): NonNullable { + type X = NonNullable + const value: X | null | undefined = obj[key] + + if (!isDefined(value)) { + throw new Error(`Expected property "${String(key)}" to be defined. Was "${value}" instead`) + } + return value +} From 36e518e1e641b3b64bdba4d277370191e29c982d Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 8 Sep 2025 17:22:57 +0000 Subject: [PATCH 4/4] build(release): next version [skip_build] - @shiftcode/logger@3.0.1-pr62.0 - @shiftcode/utilities@4.1.0-pr62.0 --- package-lock.json | 6 +++--- packages/logger/package.json | 4 ++-- packages/utilities/package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4755ce..fccd544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14907,10 +14907,10 @@ }, "packages/logger": { "name": "@shiftcode/logger", - "version": "3.0.0", + "version": "3.0.1-pr62.0", "license": "UNLICENSED", "devDependencies": { - "@shiftcode/utilities": "^4.0.0" + "@shiftcode/utilities": "^4.1.0-pr62.0" }, "engines": { "node": "^20.0.0 || ^22.0.0" @@ -14957,7 +14957,7 @@ }, "packages/utilities": { "name": "@shiftcode/utilities", - "version": "4.0.0", + "version": "4.1.0-pr62.0", "license": "MIT", "engines": { "node": "^20.0.0 || ^22.0.0" diff --git a/packages/logger/package.json b/packages/logger/package.json index 5f42e0d..a5cd542 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@shiftcode/logger", - "version": "3.0.0", + "version": "3.0.1-pr62.0", "description": "logger for local and aws lambda execution", "repository": "https://github.com/shiftcode/sc-commons-public", "license": "UNLICENSED", @@ -35,7 +35,7 @@ "test:watch": "npm run test -- --watch" }, "devDependencies": { - "@shiftcode/utilities": "^4.0.0" + "@shiftcode/utilities": "^4.1.0-pr62.0" }, "peerDependencies": { "@shiftcode/utilities": "^4.0.0 || ^4.0.0-pr45" diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 80bb2ab..d48ee56 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -1,6 +1,6 @@ { "name": "@shiftcode/utilities", - "version": "4.0.0", + "version": "4.1.0-pr62.0", "description": "Contains some utilities", "repository": "https://github.com/shiftcode/sc-commons-public", "license": "MIT",