I use FeathersJS (which is awesome) but unfortunately, it's not written in TypeScript so the typings have to be written and maintained separately.
Here is the typings files. In it, there's a Service<T>
type that looks like:
export type Service<T> = ServiceOverloads<T> & ServiceAddons<T> & ServiceMethods<T>;
ServiceOverloads<T>
looks like:
export interface ServiceOverloads<T> {
create(data: Array<Partial<T>>, params?: Params): Promise<T[]>;
create(data: Partial<T>, params?: Params): Promise<T>;
patch(id: NullableId, data: Pick<T, keyof T>, params?: Params): Promise<T>;
}
and ServiceMethods<T>
looks like:
export interface ServiceMethods<T> {
find(params?: Params): Promise<T[] | Paginated<T>>;
get(id: Id, params?: Params): Promise<T>;
create(data: Partial<T> | Array<Partial<T>>, params?: Params): Promise<T | T[]>;
update(id: NullableId, data: T, params?: Params): Promise<T>;
patch(id: NullableId, data: Partial<T>, params?: Params): Promise<T>;
remove(id: NullableId, params?: Params): Promise<T>;
}
As you can see, the create
and patch
methods should conflict because they generate the same JavaScript.
Why does the type alias Service<T>
compile but when trying to implement it fails due to conflicting methods? If I copy the methods exactly from each interface and provide a stub implementation, that should work no?
In TypeScript, the intersection of types with function signatures corresponds to overloading those functions. From the relevant GitHub issue introducing intersection types:
Call and Construct Signatures
If
A
has a signatureF
andB
has a signatureG
, thenA & B
has signaturesF
andG
in that order (the order of signatures matter for purposes of overload resolution). Except for the order of signatures, the typesA & B
andB & A
are equivalent.
In your case, ServiceOverloads<T> & ServiceMethods<T>
is effectively
{
create(data: Array<Partial<T>>, params?: Params): Promise<T[]>;
create(data: Partial<T>, params?: Params): Promise<T>;
create(data: Partial<T> | Array<Partial<T>>, params?: Params): Promise<T | T[]>;
patch(id: NullableId, data: Pick<T, keyof T>, params?: Params): Promise<T>;
patch(id: NullableId, data: Partial<T>, params?: Params): Promise<T>;
find(params?: Params): Promise<T[] | Paginated<T>>;
get(id: Id, params?: Params): Promise<T>;
update(id: NullableId, data: T, params?: Params): Promise<T>;
remove(id: NullableId, params?: Params): Promise<T>;
}
and therefore you need to implement it where create()
and patch()
are overloaded methods, not single-signature methods. Here's a sample:
class ServiceImpl<T> {
create(data: Array<Partial<T>>, params?: Params): Promise<T[]>;
create(data: Partial<T>, params?: Params): Promise<T>;
create(data: Partial<T> | Array<Partial<T>>, params?: Params): Promise<T | T[]>;
create(data: Partial<T> | Array<Partial<T>>, params?: Params) : Promise<T> | Promise<T[]> {
return null!; // impl
}
patch(id: NullableId, data: Pick<T, keyof T>, params?: Params): Promise<T>;
patch(id: NullableId, data: Partial<T>, params?: Params): Promise<T>;
patch(id: NullableId, data: Pick<T, keyof T> | Partial<T>, params?: Params): Promise<T> {
return null!; // impl
}
// other methods
}
function getService<T>(): Service<T> {
return new ServiceImpl<T>(); // okay
}
Hope that helps; good luck!