From 2749d2e20a28c81f52a050df438338542bbbaa74 Mon Sep 17 00:00:00 2001 From: Bartosz Drozd Date: Thu, 12 Dec 2024 14:53:16 +0100 Subject: [PATCH 1/3] feat: Add AtLeastOne type --- src/common/at-least-one.ts | 2 ++ src/index.ts | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 src/common/at-least-one.ts diff --git a/src/common/at-least-one.ts b/src/common/at-least-one.ts new file mode 100644 index 0000000..e159257 --- /dev/null +++ b/src/common/at-least-one.ts @@ -0,0 +1,2 @@ +// can be used when you want to make at least one key of an object required +export type AtLeastOne }> = Partial & U[keyof U] diff --git a/src/index.ts b/src/index.ts index 7898741..18754bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,11 +16,11 @@ export { isEntityGoneError } from './errors/errorTypeGuards' export { ConfigScope } from './config/ConfigScope' export { ensureClosingSlashTransformer } from './config/configTransformers' export { createRangeValidator } from './config/configValidators' -export { - type EnvValueValidator, - type EnvValueTransformer, - type AppConfig, - type RedisConfig, +export type { + EnvValueValidator, + EnvValueTransformer, + AppConfig, + RedisConfig, } from './config/configTypes' export { @@ -61,7 +61,7 @@ export { isObject, hasMessage, } from './utils/typeUtils' -export { type StandardizedError } from './utils/typeUtils' +export type { StandardizedError } from './utils/typeUtils' export { generateHash, HashAlgorithm, HashEncoding } from './utils/hashUtils' @@ -74,10 +74,10 @@ export { export type { AppLoggerConfig, MonorepoAppLoggerConfig } from './logging/loggerConfigResolver' export type { CommonLogger } from './logging/commonLogger' -export { - type ErrorReport, - type ErrorReporter, - type ErrorResolver, +export type { + ErrorReport, + ErrorReporter, + ErrorResolver, } from './errors/errorReporterTypes' export { executeAsyncAndHandleGlobalErrors, @@ -87,8 +87,9 @@ export { resolveGlobalErrorLogObject, } from './errors/globalErrorHandler' -export { type MayOmit } from './common/may-omit' -export { type FreeformRecord } from './common/commonTypes' +export type { MayOmit } from './common/may-omit' +export type { FreeformRecord } from './common/commonTypes' +export type { AtLeastOne } from './common/at-least-one' export { type ValidationError, type CommonErrorParams, From aab50167932b7c99e5673fbd0bfd9dfb2b1649be Mon Sep 17 00:00:00 2001 From: Bartosz Drozd Date: Fri, 13 Dec 2024 10:56:12 +0100 Subject: [PATCH 2/3] tests: add tests for AtLeastOne type --- src/common/atLeastOne.spec.ts | 17 +++++++++++++++++ src/common/{at-least-one.ts => atLeastOne.ts} | 0 src/index.ts | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/common/atLeastOne.spec.ts rename src/common/{at-least-one.ts => atLeastOne.ts} (100%) diff --git a/src/common/atLeastOne.spec.ts b/src/common/atLeastOne.spec.ts new file mode 100644 index 0000000..bcfbf93 --- /dev/null +++ b/src/common/atLeastOne.spec.ts @@ -0,0 +1,17 @@ +import { assertType } from 'vitest' +import type { AtLeastOne } from './atLeastOne' + +describe('AtLeastOne', () => { + it('makes at least one key of an object required', () => { + interface TestType { + a: string + b: number + } + + const testCase1: AtLeastOne = { a: 'a' } + const testCase2: AtLeastOne = { b: 1 } + + assertType<{ a: string; b?: number }>(testCase1) + assertType<{ a?: string; b: number }>(testCase2) + }) +}) diff --git a/src/common/at-least-one.ts b/src/common/atLeastOne.ts similarity index 100% rename from src/common/at-least-one.ts rename to src/common/atLeastOne.ts diff --git a/src/index.ts b/src/index.ts index 18754bd..3d62c38 100644 --- a/src/index.ts +++ b/src/index.ts @@ -89,7 +89,7 @@ export { export type { MayOmit } from './common/may-omit' export type { FreeformRecord } from './common/commonTypes' -export type { AtLeastOne } from './common/at-least-one' +export type { AtLeastOne } from './common/atLeastOne' export { type ValidationError, type CommonErrorParams, From 4a9c0178f0c7cea24da700f36ffdac1cd901e1de Mon Sep 17 00:00:00 2001 From: Bartosz Drozd Date: Fri, 13 Dec 2024 12:23:37 +0100 Subject: [PATCH 3/3] chore: add docs, enable typecheck --- docs/custom-types.md | 41 +++++++++++++++++++++++++++++++++++ src/common/atLeastOne.spec.ts | 8 +++++-- src/common/atLeastOne.ts | 1 - vitest.config.mts | 12 ++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 docs/custom-types.md diff --git a/docs/custom-types.md b/docs/custom-types.md new file mode 100644 index 0000000..8e53b61 --- /dev/null +++ b/docs/custom-types.md @@ -0,0 +1,41 @@ +# Custom types + +`AtLeastOne` + +Can be used when you want to make at least one key of an object required. +You don't have to specify which key should be required, it will be inferred from the input type. +Useful for example when you have a function that requires at least one of the parameters, but not necessarily all of them. + +```typescript +type TestType = { + a: string; + b: number; +} + +const foo = (params: AtLeastOne) => { + // ... +} + +// Valid +foo({ a: 'a' }); +foo({ b: 1 }); +foo({ a: 'a', b: 1 }); + +// Invalid +foo({}); +``` + +`MayOmit` + +Makes K keys optional in T. +Can be used to provide some defaults and merge input on top of it. +Requires you to explicitly specify which keys should be optional. + +```typescript +type TestType = { + a: string; + b: number; +} + +type TestTypeWithOptionalB = MayOmit; +``` diff --git a/src/common/atLeastOne.spec.ts b/src/common/atLeastOne.spec.ts index bcfbf93..e54b287 100644 --- a/src/common/atLeastOne.spec.ts +++ b/src/common/atLeastOne.spec.ts @@ -9,9 +9,13 @@ describe('AtLeastOne', () => { } const testCase1: AtLeastOne = { a: 'a' } - const testCase2: AtLeastOne = { b: 1 } - assertType<{ a: string; b?: number }>(testCase1) + + const testCase2: AtLeastOne = { b: 1 } assertType<{ a?: string; b: number }>(testCase2) + + // @ts-expect-error wrong types + const testCase3: AtLeastOne = {} + assertType(testCase3) }) }) diff --git a/src/common/atLeastOne.ts b/src/common/atLeastOne.ts index e159257..6d1dfb9 100644 --- a/src/common/atLeastOne.ts +++ b/src/common/atLeastOne.ts @@ -1,2 +1 @@ -// can be used when you want to make at least one key of an object required export type AtLeastOne }> = Partial & U[keyof U] diff --git a/vitest.config.mts b/vitest.config.mts index dc4be97..63ae9c2 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -30,5 +30,17 @@ export default defineConfig({ statements: 100, }, }, + typecheck: { + enabled: true, + /** + * By default, vitest runs typecheck on tests matching the pattern "*.test-d.ts". + * Sadly some IDEs (like Webstorm) doesn't work well with custom patterns like that. + * They do not properly recognize globals like vitest ones (describe, it, expect, etc.). + * Because of that, we include typecheck in all test files. + * The performance impact is minimal, and it makes the IDE work properly. + * Docs: https://vitest.dev/guide/testing-types.html#testing-types + */ + include: ['src/**/*.spec.ts'], + }, }, })