Declaration merging for Joi types
I want to narrow the type
property of the ValidationErrorItem from Joi
using declaration merging.
The interface in Joi looks like this:
declare namespace Joi {
interface ValidationErrorItem {
message: string;
path: Array<string | number>;
type: string;
context?: Context;
}
}
My index.d.ts
in src/types
looks like this:
import { Context } from 'joi'
declare namespace Joi {
type ValidationErrorItemType = 'any.required' | 'date.format'
export interface ValidationErrorItem {
message: string
path: Array<string | number>
type: ValidationErrorItemType
context?: Context
}
}
This is my include
in tsconfig.json
:
"include": ["src/**/*", "types/types.d.ts"]
However, VS Code still shows type
to be of type string instead of ValidationErrorItem
and when using it in a switch
statement I can still use any string without getting compiler warnings.
Update
This is my tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"declaration": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*", "src/types/*"]
}
},
"include": ["src/**/*", "types/joi.ts"]
}
This is my joi.d.ts
based on the answer from @tymzap:
declare module 'joi' {
// full path to joi typings is used to avoid circular import path
import type { Context } from 'joi/lib/index'
export { default } from 'joi/lib/index'
type ValidationErrorItemType = 'any.required' | 'date.format'
export type ValidationErrorItem = {
message: string
path: Array<string | number>
type: ValidationErrorItemType
context?: Context
}
}
This is my sample usage:
error.details.map((i) => {
const x: ValidationErrorItem = i as ValidationErrorItem
switch (x.type) {
case 'any.required':
break
default:
break
}
})
Solution 1:
You can't narrow the type using interface merging. This operation extends types, not overrides them. Extending ValidationErrorItemType
with string
gives ValidationErrorItemType | string
, that's why you still can use any string
.
To achieve this you can redeclare Joi module. Modify the type to your needs and export it after reexporting original Joi typings.
joi.d.ts:
declare module 'joi' {
// full path to joi typings is used to avoid circular import path
import type { Context } from 'joi/lib/index';
export * from 'joi/lib/index';
export { default } from 'joi/lib/index';
type ValidationErrorItemType = 'any.required' | 'date.format';
export type ValidationErrorItem = {
message: string;
path: Array<string | number>;
type: ValidationErrorItemType;
context?: Context;
}
}
Test:
import type { ValidationErrorItem } from 'joi';
const test: ValidationErrorItem = {
// error: Type '"wrong.type"' is not assignable to type 'ValidationErrorItemType'.
type: 'wrong.type',
message: 'dummy message',
path: ['dummy-path'],
};
This way you can effectively override given type from library, without merging with its original signature.
Edit: Joi uses old export =
syntax, depending on your compiler settings this could lead to errors based on typings flavour incompatibility. In this case you can add this line to tsconfig:
{
"compilerOptions": {
"skipLibCheck": true
}
}