Skip to content

Commit 10b5525

Browse files
Added ExtendBaseWith type for Rest API example
1 parent d536226 commit 10b5525

File tree

4 files changed

+102
-25
lines changed

4 files changed

+102
-25
lines changed
Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { Base } from "../../index.js";
1+
import { Base, ExtendBaseWith } from "../../index.js";
22

33
import { requestPlugin } from "./request-plugin.js";
44

5-
declare type Constructor<T> = new (...args: any[]) => T;
6-
7-
export class RestApiClient extends Base {
8-
request: ReturnType<typeof requestPlugin>["request"];
9-
}
5+
export const RestApiClient: ExtendBaseWith<
6+
Base,
7+
{
8+
defaults: {
9+
userAgent: string;
10+
};
11+
plugins: [typeof requestPlugin];
12+
}
13+
>;

examples/rest-api-client-dts/index.test-d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { expectType } from "tsd";
33
import { RestApiClient } from "./index.js";
44

55
// @ts-expect-error - An argument for 'options' was not provided
6-
new RestApiClient();
6+
let value: typeof RestApiClient = new RestApiClient();
7+
8+
expectType<{ userAgent: string }>(value.defaults);
79

810
expectType<{ userAgent: string }>(RestApiClient.defaults);
911

index.d.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@ declare type UnionToIntersection<Union> = (
2121
? Intersection
2222
: never;
2323
declare type AnyFunction = (...args: any) => any;
24-
declare type ReturnTypeOf<T extends AnyFunction | AnyFunction[]> =
25-
T extends AnyFunction
26-
? ReturnType<T>
27-
: T extends AnyFunction[]
28-
? UnionToIntersection<Exclude<ReturnType<T[number]>, void>>
29-
: never;
24+
declare type ReturnTypeOf<
25+
T extends AnyFunction | AnyFunction[]
26+
> = T extends AnyFunction
27+
? ReturnType<T>
28+
: T extends AnyFunction[]
29+
? UnionToIntersection<Exclude<ReturnType<T[number]>, void>>
30+
: never;
3031

3132
type ClassWithPlugins = Constructor<any> & {
3233
plugins: Plugin[];
3334
};
3435

35-
type RemainingRequirements<PredefinedOptions> =
36-
keyof PredefinedOptions extends never
37-
? Base.Options
38-
: Omit<Base.Options, keyof PredefinedOptions>;
36+
type RemainingRequirements<
37+
PredefinedOptions
38+
> = keyof PredefinedOptions extends never
39+
? Base.Options
40+
: Omit<Base.Options, keyof PredefinedOptions>;
3941

4042
type NonOptionalKeys<Obj> = {
4143
[K in keyof Obj]: {} extends Pick<Obj, K> ? undefined : K;
@@ -51,10 +53,7 @@ type RequiredIfRemaining<PredefinedOptions, NowProvided> = NonOptionalKeys<
5153
NowProvided
5254
];
5355

54-
type ConstructorRequiringOptionsIfNeeded<
55-
Class extends ClassWithPlugins,
56-
PredefinedOptions
57-
> = {
56+
type ConstructorRequiringOptionsIfNeeded<Class, PredefinedOptions> = {
5857
defaults: PredefinedOptions;
5958
} & {
6059
new <NowProvided>(
@@ -156,4 +155,30 @@ export declare class Base<TOptions extends Base.Options = Base.Options> {
156155

157156
constructor(options: TOptions);
158157
}
158+
159+
type Extensions = {
160+
defaults?: {};
161+
plugins?: Plugin[];
162+
};
163+
164+
type OrObject<T, Extender> = T extends Extender ? {} : T;
165+
166+
type ApplyPlugins<
167+
Plugins extends Plugin[] | undefined
168+
> = Plugins extends Plugin[]
169+
? UnionToIntersection<ReturnType<Plugins[number]>>
170+
: {};
171+
172+
export type ExtendBaseWith<
173+
BaseClass extends Base,
174+
BaseExtensions extends Extensions
175+
> = BaseClass &
176+
ConstructorRequiringOptionsIfNeeded<
177+
BaseClass & ApplyPlugins<BaseExtensions["plugins"]>,
178+
OrObject<BaseClass["options"], unknown>
179+
> &
180+
ApplyPlugins<BaseExtensions["plugins"]> & {
181+
defaults: OrObject<BaseExtensions["defaults"], undefined>;
182+
};
183+
159184
export {};

index.test-d.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expectType } from "tsd";
2-
import { Base, Plugin } from "./index.js";
2+
import { Base, ExtendBaseWith, Plugin } from "./index.js";
33

44
import { fooPlugin } from "./plugins/foo/index.js";
55
import { barPlugin } from "./plugins/bar/index.js";
@@ -229,12 +229,58 @@ expectType<{
229229
defaultThree: string[];
230230
}>({ ...BaseWithManyChainedDefaultsAndPlugins.defaults });
231231

232-
const baseWithManyChainedDefaultsAndPlugins =
233-
new BaseWithManyChainedDefaultsAndPlugins({
232+
const baseWithManyChainedDefaultsAndPlugins = new BaseWithManyChainedDefaultsAndPlugins(
233+
{
234234
required: "1.2.3",
235235
foo: "bar",
236-
});
236+
}
237+
);
237238

238239
expectType<string>(baseWithManyChainedDefaultsAndPlugins.foo);
239240
expectType<string>(baseWithManyChainedDefaultsAndPlugins.bar);
240241
expectType<string>(baseWithManyChainedDefaultsAndPlugins.getFooOption());
242+
243+
declare const RestApiClient: ExtendBaseWith<
244+
Base,
245+
{
246+
defaults: {
247+
defaultValue: string;
248+
};
249+
plugins: [
250+
() => { pluginValueOne: number },
251+
() => { pluginValueTwo: boolean }
252+
];
253+
}
254+
>;
255+
256+
expectType<string>(RestApiClient.defaults.defaultValue);
257+
258+
// @ts-expect-error
259+
RestApiClient.defaults.unexpected;
260+
261+
expectType<number>(RestApiClient.pluginValueOne);
262+
expectType<boolean>(RestApiClient.pluginValueTwo);
263+
264+
// @ts-expect-error
265+
RestApiClient.unexpected;
266+
267+
declare const MoreDefaultRestApiClient: ExtendBaseWith<
268+
typeof RestApiClient,
269+
{
270+
defaults: {
271+
anotherDefaultValue: number;
272+
};
273+
}
274+
>;
275+
276+
expectType<string>(MoreDefaultRestApiClient.defaults.defaultValue);
277+
expectType<number>(MoreDefaultRestApiClient.defaults.anotherDefaultValue);
278+
279+
declare const MorePluginRestApiClient: ExtendBaseWith<
280+
typeof MoreDefaultRestApiClient,
281+
{
282+
plugins: [() => { morePluginValue: string[] }];
283+
}
284+
>;
285+
286+
expectType<string[]>(MorePluginRestApiClient.morePluginValue);

0 commit comments

Comments
 (0)