saga-toolkit 2.1.2 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,185 @@
1
+ // src/index.ts
2
+ import { createAsyncThunk } from "@reduxjs/toolkit";
3
+
4
+ // node_modules/@redux-saga/deferred/dist/redux-saga-deferred.esm.js
5
+ function deferred() {
6
+ var def = {};
7
+ def.promise = new Promise(function(resolve, reject) {
8
+ def.resolve = resolve;
9
+ def.reject = reject;
10
+ });
11
+ return def;
12
+ }
13
+
14
+ // src/utils.ts
15
+ var requests = {};
16
+ var addRequest = (requestId) => {
17
+ const deferred2 = deferred();
18
+ const request = {
19
+ ...requests[requestId],
20
+ requestId,
21
+ deferred: deferred2
22
+ };
23
+ if (requests[requestId]) {
24
+ requests[requestId].deferred = deferred2;
25
+ if (requests[requestId].onAdd) {
26
+ requests[requestId].onAdd(request);
27
+ }
28
+ } else {
29
+ requests[requestId] = request;
30
+ }
31
+ return deferred2.promise;
32
+ };
33
+ var cleanup = (requestId) => {
34
+ delete requests[requestId];
35
+ };
36
+ var setRequestAbort = (requestId, abort) => {
37
+ if (requests[requestId]) {
38
+ requests[requestId].abort = abort;
39
+ }
40
+ };
41
+ function* getRequest(requestId) {
42
+ const request = requests[requestId];
43
+ if (!request) {
44
+ const result = yield new Promise((onAdd) => {
45
+ requests[requestId] = {
46
+ onAdd: (req) => onAdd(req)
47
+ };
48
+ });
49
+ return result;
50
+ }
51
+ return request;
52
+ }
53
+ var wrap = (saga) => function* (action, ...rest) {
54
+ const { requestId } = action.meta;
55
+ const request = yield getRequest(requestId);
56
+ if (!request.deferred) return;
57
+ const deferred2 = request.deferred;
58
+ let isFinished = false;
59
+ try {
60
+ const result = yield saga(action, ...rest);
61
+ deferred2.resolve(result);
62
+ isFinished = true;
63
+ } catch (error) {
64
+ deferred2.reject(error);
65
+ isFinished = true;
66
+ } finally {
67
+ if (!isFinished) {
68
+ deferred2.reject(new Error("Aborted"));
69
+ const currentRequest = requests[requestId];
70
+ if (currentRequest && currentRequest.abort) {
71
+ currentRequest.abort();
72
+ }
73
+ }
74
+ cleanup(requestId);
75
+ }
76
+ };
77
+
78
+ // src/effects.ts
79
+ import { put, take, fork, cancel } from "redux-saga/effects";
80
+ import { unwrapResult } from "@reduxjs/toolkit";
81
+ var takeEveryHelper = (patternOrChannel, worker, ...args) => fork(function* () {
82
+ while (true) {
83
+ const action = yield take(patternOrChannel);
84
+ yield fork(worker, ...args.concat(action));
85
+ }
86
+ });
87
+ function takeEveryAsync(pattern, saga, ...args) {
88
+ return takeEveryHelper(pattern, wrap(saga), ...args);
89
+ }
90
+ function takeLatestAsync(pattern, saga, ...args) {
91
+ const tasks = {};
92
+ let deferred2;
93
+ function* wrapper(action, ...rest) {
94
+ if (deferred2) {
95
+ const lastRequestId = yield deferred2.promise;
96
+ const request = yield getRequest(lastRequestId);
97
+ if (request.abort) {
98
+ request.abort();
99
+ }
100
+ const task = yield tasks[lastRequestId].promise;
101
+ yield cancel(task);
102
+ }
103
+ deferred2 = deferred();
104
+ const { requestId } = action.meta;
105
+ yield getRequest(requestId);
106
+ if (deferred2) {
107
+ deferred2.resolve(requestId);
108
+ }
109
+ yield wrap(saga)(action, ...rest);
110
+ deferred2 = null;
111
+ }
112
+ const customTakeEvery = (patternOrChannel, saga2, ...args2) => fork(function* () {
113
+ while (true) {
114
+ const action = yield take(patternOrChannel);
115
+ const { requestId } = action.meta;
116
+ tasks[requestId] = deferred();
117
+ const task = yield fork(saga2, ...args2.concat(action));
118
+ tasks[requestId].resolve(task);
119
+ }
120
+ });
121
+ return customTakeEvery(pattern, wrapper, ...args);
122
+ }
123
+ function takeAggregateAsync(pattern, saga, ...args) {
124
+ let deferred2;
125
+ function* wrapper(action, ...rest) {
126
+ const { requestId } = action.meta;
127
+ if (deferred2) {
128
+ const request = yield getRequest(requestId);
129
+ if (request.deferred) {
130
+ const { resolve, reject } = request.deferred;
131
+ const { promise } = yield deferred2.promise;
132
+ promise.then(resolve, reject).finally(() => cleanup(requestId)).catch(() => {
133
+ });
134
+ }
135
+ } else {
136
+ deferred2 = deferred();
137
+ const request = yield getRequest(requestId);
138
+ if (request.deferred) {
139
+ const { promise } = request.deferred;
140
+ yield wrap(saga)(action, ...rest);
141
+ if (deferred2) {
142
+ deferred2.resolve({ promise });
143
+ }
144
+ deferred2 = null;
145
+ }
146
+ }
147
+ }
148
+ return takeEveryHelper(pattern, wrapper, ...args);
149
+ }
150
+ function* putAsync(action) {
151
+ const promise = yield put(action);
152
+ const result = yield promise;
153
+ return unwrapResult(result);
154
+ }
155
+
156
+ // src/index.ts
157
+ var createSagaAction = (type) => {
158
+ const thunk = createAsyncThunk(type, (_, { requestId }) => addRequest(requestId));
159
+ function actionCreator(arg) {
160
+ const originalActionCreator = thunk(arg);
161
+ return (dispatch, getState, extra) => {
162
+ const promise = originalActionCreator(dispatch, getState, extra);
163
+ if (promise.requestId) {
164
+ setRequestAbort(promise.requestId, promise.abort);
165
+ }
166
+ return promise;
167
+ };
168
+ }
169
+ Object.assign(actionCreator, {
170
+ pending: thunk.pending,
171
+ rejected: thunk.rejected,
172
+ fulfilled: thunk.fulfilled,
173
+ typePrefix: thunk.typePrefix,
174
+ type: thunk.pending.type
175
+ });
176
+ return actionCreator;
177
+ };
178
+ export {
179
+ createSagaAction,
180
+ putAsync,
181
+ takeAggregateAsync,
182
+ takeEveryAsync,
183
+ takeLatestAsync
184
+ };
185
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../node_modules/@redux-saga/deferred/dist/redux-saga-deferred.esm.js","../src/utils.ts","../src/effects.ts"],"sourcesContent":["import { createAsyncThunk } from '@reduxjs/toolkit'\nimport type { ThunkDispatch } from '@reduxjs/toolkit'\nimport type { Action } from 'redux'\nimport { addRequest, setRequestAbort } from './utils'\nimport { SagaAction } from './types'\n\nexport * from './types'\nexport * from './effects'\n\nexport const createSagaAction = <Returned, ThunkArg = void>(type: string): SagaAction<Returned, ThunkArg> => {\n const thunk = createAsyncThunk<Returned, ThunkArg>(type, (_, { requestId }) => addRequest(requestId) as Promise<Returned>)\n\n function actionCreator(arg: ThunkArg) {\n const originalActionCreator = thunk(arg)\n\n return (dispatch: ThunkDispatch<unknown, unknown, Action>, getState: () => unknown, extra: unknown) => {\n const promise = originalActionCreator(dispatch, getState, extra)\n if (promise.requestId) {\n setRequestAbort(promise.requestId, promise.abort)\n }\n\n return promise\n }\n }\n\n Object.assign(actionCreator, {\n pending: thunk.pending,\n rejected: thunk.rejected,\n fulfilled: thunk.fulfilled,\n typePrefix: thunk.typePrefix,\n type: (thunk.pending as unknown as { type: string }).type,\n })\n\n return actionCreator as unknown as SagaAction<Returned, ThunkArg>\n}\n","function deferred() {\n var def = {};\n def.promise = new Promise(function (resolve, reject) {\n def.resolve = resolve;\n def.reject = reject;\n });\n return def;\n}\nfunction arrayOfDeferred(length) {\n var arr = [];\n for (var i = 0; i < length; i++) {\n arr.push(deferred());\n }\n return arr;\n}\n\nexport { arrayOfDeferred, deferred as default };\n","import createDeferred from '@redux-saga/deferred'\nimport { Request, SagaWorker } from './types'\n\nconst requests: Record<string, Request> = {}\n\nexport const addRequest = (requestId: string) => {\n const deferred = createDeferred()\n const request: Request = {\n ...requests[requestId],\n requestId,\n deferred,\n }\n\n if (requests[requestId]) {\n requests[requestId].deferred = deferred\n if (requests[requestId].onAdd) {\n requests[requestId].onAdd(request)\n }\n } else {\n requests[requestId] = request\n }\n\n return deferred.promise\n}\n\nexport const cleanup = (requestId: string) => {\n delete requests[requestId]\n}\n\nexport const setRequestAbort = (requestId: string, abort: () => void) => {\n if (requests[requestId]) {\n requests[requestId].abort = abort\n }\n}\n\nexport function* getRequest(requestId: string): Generator<unknown, Request, unknown> {\n const request = requests[requestId]\n\n if (!request) {\n const result = yield (new Promise(onAdd => {\n requests[requestId] = {\n onAdd: (req: Request) => onAdd(req)\n }\n }))\n return result as Request\n }\n\n return request\n}\n\nexport const wrap = (saga: SagaWorker) => function* (action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {\n const { requestId } = (action as { meta: { requestId: string } }).meta\n const request = (yield getRequest(requestId)) as Request\n\n if (!request.deferred) return\n\n const deferred = request.deferred\n let isFinished = false\n\n try {\n const result = yield saga(action, ...rest)\n deferred.resolve(result)\n isFinished = true\n } catch (error) {\n deferred.reject(error)\n isFinished = true\n } finally {\n if (!isFinished) {\n deferred.reject(new Error('Aborted'))\n const currentRequest = requests[requestId]\n if (currentRequest && currentRequest.abort) {\n currentRequest.abort()\n }\n }\n cleanup(requestId)\n }\n}\n","import { put, take, fork, cancel } from 'redux-saga/effects'\nimport type { PutEffect, ActionPattern } from 'redux-saga/effects'\nimport type { Task, Channel } from 'redux-saga'\nimport type { Action } from 'redux'\nimport createDeferred from '@redux-saga/deferred'\nimport { AsyncThunkAction, unwrapResult } from '@reduxjs/toolkit'\nimport { SagaWorker, Deferred, Request } from './types'\nimport { wrap, getRequest, cleanup } from './utils'\n\n// Helper to avoid 'takeEvery' overload issues with spread arguments\nconst takeEveryHelper = (patternOrChannel: ActionPattern | Channel<Action>, worker: SagaWorker, ...args: unknown[]) => fork(function* () {\n while (true) {\n const action = (yield take(patternOrChannel as unknown as ActionPattern)) as Action\n yield fork(worker, ...args.concat(action))\n }\n})\n\nexport function takeEveryAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {\n return takeEveryHelper(pattern as ActionPattern | Channel<Action>, wrap(saga as unknown as SagaWorker), ...args)\n}\n\nexport function takeLatestAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {\n const tasks: Record<string, Deferred> = {}\n let deferred: Deferred | null\n\n function* wrapper(action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {\n if (deferred) {\n const lastRequestId = (yield deferred.promise) as string\n const request = (yield getRequest(lastRequestId)) as Request\n\n if (request.abort) {\n request.abort()\n }\n\n const task = (yield tasks[lastRequestId].promise) as Task\n\n yield cancel(task)\n }\n\n deferred = createDeferred()\n const { requestId } = (action as { meta: { requestId: string } }).meta\n\n yield getRequest(requestId) // Ensure request is registered/ready if needed\n\n if (deferred) {\n deferred.resolve(requestId)\n }\n\n yield wrap(saga as unknown as SagaWorker)(action, ...rest)\n\n deferred = null\n }\n\n const customTakeEvery = (patternOrChannel: ActionPattern | Channel<Action>, saga: SagaWorker, ...args: unknown[]) => fork(function* (): Generator<unknown, void, unknown> {\n while (true) {\n const action = (yield take(patternOrChannel as unknown as ActionPattern)) as { meta: { requestId: string } }\n const { requestId } = action.meta\n tasks[requestId] = createDeferred()\n const task = (yield fork(saga, ...args.concat(action))) as Task\n tasks[requestId].resolve(task)\n }\n })\n\n return customTakeEvery(pattern as ActionPattern | Channel<Action>, wrapper, ...args)\n}\n\nexport function takeAggregateAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {\n let deferred: Deferred | null\n\n function* wrapper(action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {\n const { requestId } = (action as { meta: { requestId: string } }).meta\n\n if (deferred) {\n const request = (yield getRequest(requestId)) as Request\n if (request.deferred) {\n const { resolve, reject } = request.deferred\n const { promise } = (yield deferred.promise) as { promise: Promise<unknown> }\n\n promise\n .then(resolve, reject)\n .finally(() => cleanup(requestId))\n .catch(() => { })\n }\n } else {\n deferred = createDeferred()\n const request = (yield getRequest(requestId)) as Request\n if (request.deferred) {\n const { promise } = request.deferred\n\n yield wrap(saga as unknown as SagaWorker)(action, ...rest)\n\n if (deferred) {\n deferred.resolve({ promise })\n }\n deferred = null\n }\n }\n }\n\n return takeEveryHelper(pattern as ActionPattern | Channel<Action>, wrapper, ...args)\n}\n\nexport function* putAsync(action: Action | PutEffect | AsyncThunkAction<unknown, unknown, object>): Generator<PutEffect | Promise<unknown>, unknown, unknown> {\n const promise = yield put(action as Action)\n const result = yield (promise as Promise<unknown>)\n return unwrapResult(result as { payload: unknown, error?: unknown, meta?: unknown })\n}\n"],"mappings":"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","names":["deferred","deferred","saga","args"]}
package/package.json CHANGED
@@ -1,11 +1,28 @@
1
1
  {
2
2
  "name": "saga-toolkit",
3
- "version": "2.1.2",
3
+ "version": "2.2.2",
4
4
  "description": "An extension for redux-toolkit that allows sagas to resolve async thunk actions.",
5
- "main": "index.js",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "src"
19
+ ],
6
20
  "scripts": {
7
- "build": "./node_modules/.bin/babel sagaToolkit.js --out-file index.js",
8
- "test": "echo \"Error: no test specified\" && exit 1"
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "test": "vitest",
24
+ "lint": "eslint src/index.ts",
25
+ "prepublishOnly": "npm run build"
9
26
  },
10
27
  "repository": {
11
28
  "type": "git",
@@ -26,11 +43,22 @@
26
43
  },
27
44
  "homepage": "https://github.com/janoist1/saga-toolkit#readme",
28
45
  "devDependencies": {
29
- "@babel/cli": "^7.16.8",
30
- "@babel/core": "^7.16.12",
31
- "@babel/node": "^7.16.8",
32
- "@babel/preset-env": "^7.16.11",
33
46
  "@reduxjs/toolkit": "^1.9.5",
34
- "redux": "^4.1.2"
47
+ "@types/node": "^20.0.0",
48
+ "@types/redux-saga": "^0.10.5",
49
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
50
+ "@typescript-eslint/parser": "^8.53.0",
51
+ "@vitest/coverage-v8": "^1.6.1",
52
+ "eslint": "^9.39.2",
53
+ "redux": "^4.1.2",
54
+ "redux-saga": "^1.2.3",
55
+ "tsup": "^8.0.0",
56
+ "typescript": "^5.0.0",
57
+ "typescript-eslint": "^8.53.0",
58
+ "vitest": "^1.0.0"
59
+ },
60
+ "peerDependencies": {
61
+ "@reduxjs/toolkit": "^1.9.5",
62
+ "redux-saga": "^1.0.0"
35
63
  }
36
- }
64
+ }
package/src/effects.ts ADDED
@@ -0,0 +1,107 @@
1
+ import { put, take, fork, cancel } from 'redux-saga/effects'
2
+ import type { PutEffect, ActionPattern } from 'redux-saga/effects'
3
+ import type { Task, Channel } from 'redux-saga'
4
+ import type { Action } from 'redux'
5
+ import createDeferred from '@redux-saga/deferred'
6
+ import { AsyncThunkAction, unwrapResult } from '@reduxjs/toolkit'
7
+ import { SagaWorker, Deferred, Request } from './types'
8
+ import { wrap, getRequest, cleanup } from './utils'
9
+
10
+ // Helper to avoid 'takeEvery' overload issues with spread arguments
11
+ const takeEveryHelper = (patternOrChannel: ActionPattern | Channel<Action>, worker: SagaWorker, ...args: unknown[]) => fork(function* () {
12
+ while (true) {
13
+ const action = (yield take(patternOrChannel as unknown as ActionPattern)) as Action
14
+ yield fork(worker, ...args.concat(action))
15
+ }
16
+ })
17
+
18
+ export function takeEveryAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {
19
+ return takeEveryHelper(pattern as ActionPattern | Channel<Action>, wrap(saga as unknown as SagaWorker), ...args)
20
+ }
21
+
22
+ export function takeLatestAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {
23
+ const tasks: Record<string, Deferred> = {}
24
+ let deferred: Deferred | null
25
+
26
+ function* wrapper(action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {
27
+ if (deferred) {
28
+ const lastRequestId = (yield deferred.promise) as string
29
+ const request = (yield getRequest(lastRequestId)) as Request
30
+
31
+ if (request.abort) {
32
+ request.abort()
33
+ }
34
+
35
+ const task = (yield tasks[lastRequestId].promise) as Task
36
+
37
+ yield cancel(task)
38
+ }
39
+
40
+ deferred = createDeferred()
41
+ const { requestId } = (action as { meta: { requestId: string } }).meta
42
+
43
+ yield getRequest(requestId) // Ensure request is registered/ready if needed
44
+
45
+ if (deferred) {
46
+ deferred.resolve(requestId)
47
+ }
48
+
49
+ yield wrap(saga as unknown as SagaWorker)(action, ...rest)
50
+
51
+ deferred = null
52
+ }
53
+
54
+ const customTakeEvery = (patternOrChannel: ActionPattern | Channel<Action>, saga: SagaWorker, ...args: unknown[]) => fork(function* (): Generator<unknown, void, unknown> {
55
+ while (true) {
56
+ const action = (yield take(patternOrChannel as unknown as ActionPattern)) as { meta: { requestId: string } }
57
+ const { requestId } = action.meta
58
+ tasks[requestId] = createDeferred()
59
+ const task = (yield fork(saga, ...args.concat(action))) as Task
60
+ tasks[requestId].resolve(task)
61
+ }
62
+ })
63
+
64
+ return customTakeEvery(pattern as ActionPattern | Channel<Action>, wrapper, ...args)
65
+ }
66
+
67
+ export function takeAggregateAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]) {
68
+ let deferred: Deferred | null
69
+
70
+ function* wrapper(action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {
71
+ const { requestId } = (action as { meta: { requestId: string } }).meta
72
+
73
+ if (deferred) {
74
+ const request = (yield getRequest(requestId)) as Request
75
+ if (request.deferred) {
76
+ const { resolve, reject } = request.deferred
77
+ const { promise } = (yield deferred.promise) as { promise: Promise<unknown> }
78
+
79
+ promise
80
+ .then(resolve, reject)
81
+ .finally(() => cleanup(requestId))
82
+ .catch(() => { })
83
+ }
84
+ } else {
85
+ deferred = createDeferred()
86
+ const request = (yield getRequest(requestId)) as Request
87
+ if (request.deferred) {
88
+ const { promise } = request.deferred
89
+
90
+ yield wrap(saga as unknown as SagaWorker)(action, ...rest)
91
+
92
+ if (deferred) {
93
+ deferred.resolve({ promise })
94
+ }
95
+ deferred = null
96
+ }
97
+ }
98
+ }
99
+
100
+ return takeEveryHelper(pattern as ActionPattern | Channel<Action>, wrapper, ...args)
101
+ }
102
+
103
+ export function* putAsync(action: Action | PutEffect | AsyncThunkAction<unknown, unknown, object>): Generator<PutEffect | Promise<unknown>, unknown, unknown> {
104
+ const promise = yield put(action as Action)
105
+ const result = yield (promise as Promise<unknown>)
106
+ return unwrapResult(result as { payload: unknown, error?: unknown, meta?: unknown })
107
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { createAsyncThunk } from '@reduxjs/toolkit'
2
+ import type { ThunkDispatch } from '@reduxjs/toolkit'
3
+ import type { Action } from 'redux'
4
+ import { addRequest, setRequestAbort } from './utils'
5
+ import { SagaAction } from './types'
6
+
7
+ export * from './types'
8
+ export * from './effects'
9
+
10
+ export const createSagaAction = <Returned, ThunkArg = void>(type: string): SagaAction<Returned, ThunkArg> => {
11
+ const thunk = createAsyncThunk<Returned, ThunkArg>(type, (_, { requestId }) => addRequest(requestId) as Promise<Returned>)
12
+
13
+ function actionCreator(arg: ThunkArg) {
14
+ const originalActionCreator = thunk(arg)
15
+
16
+ return (dispatch: ThunkDispatch<unknown, unknown, Action>, getState: () => unknown, extra: unknown) => {
17
+ const promise = originalActionCreator(dispatch, getState, extra)
18
+ if (promise.requestId) {
19
+ setRequestAbort(promise.requestId, promise.abort)
20
+ }
21
+
22
+ return promise
23
+ }
24
+ }
25
+
26
+ Object.assign(actionCreator, {
27
+ pending: thunk.pending,
28
+ rejected: thunk.rejected,
29
+ fulfilled: thunk.fulfilled,
30
+ typePrefix: thunk.typePrefix,
31
+ type: (thunk.pending as unknown as { type: string }).type,
32
+ })
33
+
34
+ return actionCreator as unknown as SagaAction<Returned, ThunkArg>
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { AsyncThunk } from '@reduxjs/toolkit'
2
+
3
+ export type SagaWorker = (...args: unknown[]) => unknown
4
+
5
+ export interface Deferred<T = unknown> {
6
+ resolve: (value: T) => void
7
+ reject: (error: unknown) => void
8
+ promise: Promise<T>
9
+ }
10
+
11
+ export interface Request {
12
+ requestId?: string
13
+ deferred?: Deferred
14
+ onAdd?: (request: Request) => void
15
+ abort?: () => void
16
+ }
17
+
18
+ export type SagaAction<Returned, ThunkArg = void> = AsyncThunk<Returned, ThunkArg, object>
package/src/utils.ts ADDED
@@ -0,0 +1,77 @@
1
+ import createDeferred from '@redux-saga/deferred'
2
+ import { Request, SagaWorker } from './types'
3
+
4
+ const requests: Record<string, Request> = {}
5
+
6
+ export const addRequest = (requestId: string) => {
7
+ const deferred = createDeferred()
8
+ const request: Request = {
9
+ ...requests[requestId],
10
+ requestId,
11
+ deferred,
12
+ }
13
+
14
+ if (requests[requestId]) {
15
+ requests[requestId].deferred = deferred
16
+ if (requests[requestId].onAdd) {
17
+ requests[requestId].onAdd(request)
18
+ }
19
+ } else {
20
+ requests[requestId] = request
21
+ }
22
+
23
+ return deferred.promise
24
+ }
25
+
26
+ export const cleanup = (requestId: string) => {
27
+ delete requests[requestId]
28
+ }
29
+
30
+ export const setRequestAbort = (requestId: string, abort: () => void) => {
31
+ if (requests[requestId]) {
32
+ requests[requestId].abort = abort
33
+ }
34
+ }
35
+
36
+ export function* getRequest(requestId: string): Generator<unknown, Request, unknown> {
37
+ const request = requests[requestId]
38
+
39
+ if (!request) {
40
+ const result = yield (new Promise(onAdd => {
41
+ requests[requestId] = {
42
+ onAdd: (req: Request) => onAdd(req)
43
+ }
44
+ }))
45
+ return result as Request
46
+ }
47
+
48
+ return request
49
+ }
50
+
51
+ export const wrap = (saga: SagaWorker) => function* (action: unknown, ...rest: unknown[]): Generator<unknown, void, unknown> {
52
+ const { requestId } = (action as { meta: { requestId: string } }).meta
53
+ const request = (yield getRequest(requestId)) as Request
54
+
55
+ if (!request.deferred) return
56
+
57
+ const deferred = request.deferred
58
+ let isFinished = false
59
+
60
+ try {
61
+ const result = yield saga(action, ...rest)
62
+ deferred.resolve(result)
63
+ isFinished = true
64
+ } catch (error) {
65
+ deferred.reject(error)
66
+ isFinished = true
67
+ } finally {
68
+ if (!isFinished) {
69
+ deferred.reject(new Error('Aborted'))
70
+ const currentRequest = requests[requestId]
71
+ if (currentRequest && currentRequest.abort) {
72
+ currentRequest.abort()
73
+ }
74
+ }
75
+ cleanup(requestId)
76
+ }
77
+ }
package/example/.env DELETED
@@ -1 +0,0 @@
1
- SKIP_PREFLIGHT_CHECK=true
package/example/README.md DELETED
@@ -1,70 +0,0 @@
1
- # Getting Started with Create React App
2
-
3
- This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4
-
5
- ## Available Scripts
6
-
7
- In the project directory, you can run:
8
-
9
- ### `yarn start`
10
-
11
- Runs the app in the development mode.\
12
- Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13
-
14
- The page will reload if you make edits.\
15
- You will also see any lint errors in the console.
16
-
17
- ### `yarn test`
18
-
19
- Launches the test runner in the interactive watch mode.\
20
- See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21
-
22
- ### `yarn build`
23
-
24
- Builds the app for production to the `build` folder.\
25
- It correctly bundles React in production mode and optimizes the build for the best performance.
26
-
27
- The build is minified and the filenames include the hashes.\
28
- Your app is ready to be deployed!
29
-
30
- See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31
-
32
- ### `yarn eject`
33
-
34
- **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35
-
36
- If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37
-
38
- Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39
-
40
- You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41
-
42
- ## Learn More
43
-
44
- You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45
-
46
- To learn React, check out the [React documentation](https://reactjs.org/).
47
-
48
- ### Code Splitting
49
-
50
- This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51
-
52
- ### Analyzing the Bundle Size
53
-
54
- This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55
-
56
- ### Making a Progressive Web App
57
-
58
- This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59
-
60
- ### Advanced Configuration
61
-
62
- This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63
-
64
- ### Deployment
65
-
66
- This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67
-
68
- ### `yarn build` fails to minify
69
-
70
- This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)