Skip to content

Commit fa4fcaf

Browse files
committed
Smarter handling of optional arguments and properties using TS 2.8
1 parent 63185aa commit fa4fcaf

File tree

4 files changed

+225
-60
lines changed

4 files changed

+225
-60
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"tape": "^4.6.2",
4141
"ts-node": "^4.0.0",
4242
"tslint": "^5.0.0",
43-
"typescript": "^2.1.5",
43+
"typescript": "^2.8.1",
4444
"typings-tester": "^0.3.0"
4545
},
4646
"greenkeeper": {

src/index.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,22 @@ export interface Action<P> extends AnyAction {
1111
meta?: Meta;
1212
}
1313

14-
export interface Success<P, S> {
14+
15+
export type RequiredKeys<T> = {
16+
[P in keyof T]: T[P] extends undefined ? never : P
17+
}[keyof T];
18+
// Makes all properties of type `undefined` optional
19+
export type Optionalize<T> = Partial<T> & {[P in RequiredKeys<T>]: T[P]};
20+
21+
export type Success<P, S> = Optionalize<{
1522
params: P;
1623
result: S;
17-
}
24+
}>;
1825

19-
export interface Failure<P, E> {
26+
export type Failure<P, E> = Optionalize<{
2027
params: P;
2128
error: E;
22-
}
29+
}>;
2330

2431
export function isType<P>(
2532
action: AnyAction,
@@ -28,15 +35,14 @@ export function isType<P>(
2835
return action.type === actionCreator.type;
2936
}
3037

31-
export interface ActionCreator<P> {
38+
export type ActionCreator<P> = {
3239
type: string;
3340
match: (action: AnyAction) => action is Action<P>;
34-
(payload: P, meta?: Meta): Action<P>;
35-
}
36-
37-
export interface EmptyActionCreator extends ActionCreator<undefined> {
38-
(payload?: undefined, meta?: Meta): Action<undefined>;
39-
}
41+
} & (
42+
P extends undefined
43+
? (payload?: P, meta?: Meta) => Action<P>
44+
: (payload: P, meta?: Meta) => Action<P>
45+
);
4046

4147
export interface AsyncActionCreators<P, S, E> {
4248
type: string;
@@ -46,11 +52,9 @@ export interface AsyncActionCreators<P, S, E> {
4652
}
4753

4854
export interface ActionCreatorFactory {
49-
(type: string, commonMeta?: Meta,
50-
error?: boolean): EmptyActionCreator;
51-
<P>(type: string, commonMeta?: Meta,
55+
<P = undefined>(type: string, commonMeta?: Meta,
5256
isError?: boolean): ActionCreator<P>;
53-
<P>(type: string, commonMeta?: Meta,
57+
<P = undefined>(type: string, commonMeta?: Meta,
5458
isError?: (payload: P) => boolean): ActionCreator<P>;
5559

5660
async<P, S>(
@@ -79,7 +83,7 @@ export function actionCreatorFactory(
7983
function actionCreator<P>(
8084
type: string, commonMeta?: Meta,
8185
isError: ((payload: P) => boolean) | boolean = defaultIsError,
82-
): ActionCreator<P> {
86+
) {
8387
const fullType = base + type;
8488

8589
if (process.env.NODE_ENV !== 'production') {
@@ -112,7 +116,7 @@ export function actionCreatorFactory(
112116
match: (action: AnyAction): action is Action<P> =>
113117
action.type === fullType,
114118
},
115-
);
119+
) as ActionCreator<P>;
116120
}
117121

118122
function asyncActionCreators<P, S, E>(

tests/typings/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,111 @@ function testAsyncPayload() {
6565
});
6666
}
6767

68+
function testAsyncNoParams() {
69+
const asyncNoParams = actionCreator.async<undefined,
70+
{bar: string},
71+
{baz: string}>('ASYNC_NO_PARAMS');
72+
73+
const started = asyncNoParams.started();
74+
// typings:expect-error
75+
const started1 = asyncNoParams.started({foo: 'foo'});
76+
77+
const done = asyncNoParams.done({
78+
result: {bar: 'bar'},
79+
});
80+
// typings:expect-error
81+
const done1 = asyncNoParams.done({
82+
params: {foo: 'foo'},
83+
result: {bar: 'bar'},
84+
});
85+
// typings:expect-error
86+
const done2 = asyncNoParams.done({
87+
result: {bar: 1},
88+
});
89+
90+
const failed = asyncNoParams.failed({
91+
error: {baz: 'baz'},
92+
});
93+
// typings:expect-error
94+
const failed1 = asyncNoParams.failed({
95+
params: {foo: 'foo'},
96+
error: {baz: 'baz'},
97+
});
98+
// typings:expect-error
99+
const failed2 = asyncNoParams.failed({
100+
error: {baz: 1},
101+
});
102+
}
103+
104+
function testAsyncNoResult() {
105+
const asyncNoResult = actionCreator.async<{foo: string},
106+
undefined,
107+
{baz: string}>('ASYNC_NO_RESULT');
108+
109+
const started = asyncNoResult.started({foo: 'foo'});
110+
// typings:expect-error
111+
const started1 = asyncNoResult.started({});
112+
// typings:expect-error
113+
const started2 = asyncNoResult.started();
114+
115+
const done = asyncNoResult.done({
116+
params: {foo: 'foo'},
117+
});
118+
// typings:expect-error
119+
const done1 = asyncNoResult.done({
120+
params: {foo: 1},
121+
});
122+
// typings:expect-error
123+
const done2 = asyncNoResult.done({
124+
params: {foo: 'foo'},
125+
result: {bar: 'bar'},
126+
});
127+
128+
const failed = asyncNoResult.failed({
129+
params: {foo: 'foo'},
130+
error: {baz: 'baz'},
131+
});
132+
// typings:expect-error
133+
const failed1 = asyncNoResult.failed({
134+
params: {foo: 1},
135+
error: {baz: 'baz'},
136+
});
137+
// typings:expect-error
138+
const failed2 = asyncNoResult.failed({
139+
params: {foo: 'foo'},
140+
error: {baz: 1},
141+
});
142+
}
143+
144+
function testAsyncNoParamsAndResult() {
145+
const async = actionCreator.async<undefined,
146+
undefined,
147+
{baz: string}>('ASYNC');
148+
149+
const started = async.started();
150+
// typings:expect-error
151+
const started2 = async.started({foo: 'foo'});
152+
153+
const done = async.done({});
154+
// typings:expect-error
155+
const done1 = async.done({
156+
params: {foo: 'foo'},
157+
});
158+
// typings:expect-error
159+
const done2 = async.done({
160+
result: {bar: 'bar'},
161+
});
162+
163+
const failed = async.failed({
164+
error: {baz: 'baz'},
165+
});
166+
// typings:expect-error
167+
const failed1 = async.failed({
168+
params: {foo: 'foo'},
169+
error: {baz: 'baz'},
170+
});
171+
}
172+
68173
function testIsType() {
69174
const withPayload = actionCreator<{foo: string}>('WITH_PAYLOAD');
70175
const withoutPayload = actionCreator('WITHOUT_PAYLOAD');

0 commit comments

Comments
 (0)