{"id":15688337,"url":"https://github.com/zordius/reservice","last_synced_at":"2025-05-07T21:23:12.598Z","repository":{"id":57354564,"uuid":"88806331","full_name":"zordius/reservice","owner":"zordius","description":"An isomorphic/universal asynchronous tasks solution for redux.","archived":false,"fork":false,"pushed_at":"2020-08-11T17:28:27.000Z","size":134,"stargazers_count":8,"open_issues_count":1,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T12:53:23.296Z","etag":null,"topics":["asynchronous-tasks","expressjs","middleware","redux"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zordius.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-20T01:17:10.000Z","updated_at":"2020-03-29T13:50:38.000Z","dependencies_parsed_at":"2022-09-12T03:21:19.070Z","dependency_job_id":null,"html_url":"https://github.com/zordius/reservice","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zordius%2Freservice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zordius%2Freservice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zordius%2Freservice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zordius%2Freservice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zordius","download_url":"https://codeload.github.com/zordius/reservice/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252955184,"owners_count":21831064,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous-tasks","expressjs","middleware","redux"],"created_at":"2024-10-03T17:58:08.693Z","updated_at":"2025-05-07T21:23:12.574Z","avatar_url":"https://github.com/zordius.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"reservice\n=========\nAn isomorphic/universal asynchronous tasks solution for redux.\n\n[![npm version](https://img.shields.io/npm/v/reservice.svg)](https://www.npmjs.org/package/reservice) [![Build Status](https://travis-ci.org/zordius/reservice.svg?branch=master)](https://travis-ci.org/zordius/reservice)  [![Test Coverage](https://codeclimate.com/github/zordius/reservice/badges/coverage.svg)](https://codeclimate.com/github/zordius/reservice) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)\n\nYou may already using \u003ca href=\"https://github.com/gaearon/redux-thunk\"\u003eredux-thunk\u003c/a\u003e for your asynchronous tasks. Put thunk, function or promise into an action makes it not pure, which means the action may not be serialized or replayed well.\n\nA better asynchronous task practice is: create your action as pure object, do asynchronous tasks in your own redux middlewares. This practice keep all your actions creators and reducers pure and clean, make your application more isomorphic or universal. The only place you put asynchronous codes are redux middlewares....Or, a better place: redux service (a.k.a. re-service).\n\nA redux service means: an asynchronous task triggered by a start service action. After it done, the result will be dispatched as another service done action.\n\n\u003cimg src=\"https://github.com/zordius/reservice/blob/master/reservice.png?raw=true\" /\u003e\n\nRe-service provides a good practice for all your asynchronous tasks, includes:\n* A helper function to create service action creator. (the action is \u003ca href=\"https://github.com/acdlite/flux-standard-action\"\u003eFSA\u003c/a\u003e compliant)\n* A redux middleware to:\n  * handle the service action\n    * At client side, transport action to server then get result.\n    * At server side, execute the service then get result.\n  * then dispatch service result action\n* An \u003ca href=\"https://www.npmjs.com/package/express\"\u003eexpress\u003c/a\u003e middleware to deal with transported service actions.\n\nInstall\n-------\n\n```\nnpm install reservice --save\n```\n\nYou will need these polyfills for older browsers or other environments:\n* [Promise](https://www.npmjs.com/search?q=promise%20polyfill\u0026page=1\u0026ranking=popularity) : [browser support](http://caniuse.com/#feat=promises)\n* [fetch](https://www.npmjs.com/search?q=fetch%20polyfill\u0026page=1\u0026ranking=popularity) : [browser support](http://caniuse.com/#feat=fetch)\n\nUsage\n-----\n\n**A Service**\n```javascript\n// req is optional, you can receive req to deal with session based tasks.\nconst myService = (payload, req) =\u003e {\n  // Do any async task you like, return a promise or result.\n  // You can not know any redux related things,\n  // but you can access the express request object here.\n  ...\n  return result;\n}\n```\n\n**A Service Action Creator**\n```javascript\nimport { createService } from 'reservice';\n\n// Check redux-actions to know more about payloadCreator\n// https://github.com/acdlite/redux-actions#createactiontype-payloadcreator--identity-metacreator\nconst doSomeThing = createService('DO_SOMETHING', payloadCreator);\n\nexpect(doSomeThing('good')).toEqual({\n  type: 'CALL_RESERVICE',\n  payload: 'good',\n  reservice: {\n    name: 'DO_SOMETHING',\n    state: 'CREATED',\n  },\n});\n```\n\n**Define Service List**\n```javascript\nconst serviceList = {\n  [doSomeThing]: myService,\n  [anotherServiceCreator]: theCodeOfAnotherService,\n  ...\n}\n```\n\n**Setup Express Application**\n```javascript\n// your server.js\nimport { createMiddlewareByServiceList } from 'reservice';\nimport bodyParser from 'body-parser';\n\n...\n\n// reservice middleware need to access body as json\napp.use(bodyParser.json());\n\n// Add this line to declare serviceList and use the express middleware\napp.use(createMiddlewareByServiceList(serviceList));\n```\n\n**The Reducer**\n```javascript\nimport { handleActions } from 'redux-actions';\n\n// create a reducer\nconst myReducer = handleActions({\n  [doSomeThing]: (state, action) =\u003e { ... },\n  [anotherServiceCreator]: anotherReducer,\n  ...\n}, initialState);\n\n// If you also want to take care service start, try this\nimport { ACTION_TYPE_RESERVICE } from 'reservice';\n\nconst nowLoadingReducer = (state = initialState, action) =\u003e {\n  if (action.type !== ACTION_TYPE_RESERVICE) {\n    return state;\n  }\n\n  // service started, remeber to set nowLoading to false in yourown reducers\n  // you can set different loading states by checking action.reservice.name\n  return { ...state, nowLoading: true };\n}\n```\n\n**Setup Redux Store**\n```javascript\nimport { createStore, applyMiddleware } from 'redux';\nimport { serviceMiddleware, settleRequest } from 'reservice';\n\nconst store = createStore(\n  myReducer,\n  applyMiddleware(serviceMiddleware)\n);\n\n// Optional: If you like to access request in service\n// You need to do this.\nstore.dispatch(settleRequest(req));\n```\n\nExample\n-------\n\nPlease check \u003ca href=\"example\"\u003eexample\u003c/a\u003e to get a deeper guide.\n\nDebug\n-----\n\n* reservice already adopt [debug](https://www.npmjs.com/package/debug) , you can export DEBUG=... to show debug log:\n  * `reservice:start` : show service name, payload\n  * `reservice:receive` : show service name, payload when client side dispatch received\n  * `reservice:success` : show service name, payload and result when service successed\n  * `reservice:fail` : show service name, payload and error when service failed\n  * `reservice:error` : show service name, payload and error.stack when service failed\n  * `reservice:select` : show service name, payload and selected result when service successed, refer to \u003ca href=\"https://github.com/zordius/reservice#advanced-usage-selector\"\u003eselector\u003c/a\u003e.\n\nOptional Usage: Change Default Setting\n--------------------------------------\n\nYou can change default service path and method by this way:\n\n```javascript\nimport { setupServiceEndpoint } from 'reservice';\n\nsetupServiceEndpoint('/mypath/my_service/');\n\n// Or also change http method\nsetupServiceEndpoint('/another/path/ok', 'POST');\n```\n\nOptional Usage: createService\n-----------------------------\n\nIf you plan to migrate from redux-thunk and prefer to reuse your action type, you may specify the startType and endType when you createService.\n\n```javascript\nconst doSomeThing = createService({\n  endType: 'DO_SOMETHING',\n  startType: 'DO_SOMETHING_STARTED',\n  payloadCreator,\n  metaCreator,\n});\n\nstore.dispatch(doSomeThing('good'));\n\nexpect(store.dispatch.calls.argsFor(0)).toEqual([{\n  type: 'CALL_RESERVICE',\n  payload: 'good',\n  reservice: {\n    name: 'DO_SOMETHING',\n    start: 'DO_SOMETHING_STARTED',\n    state: 'CREATED',\n  },\n}]);\n\n// After the reservice middleware handled the action,\n// another action will be dispatched.\nexpect(store.dispatch.calls.argsFor(1)).toEqual([{\n  type: 'DO_SOMETHING_STARTED',\n  payload: 'good',\n  reservice: {\n    name: 'DO_SOMETHING',\n    start: 'DO_SOMETHING_STARTED',\n    state: 'CREATED',\n    ...\n  },\n}]);\n\n// After the service is done, this action will be dispatched.\nexpect(store.dispatch.calls.argsFor(2)).toEqual([{\n  type: 'DO_SOMETHING',\n  payload: 'the result...',\n  error: false,\n  reservice: {\n    name: 'DO_SOMETHING',\n    start: 'DO_SOMETHING_STARTED',\n    state: 'END',\n    ...\n  },\n}]);\n```\n\nOptional Usage: Selector\n------------------------\n\nIn most case you may try to refine API response data by selector function then put it into redux store. You can do it inthe service, or do it in the reducer.\n\nIf you run selector in the service, you dispatch only selected data to reducer. This practice save time and space when the smaller service result be transmitted from server to client, but prevent you to see full API response in network or redux debugging tools.\n\nIf you run selector in the reducer, you dispatch full data to reducer. This practice may take more time and space to transmit result from server to client, but you can see full API response in debug tools.\n\nReservice provide two small functions to help you adopt all these two practices in development and production environments:\n\n```javascript\n// The selector function\nconst mySelector = result =\u003e ({\n  data: result.body.data.reduce(refineMyData, {}),\n  error: result.body.error\n});\n\n// In a service\nimport { prodSelect } from 'reservice';\n// Run your selector only when in production environment, keep original result when in development environment.\nconst myProdSelector = prodSelect(mySelector);\nconst myService = payload =\u003e callAPI(payload).then(result =\u003e myProdSelector(result));\n\n// In a reducer\nimport { devSelect } from 'reservice';\n// Run your selector only when in development environment, keep original result when in production environment.\nconst myDevSelector = devSelect(mySelector);\nconst myReducer = (state = initialState, action) =\u003e {\n  switch (action.type) {\n    case 'MY_SERVICE':\n      return { ...state, ...myDevSelector(action.payload) }\n  }\n  return state;\n}\n```\n\nAdvanced Usage: Selector\n------------------------\n\nOr, you can define service as { service, selector } , reservice will keep full result in action.reservice.full_payload for debugging when not in production environment. And, the selected result still be placed in action.payload.\n\n```javascript\n// Original Service code with result selector\nconst selectResult = (result) =\u003e result.body.items;\nexport myService = (payload, req) =\u003e callSomeApi({ ...payload, req }).then(selectResult);\n\n// change export from function into { service , selector } for better debugging info\nexport myService = {\n  service: (payload, req) =\u003e callSomeApi({ ...payload, req }),\n  selector: (result) =\u003e result.body.items,\n};\n```\n\nHere is a \u003ca href=\"https://github.com/zordius/reservice/commit/89916ad3774b25942ae5e88aa44d0a4463e7b9ec\"\u003emigration example\u003c/a\u003e to adopt reservice selector.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzordius%2Freservice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzordius%2Freservice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzordius%2Freservice/lists"}