Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add AtLeastOne type #171

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/custom-types.md
Original file line number Diff line number Diff line change
@@ -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<TestType>) => {
// ...
}

// 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<TestType, 'b'>;
```
21 changes: 21 additions & 0 deletions src/common/atLeastOne.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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<TestType> = { a: 'a' }
assertType<{ a: string; b?: number }>(testCase1)

const testCase2: AtLeastOne<TestType> = { b: 1 }
assertType<{ a?: string; b: number }>(testCase2)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add negative case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added negative test ✅

I've also enabled typecheck as by default vitest has it disabled - please see vitest.config.mts


// @ts-expect-error wrong types
const testCase3: AtLeastOne<TestType> = {}
assertType(testCase3)
})
})
1 change: 1 addition & 0 deletions src/common/atLeastOne.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add documentation for it to readme.md?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added them to docs/custom-types.md as I see we have some documents already there. I've included the existing types that haven't been documented so far.

25 changes: 13 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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'

Expand All @@ -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,
Expand All @@ -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/atLeastOne'
export {
type ValidationError,
type CommonErrorParams,
Expand Down
12 changes: 12 additions & 0 deletions vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
},
})
Loading