1 import { createAction } from '@reduxjs/toolkit';
3 import { type ActionCallback, withCallback } from '@proton/pass/store/actions/enhancers/callback';
4 import type { TagMatch, Tagged } from '@proton/pass/types';
5 import { pipe } from '@proton/pass/utils/fp/pipe';
7 import { withRequest, withRequestFailure, withRequestSuccess } from './enhancers';
8 import type { RequestConfig } from './types';
10 type RequestPrepareAction<P extends any[], R> = (...params: P) => R;
11 type RequestKeyPrepator<T> = (dto: T) => string;
12 type RequestKeyDefault = () => string;
13 type RequestKeyPreparators<T> = RequestKeyDefault | RequestKeyPrepator<T>;
15 type Payload<T = any> = { payload: T };
17 export type RequestFlow<I, S, F> = ReturnType<ReturnType<typeof requestActionsFactory<I, S, F>>>;
18 export type RequestIntent<T extends RequestFlow<any, any, any>> = T extends RequestFlow<infer U, any, any> ? U : never;
19 export type RequestSuccess<T extends RequestFlow<any, any, any>> = T extends RequestFlow<any, infer U, any> ? U : never;
21 type CreateRequestActionsOptions<
23 IntentPrepared extends Payload,
24 IntentData extends any,
26 SuccessPrepared extends Payload,
27 SuccessData extends any,
29 FailurePrepared extends Payload,
30 FailureData extends any,
31 RequestKey extends RequestKeyPrepator<IntentDTO>,
33 /** Key option determines requestID generation:
34 * - When provided: ${namespace}::${key(dto)} requiring IntentDTO param
35 * - When omitted: uses action namespace & `requestID` function becomes `() => string` */
37 /** Intent action configuration */
39 /** Defaults to `false` - set to `true` to track "intent" request data */
40 config?: RequestConfig<'start', IntentData>;
41 /** Defaults to `(intent: IntentDTO) => ({ payload: IntentDTO })` */
42 prepare?: RequestPrepareAction<[intent: IntentDTO], IntentPrepared>;
44 /** Success action configuration */
46 /** Defaults to `false` - set to `true` to track "success" request data */
47 config?: RequestConfig<'success', SuccessData>;
48 /** Defaults to `(success: SuccessDTO) => ({ payload: SuccessDTO })` */
49 prepare?: RequestPrepareAction<[success: SuccessDTO], SuccessPrepared>;
51 /** Failure action configuration */
53 /** Defaults to `false` - set to `true` to track "failure" request data */
54 config?: RequestConfig<'failure', FailureData>;
55 /** Defaults to `(error: unknown, failure: FailureDTO) => ({ payload: FailureDTO, error: unknown })` */
56 prepare?: RequestPrepareAction<[error: unknown, failure: FailureDTO], FailurePrepared>;
60 /** Creates action creators for each stage of a request sequence:
61 * intent, success, and error. These action creators facilitate the
62 * dispatching of actions to represent the initiation of a request,
63 * successful completion of a request, and handling of errors that
64 * occur during the request process. */
65 export const requestActionsFactory =
66 <IntentDTO, SuccessDTO, FailureDTO = void>(namespace: string) =>
67 /** All generics include sensible defaults allowing partial `options`:
68 * - Prepared types default to `Payload<DTO>` maintaining the payload structure
69 * - Data flags default to false (no request tracking)
70 * - `RequestKey` defaults to `() => string` when key option is omitted */
72 IntentPrepared extends Payload = Payload<IntentDTO>,
73 SuccessPrepared extends Payload = Payload<SuccessDTO>,
74 FailurePrepared extends Payload = Payload<FailureDTO>,
75 IntentData extends any = never,
76 SuccessData extends any = never,
77 FailureData extends any = never,
78 /* Tags default key preparator with 'fallback' for type discrimination
79 * while preserving RequestKey type parameter constraints. */
80 RequestKey extends RequestKeyPrepator<IntentDTO> = Tagged<RequestKeyPreparators<IntentDTO>, 'fallback'>,
82 options: CreateRequestActionsOptions<
95 type IntentPA = RequestPrepareAction<[intent: IntentDTO], IntentPrepared>;
96 type SuccessPA = RequestPrepareAction<[success: SuccessDTO], SuccessPrepared>;
97 type FailurePA = RequestPrepareAction<[error: unknown, failure: FailureDTO], FailurePrepared>;
98 type RequestPA = TagMatch<RequestKey, 'fallback', RequestKeyDefault, RequestKey>;
100 const toPayload = (payload: unknown) => ({ payload });
101 const toPayloadWithError = (error: unknown, payload: unknown) => ({ payload, error });
103 const intentPA = (options.intent?.prepare ?? toPayload) as IntentPA;
104 const successPA = (options.success?.prepare ?? toPayload) as SuccessPA;
105 const failurePA = (options.failure?.prepare ?? toPayloadWithError) as FailurePA;
106 const requestID = (dto: IntentDTO) =>
107 'key' in options && options.key ? `${namespace}::${options.key(dto)}` : namespace;
110 requestID: requestID as RequestPA,
111 intent: createAction(`${namespace}::intent`, (dto: IntentDTO, callback?: ActionCallback) =>
116 ...(options.intent?.config ?? {}),
118 withCallback(callback)
121 success: createAction(`${namespace}::success`, withRequestSuccess(successPA, options.success?.config)),
122 failure: createAction(`${namespace}::failure`, withRequestFailure(failurePA, options.failure?.config)),