import { Prism } from 'monocle-ts' import { iso, Newtype, prism } from 'newtype-ts' import { Predicate, unsafeCoerce } from 'fp-ts/lib/function' import { isUndefined } from './typeGuards' // TODO: Use `SimSpaceNewtype` instead of `Newtype` if we ever make use of this export interface Optimistic extends Newtype< { readonly Optimistic: unique symbol phantom: A }, string > {} export interface Request extends Newtype< { readonly Request: unique symbol phantom: A }, string > {} export interface Failure extends Newtype< { readonly Failure: unique symbol phantom: A }, string > {} export interface Success extends Newtype< { readonly Success: unique symbol phantom: A }, string > {} export type ApiActionType = | Optimistic | Request | Failure | Success // this is safe, because we know ApiActionType is a union of newtypes, // and we know each of those newtypes is actually a string at runtime export const coerceApiActionTypeToString = ( x: ApiActionType, ): string => unsafeCoerce, string>(x) export type ApiActionTypes = { types: { optimistic: Optimistic request: Request failure: Failure success: Success } guards: { isOptimistic: (x: ApiActionType) => x is Optimistic isRequest: (x: ApiActionType) => x is Request isFailure: (x: ApiActionType) => x is Failure isSuccess: (x: ApiActionType) => x is Success } prisms: { optimistic: Prism> request: Prism> failure: Prism> success: Prism> } } export const mkApiActionTypes = ( pathPrefix: A, ): ApiActionTypes => { const isoOptimistic = iso>() const isoRequest = iso>() const isoFailure = iso>() const isoSuccess = iso>() const pathOptimistic = `${pathPrefix}_OPTIMISTIC` const optimistic = isoOptimistic.wrap(pathOptimistic) const pathRequest = `${pathPrefix}_REQUEST` const request = isoRequest.wrap(pathRequest) const pathFailure = `${pathPrefix}_FAILURE` const failure = isoFailure.wrap(pathFailure) const pathSuccess = `${pathPrefix}_SUCCESS` const success = isoSuccess.wrap(pathSuccess) const isOptimistic = (x: ApiActionType): x is Optimistic => coerceApiActionTypeToString(x) === pathOptimistic const isRequest = (x: ApiActionType): x is Request => coerceApiActionTypeToString(x) === pathRequest const isFailure = (x: ApiActionType): x is Failure => coerceApiActionTypeToString(x) === pathFailure const isSuccess = (x: ApiActionType): x is Success => coerceApiActionTypeToString(x) === pathSuccess const isOptimistic_: Predicate = x => x === pathOptimistic const isRequest_: Predicate = x => x === pathRequest const isFailure_: Predicate = x => x === pathFailure const isSuccess_: Predicate = x => x === pathSuccess const prismOptimistic = prism>(isOptimistic_) const prismRequest = prism>(isRequest_) const prismFailure = prism>(isFailure_) const prismSuccess = prism>(isSuccess_) return { types: { optimistic, request, failure, success, }, guards: { isOptimistic, isRequest, isFailure, isSuccess, }, prisms: { optimistic: prismOptimistic, request: prismRequest, failure: prismFailure, success: prismSuccess, }, } } export type GeneratedApiAction = Payload extends undefined ? Meta extends undefined ? { type: Type } : { type: Type; meta: Meta } : Meta extends undefined ? { type: Type; payload: Payload } : { type: Type; payload: Payload; meta: Meta } export const mkApiAction = < A extends string, Type extends ApiActionType, Payload = undefined, Meta = undefined >( type: Type, payload?: Payload, meta?: Meta, ): GeneratedApiAction => (isUndefined(payload) ? isUndefined(meta) ? { type } : { type, meta } : isUndefined(meta) ? { type, payload } : { type, payload, meta }) as GeneratedApiAction