import { call, takeEvery } from 'redux-saga/effects'; import type { SagaIterator } from 'redux-saga'; import { sagaCreator } from './store'; import { isFn, isObject } from './util'; import { createKey } from './create-key'; import type { Middleware, MiddlewareCo, Next, ActionWithPayload, CreateAction, CreateActionWithPayload, PipeCtx, Payload, } from './types'; import { API_ACTION_PREFIX } from './constant'; import { compose } from './compose'; export interface SagaApi { saga: () => (...options: any[]) => SagaIterator; use: (fn: Middleware) => void; routes: () => Middleware; /** * Name only */ create(name: string): CreateAction; create

( name: string, ): CreateActionWithPayload & Payload

, P>; /** * Name and options */ create(name: string, req: { saga?: any }): CreateAction; create

( name: string, req: { saga?: any }, ): CreateActionWithPayload & Payload

, P>; /** * Name and middleware */ create(name: string, fn: MiddlewareCo): CreateAction; create( name: string, fn: MiddlewareCo, ): CreateAction; create

( name: string, fn: MiddlewareCo & Payload

>, ): CreateActionWithPayload & Payload

, P>; create( name: string, fn: MiddlewareCo, ): CreateActionWithPayload; /* * Name, options, and middleware */ create( name: string, req: { saga?: any }, fn: MiddlewareCo, ): CreateAction; create( name: string, req: { saga?: any }, fn: MiddlewareCo, ): CreateAction; create

( name: string, req: { saga?: any }, fn: MiddlewareCo & Payload

>, ): CreateActionWithPayload & Payload

, P>; create( name: string, req: { saga?: any }, fn: MiddlewareCo, ): CreateActionWithPayload; } export const defaultOnError = (err: Error) => { throw err; }; /** * Creates a middleware pipeline. * * @remarks * This middleware pipeline is almost exactly like koa's middleware system. * See {@link https://koajs.com} * * @example * ```ts * import { createPipe } from 'saga-query'; * * const thunk = createPipe(); * thunk.use(function* (ctx, next) { * console.log('beginning'); * yield next(); * console.log('end'); * }); * thunks.use(thunks.routes()); * * const doit = thunk.create('do-something', function*(ctx, next) { * console.log('middle'); * yield next(); * console.log('middle end'); * }); * * // ... * * store.dispatch(doit()); * // beginning * // middle * // middle end * // end * ``` */ export function createPipe>({ saga = takeEvery, onError = defaultOnError, }: { saga?: (...args: any[]) => any; onError?: (err: Error) => any; } = {}): SagaApi { const middleware: Middleware[] = []; const sagas: { [key: string]: any } = {}; const middlewareMap: { [key: string]: Middleware } = {}; const actionMap: { [key: string]: CreateActionWithPayload; } = {}; function* defaultMiddleware(_: Ctx, next: Next): SagaIterator { yield next(); } const createType = (post: string) => `${API_ACTION_PREFIX}${post.startsWith('/') ? '' : '/'}${post}`; function* onApi(action: ActionWithPayload): SagaIterator { const { name, key, options } = action.payload; const actionFn = actionMap[name]; const ctx: any = { action, name, key, payload: options, actionFn, }; const fn = compose(middleware); yield call(fn, ctx); return ctx; } function create(name: string, ...args: any[]) { const type = createType(name); const action = (payload?: any) => { return { type, payload }; }; let req = null; let fn = null; if (args.length === 2) { req = args[0]; fn = args[1]; } if (args.length === 1) { if (isFn(args[0]) || Array.isArray(args[0])) { fn = args[0]; } else { req = args[0]; } } if (req && !isObject(req)) { throw new Error('Options must be an object'); } if (fn && Array.isArray(fn)) { fn = compose(fn); } if (fn && !isFn(fn)) { throw new Error('Middleware must be a function'); } if (middlewareMap[name]) { console.warn( `[${name}] already exists which means you have two functions with the same name`, ); } middlewareMap[name] = fn || defaultMiddleware; const tt = req ? (req as any).saga : saga; function* curSaga(): SagaIterator { yield tt(type, onApi); } sagas[name] = curSaga; const actionFn = (options?: any) => { const key = createKey(name, options); return action({ name, key, options }); }; actionFn.run = onApi as any; actionFn.toString = () => name; actionMap[name] = actionFn; return actionFn; } function routes() { function* router(ctx: Ctx, next: Next): SagaIterator { const match = middlewareMap[ctx.name]; if (!match) { yield next(); return; } yield call(match, ctx, next); } return router; } return { saga: () => sagaCreator(sagas, onError), use: (fn: Middleware) => { middleware.push(fn); }, create, routes, }; }