UNPKG

9.59 kBPlain TextView Raw
1import type { Action } from 'redux'
2import type {
3 IsUnknownOrNonInferrable,
4 IfMaybeUndefined,
5 IfVoid,
6 IsAny,
7} from './tsHelpers'
8import isPlainObject from './isPlainObject'
9
10/**
11 * An action with a string type and an associated payload. This is the
12 * type of action returned by `createAction()` action creators.
13 *
14 * @template P The type of the action's payload.
15 * @template T the type used for the action type.
16 * @template M The type of the action's meta (optional)
17 * @template E The type of the action's error (optional)
18 *
19 * @public
20 */
21export type PayloadAction<
22 P = void,
23 T extends string = string,
24 M = never,
25 E = never
26> = {
27 payload: P
28 type: T
29} & ([M] extends [never]
30 ? {}
31 : {
32 meta: M
33 }) &
34 ([E] extends [never]
35 ? {}
36 : {
37 error: E
38 })
39
40/**
41 * A "prepare" method to be used as the second parameter of `createAction`.
42 * Takes any number of arguments and returns a Flux Standard Action without
43 * type (will be added later) that *must* contain a payload (might be undefined).
44 *
45 * @public
46 */
47export type PrepareAction<P> =
48 | ((...args: any[]) => { payload: P })
49 | ((...args: any[]) => { payload: P; meta: any })
50 | ((...args: any[]) => { payload: P; error: any })
51 | ((...args: any[]) => { payload: P; meta: any; error: any })
52
53/**
54 * Internal version of `ActionCreatorWithPreparedPayload`. Not to be used externally.
55 *
56 * @internal
57 */
58export type _ActionCreatorWithPreparedPayload<
59 PA extends PrepareAction<any> | void,
60 T extends string = string
61> = PA extends PrepareAction<infer P>
62 ? ActionCreatorWithPreparedPayload<
63 Parameters<PA>,
64 P,
65 T,
66 ReturnType<PA> extends {
67 error: infer E
68 }
69 ? E
70 : never,
71 ReturnType<PA> extends {
72 meta: infer M
73 }
74 ? M
75 : never
76 >
77 : void
78
79/**
80 * Basic type for all action creators.
81 *
82 * @inheritdoc {redux#ActionCreator}
83 */
84interface BaseActionCreator<P, T extends string, M = never, E = never> {
85 type: T
86 match: (action: Action<unknown>) => action is PayloadAction<P, T, M, E>
87}
88
89/**
90 * An action creator that takes multiple arguments that are passed
91 * to a `PrepareAction` method to create the final Action.
92 * @typeParam Args arguments for the action creator function
93 * @typeParam P `payload` type
94 * @typeParam T `type` name
95 * @typeParam E optional `error` type
96 * @typeParam M optional `meta` type
97 *
98 * @inheritdoc {redux#ActionCreator}
99 *
100 * @public
101 */
102export interface ActionCreatorWithPreparedPayload<
103 Args extends unknown[],
104 P,
105 T extends string = string,
106 E = never,
107 M = never
108> extends BaseActionCreator<P, T, M, E> {
109 /**
110 * Calling this {@link redux#ActionCreator} with `Args` will return
111 * an Action with a payload of type `P` and (depending on the `PrepareAction`
112 * method used) a `meta`- and `error` property of types `M` and `E` respectively.
113 */
114 (...args: Args): PayloadAction<P, T, M, E>
115}
116
117/**
118 * An action creator of type `T` that takes an optional payload of type `P`.
119 *
120 * @inheritdoc {redux#ActionCreator}
121 *
122 * @public
123 */
124export interface ActionCreatorWithOptionalPayload<P, T extends string = string>
125 extends BaseActionCreator<P, T> {
126 /**
127 * Calling this {@link redux#ActionCreator} with an argument will
128 * return a {@link PayloadAction} of type `T` with a payload of `P`.
129 * Calling it without an argument will return a PayloadAction with a payload of `undefined`.
130 */
131 (payload?: P): PayloadAction<P, T>
132}
133
134/**
135 * An action creator of type `T` that takes no payload.
136 *
137 * @inheritdoc {redux#ActionCreator}
138 *
139 * @public
140 */
141export interface ActionCreatorWithoutPayload<T extends string = string>
142 extends BaseActionCreator<undefined, T> {
143 /**
144 * Calling this {@link redux#ActionCreator} will
145 * return a {@link PayloadAction} of type `T` with a payload of `undefined`
146 */
147 (): PayloadAction<undefined, T>
148}
149
150/**
151 * An action creator of type `T` that requires a payload of type P.
152 *
153 * @inheritdoc {redux#ActionCreator}
154 *
155 * @public
156 */
157export interface ActionCreatorWithPayload<P, T extends string = string>
158 extends BaseActionCreator<P, T> {
159 /**
160 * Calling this {@link redux#ActionCreator} with an argument will
161 * return a {@link PayloadAction} of type `T` with a payload of `P`
162 */
163 (payload: P): PayloadAction<P, T>
164}
165
166/**
167 * An action creator of type `T` whose `payload` type could not be inferred. Accepts everything as `payload`.
168 *
169 * @inheritdoc {redux#ActionCreator}
170 *
171 * @public
172 */
173export interface ActionCreatorWithNonInferrablePayload<
174 T extends string = string
175> extends BaseActionCreator<unknown, T> {
176 /**
177 * Calling this {@link redux#ActionCreator} with an argument will
178 * return a {@link PayloadAction} of type `T` with a payload
179 * of exactly the type of the argument.
180 */
181 <PT extends unknown>(payload: PT): PayloadAction<PT, T>
182}
183
184/**
185 * An action creator that produces actions with a `payload` attribute.
186 *
187 * @typeParam P the `payload` type
188 * @typeParam T the `type` of the resulting action
189 * @typeParam PA if the resulting action is preprocessed by a `prepare` method, the signature of said method.
190 *
191 * @public
192 */
193export type PayloadActionCreator<
194 P = void,
195 T extends string = string,
196 PA extends PrepareAction<P> | void = void
197> = IfPrepareActionMethodProvided<
198 PA,
199 _ActionCreatorWithPreparedPayload<PA, T>,
200 // else
201 IsAny<
202 P,
203 ActionCreatorWithPayload<any, T>,
204 IsUnknownOrNonInferrable<
205 P,
206 ActionCreatorWithNonInferrablePayload<T>,
207 // else
208 IfVoid<
209 P,
210 ActionCreatorWithoutPayload<T>,
211 // else
212 IfMaybeUndefined<
213 P,
214 ActionCreatorWithOptionalPayload<P, T>,
215 // else
216 ActionCreatorWithPayload<P, T>
217 >
218 >
219 >
220 >
221>
222
223/**
224 * A utility function to create an action creator for the given action type
225 * string. The action creator accepts a single argument, which will be included
226 * in the action object as a field called payload. The action creator function
227 * will also have its toString() overriden so that it returns the action type,
228 * allowing it to be used in reducer logic that is looking for that action type.
229 *
230 * @param type The action type to use for created actions.
231 * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
232 * If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
233 *
234 * @public
235 */
236export function createAction<P = void, T extends string = string>(
237 type: T
238): PayloadActionCreator<P, T>
239
240/**
241 * A utility function to create an action creator for the given action type
242 * string. The action creator accepts a single argument, which will be included
243 * in the action object as a field called payload. The action creator function
244 * will also have its toString() overriden so that it returns the action type,
245 * allowing it to be used in reducer logic that is looking for that action type.
246 *
247 * @param type The action type to use for created actions.
248 * @param prepare (optional) a method that takes any number of arguments and returns { payload } or { payload, meta }.
249 * If this is given, the resulting action creator will pass its arguments to this method to calculate payload & meta.
250 *
251 * @public
252 */
253export function createAction<
254 PA extends PrepareAction<any>,
255 T extends string = string
256>(
257 type: T,
258 prepareAction: PA
259): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>
260
261export function createAction(type: string, prepareAction?: Function): any {
262 function actionCreator(...args: any[]) {
263 if (prepareAction) {
264 let prepared = prepareAction(...args)
265 if (!prepared) {
266 throw new Error('prepareAction did not return an object')
267 }
268
269 return {
270 type,
271 payload: prepared.payload,
272 ...('meta' in prepared && { meta: prepared.meta }),
273 ...('error' in prepared && { error: prepared.error }),
274 }
275 }
276 return { type, payload: args[0] }
277 }
278
279 actionCreator.toString = () => `${type}`
280
281 actionCreator.type = type
282
283 actionCreator.match = (action: Action<unknown>): action is PayloadAction =>
284 action.type === type
285
286 return actionCreator
287}
288
289export function isFSA(action: unknown): action is {
290 type: string
291 payload?: unknown
292 error?: unknown
293 meta?: unknown
294} {
295 return (
296 isPlainObject(action) &&
297 typeof (action as any).type === 'string' &&
298 Object.keys(action).every(isValidKey)
299 )
300}
301
302function isValidKey(key: string) {
303 return ['type', 'payload', 'error', 'meta'].indexOf(key) > -1
304}
305
306/**
307 * Returns the action type of the actions created by the passed
308 * `createAction()`-generated action creator (arbitrary action creators
309 * are not supported).
310 *
311 * @param action The action creator whose action type to get.
312 * @returns The action type used by the action creator.
313 *
314 * @public
315 */
316export function getType<T extends string>(
317 actionCreator: PayloadActionCreator<any, T>
318): T {
319 return `${actionCreator}` as T
320}
321
322// helper types for more readable typings
323
324type IfPrepareActionMethodProvided<
325 PA extends PrepareAction<any> | void,
326 True,
327 False
328> = PA extends (...args: any[]) => any ? True : False