├── .github ├── contributing.md ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── nodejs.yml │ ├── release.yml │ └── update-dependencies.yml ├── .gitignore ├── .nycrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── client └── index.js ├── lib └── index.js ├── package-lock.json ├── package.json ├── test ├── fixture.js ├── index.test.js └── integration.test.js └── types ├── index.d.ts ├── index.test.ts ├── tsconfig.json └── tslint.json /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Feathers 2 | 3 | Thank you for contributing to Feathers! :heart: :tada: 4 | 5 | This repo is the main core and where most issues are reported. Feathers embraces modularity and is broken up across many repos. To make this easier to manage we currently use [Zenhub](https://www.zenhub.com/) for issue triage and visibility. They have a free browser plugin you can install so that you can see what is in flight at any time, but of course you also always see current issues in Github. 6 | 7 | ## Report a bug 8 | 9 | Before creating an issue please make sure you have checked out the docs, specifically the [FAQ](https://docs.feathersjs.com/help/faq.html) section. You might want to also try searching Github. It's pretty likely someone has already asked a similar question. 10 | 11 | If you haven't found your answer please feel free to join our [slack channel](http://slack.feathersjs.com), create an issue on Github, or post on [Stackoverflow](http://stackoverflow.com) using the `feathers` or `feathersjs` tag. We try our best to monitor Stackoverflow but you're likely to get more immediate responses in Slack and Github. 12 | 13 | Issues can be reported in the [issue tracker](https://github.com/feathersjs/feathers/issues). Since feathers combines many modules it can be hard for us to assess the root cause without knowing which modules are being used and what your configuration looks like, so **it helps us immensely if you can link to a simple example that reproduces your issue**. 14 | 15 | ## Report a Security Concern 16 | 17 | We take security very seriously at Feathers. We welcome any peer review of our 100% open source code to ensure nobody's Feathers app is ever compromised or hacked. As a web application developer you are responsible for any security breaches. We do our very best to make sure Feathers is as secure as possible by default. 18 | 19 | In order to give the community time to respond and upgrade we strongly urge you report all security issues to us. Send one of the core team members a PM in [Slack](http://slack.feathersjs.com) or email us at hello@feathersjs.com with details and we will respond ASAP. 20 | 21 | For full details refer to our [Security docs](https://docs.feathersjs.com/SECURITY.html). 22 | 23 | ## Pull Requests 24 | 25 | We :heart: pull requests and we're continually working to make it as easy as possible for people to contribute, including a [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) and a [common test suite](https://github.com/feathersjs/feathers-service-tests) for database adapters. 26 | 27 | We prefer small pull requests with minimal code changes. The smaller they are the easier they are to review and merge. A core team member will pick up your PR and review it as soon as they can. They may ask for changes or reject your pull request. This is not a reflection of you as an engineer or a person. Please accept feedback graciously as we will also try to be sensitive when providing it. 28 | 29 | Although we generally accept many PRs they can be rejected for many reasons. We will be as transparent as possible but it may simply be that you do not have the same context or information regarding the roadmap that the core team members have. We value the time you take to put together any contributions so we pledge to always be respectful of that time and will try to be as open as possible so that you don't waste it. :smile: 30 | 31 | **All PRs (except documentation) should be accompanied with tests and pass the linting rules.** 32 | 33 | ### Code style 34 | 35 | Before running the tests from the `test/` folder `npm test` will run ESlint. You can check your code changes individually by running `npm run lint`. 36 | 37 | ### ES6 compilation 38 | 39 | Feathers uses [Babel](https://babeljs.io/) to leverage the latest developments of the JavaScript language. All code and samples are currently written in ES2015. To transpile the code in this repository run 40 | 41 | > npm run compile 42 | 43 | __Note:__ `npm test` will run the compilation automatically before the tests. 44 | 45 | ### Tests 46 | 47 | [Mocha](http://mochajs.org/) tests are located in the `test/` folder and can be run using the `npm run mocha` or `npm test` (with ESLint and code coverage) command. 48 | 49 | ### Documentation 50 | 51 | Feathers documentation is contained in Markdown files in the [feathers-docs](https://github.com/feathersjs/feathers-docs) repository. To change the documentation submit a pull request to that repo, referencing any other PR if applicable, and the docs will be updated with the next release. 52 | 53 | ## External Modules 54 | 55 | If you're written something awesome for Feathers, the Feathers ecosystem, or using Feathers please add it to the [showcase](https://docs.feathersjs.com/why/showcase.html). You also might want to check out the [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) that can be used to scaffold plugins to be Feathers compliant from the start. 56 | 57 | If you think it would be a good core module then please contact one of the Feathers core team members in [Slack](http://slack.feathersjs.com) and we can discuss whether it belongs in core and how to get it there. :beers: 58 | 59 | ## Contributor Code of Conduct 60 | 61 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 62 | 63 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 64 | 65 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 66 | 67 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 68 | 69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 72 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Steps to reproduce 2 | 3 | (First please check that this issue is not already solved as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#report-a-bug)) 5 | 6 | - [ ] Tell us what broke. The more detailed the better. 7 | - [ ] If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc. 8 | 9 | ### Expected behavior 10 | Tell us what should happen 11 | 12 | ### Actual behavior 13 | Tell us what happens instead 14 | 15 | ### System configuration 16 | 17 | Tell us about the applicable parts of your setup. 18 | 19 | **Module versions** (especially the part that's not working): 20 | 21 | **NodeJS version**: 22 | 23 | **Operating System**: 24 | 25 | **Browser Version**: 26 | 27 | **React Native Version**: 28 | 29 | **Module Loader**: -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | (If you have not already please refer to the contributing guideline as [described 4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#pull-requests)) 5 | 6 | - [ ] Tell us about the problem your pull request is solving. 7 | - [ ] Are there any open issues that are related to this? 8 | - [ ] Is this PR dependent on PRs in other repos? 9 | 10 | If so, please mention them to keep the conversations linked together. 11 | 12 | ### Other Information 13 | 14 | If there's anything else that's important and relevant to your pull 15 | request, mention that information here. This could include 16 | benchmarks, or other information. 17 | 18 | Your PR will be reviewed by a core team member and they will work with you to get your changes merged in a timely manner. If merged your PR will automatically be added to the changelog in the next release. 19 | 20 | If your changes involve documentation updates please mention that and link the appropriate PR in [feathers-docs](https://github.com/feathersjs/feathers-docs). 21 | 22 | Thanks for contributing to Feathers! :heart: -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [13.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm test 22 | env: 23 | CI: true -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - release 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | name: Release 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 14 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Release 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | run: npx semantic-release 28 | -------------------------------------------------------------------------------- /.github/workflows/update-dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Update dependencies 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 1 * *' 6 | workflow_dispatch: 7 | jobs: 8 | update-dependencies: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Use Node.js 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: '15.x' 16 | - run: npm ci 17 | - run: | 18 | git config user.name "GitHub Actions Bot" 19 | git config user.email "hello@feathersjs.com" 20 | git checkout -b update-dependencies-$GITHUB_RUN_ID 21 | - run: | 22 | npm run update-dependencies 23 | npm install 24 | - run: | 25 | git commit -am "chore(dependencies): Update dependencies" 26 | git push origin update-dependencies-$GITHUB_RUN_ID 27 | - run: | 28 | gh pr create --title "fix(dependencies): Update all dependencies" --body "" 29 | env: 30 | GITHUB_TOKEN: ${{secrets.CI_ACCESS_TOKEN}} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "tempDirectory": "./coverage/.tmp", 4 | "semistandard": { 5 | "env": [ 6 | "mocha" 7 | ] 8 | }, 9 | "extension": [ 10 | ".ts", 11 | ".tsx", 12 | ".js" 13 | ], 14 | "exclude": [ 15 | "**/test/*", 16 | "**/dist/*", 17 | "**/*.dist.js", 18 | "**/templates/*", 19 | "**/adapter-commons/src/sort.ts" 20 | ], 21 | "print": "detail", 22 | "reporter": [ 23 | "html", 24 | "text", 25 | "text-summary", 26 | "lcov" 27 | ], 28 | "watermarks": { 29 | "statements": [ 30 | 70, 31 | 90 32 | ], 33 | "lines": [ 34 | 70, 35 | 90 36 | ], 37 | "functions": [ 38 | 70, 39 | 90 40 | ], 41 | "branches": [ 42 | 70, 43 | 90 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.1](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v1.1.0...v1.1.1) (2021-07-13) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **dependencies:** Update all dependencies ([#105](https://github.com/feathersjs-ecosystem/feathers-batch/issues/105)) ([fe4caf6](https://github.com/feathersjs-ecosystem/feathers-batch/commit/fe4caf60e37eebdb998631ce0b0a54fab3df4996)) 7 | 8 | # Changelog 9 | 10 | # [1.1.0](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v1.0.1...v1.1.0) (2021-01-02) 11 | 12 | 13 | ### Features 14 | 15 | * **release:** Set up semantic release ([#87](https://github.com/feathersjs-ecosystem/feathers-batch/issues/87)) ([1cdf7a2](https://github.com/feathersjs-ecosystem/feathers-batch/commit/1cdf7a27ffcc39138ec8c918c3153f80d63db40c)) 16 | 17 | 18 | ## [v1.0.1](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v1.0.1) (2020-10-07) 19 | 20 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v1.0.0...v1.0.1) 21 | 22 | **Merged pull requests:** 23 | 24 | - Bump @types/node from 14.11.2 to 14.11.5 [\#59](https://github.com/feathersjs-ecosystem/feathers-batch/pull/59) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 25 | 26 | ## [v1.0.0](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v1.0.0) (2020-10-04) 27 | 28 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v0.2.1...v1.0.0) 29 | 30 | **Fixed bugs:** 31 | 32 | - TypeError: Cannot read property 'service' of undefined [\#43](https://github.com/feathersjs-ecosystem/feathers-batch/issues/43) 33 | 34 | **Merged pull requests:** 35 | 36 | - New modern version of feathers-batch [\#58](https://github.com/feathersjs-ecosystem/feathers-batch/pull/58) ([daffl](https://github.com/daffl)) 37 | 38 | ## [v0.2.1](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v0.2.1) (2018-02-27) 39 | 40 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v0.2.0...v0.2.1) 41 | 42 | **Closed issues:** 43 | 44 | - How to use patch? [\#33](https://github.com/feathersjs-ecosystem/feathers-batch/issues/33) 45 | 46 | **Merged pull requests:** 47 | 48 | - Fix repository location [\#42](https://github.com/feathersjs-ecosystem/feathers-batch/pull/42) ([daffl](https://github.com/daffl)) 49 | 50 | ## [v0.2.0](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v0.2.0) (2018-02-27) 51 | 52 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v0.1.1...v0.2.0) 53 | 54 | **Closed issues:** 55 | 56 | - Does not work with Feathers v3.x - because callbacks [\#36](https://github.com/feathersjs-ecosystem/feathers-batch/issues/36) 57 | - An in-range update of feathers-memory is breaking the build 🚨 [\#31](https://github.com/feathersjs-ecosystem/feathers-batch/issues/31) 58 | - No response [\#18](https://github.com/feathersjs-ecosystem/feathers-batch/issues/18) 59 | 60 | **Merged pull requests:** 61 | 62 | - Prepare for release [\#41](https://github.com/feathersjs-ecosystem/feathers-batch/pull/41) ([daffl](https://github.com/daffl)) 63 | - Feathers 3 support [\#39](https://github.com/feathersjs-ecosystem/feathers-batch/pull/39) ([NikitaVlaznev](https://github.com/NikitaVlaznev)) 64 | - Update semistandard to the latest version 🚀 [\#35](https://github.com/feathersjs-ecosystem/feathers-batch/pull/35) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 65 | - Update mocha to the latest version 🚀 [\#32](https://github.com/feathersjs-ecosystem/feathers-batch/pull/32) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 66 | - Update debug to the latest version 🚀 [\#30](https://github.com/feathersjs-ecosystem/feathers-batch/pull/30) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 67 | - Update semistandard to the latest version 🚀 [\#28](https://github.com/feathersjs-ecosystem/feathers-batch/pull/28) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 68 | - Update dependencies to enable Greenkeeper 🌴 [\#27](https://github.com/feathersjs-ecosystem/feathers-batch/pull/27) ([greenkeeper[bot]](https://github.com/apps/greenkeeper)) 69 | - Update feathers-memory to version 1.0.0 🚀 [\#25](https://github.com/feathersjs-ecosystem/feathers-batch/pull/25) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 70 | - Update feathers-commons to version 0.8.0 🚀 [\#24](https://github.com/feathersjs-ecosystem/feathers-batch/pull/24) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 71 | - 👻😱 Node.js 0.10 is unmaintained 😱👻 [\#23](https://github.com/feathersjs-ecosystem/feathers-batch/pull/23) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 72 | - jshint —\> semistandard [\#22](https://github.com/feathersjs-ecosystem/feathers-batch/pull/22) ([corymsmith](https://github.com/corymsmith)) 73 | - Update feathers-memory to version 0.8.0 🚀 [\#19](https://github.com/feathersjs-ecosystem/feathers-batch/pull/19) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 74 | - Update README.md [\#17](https://github.com/feathersjs-ecosystem/feathers-batch/pull/17) ([yogex](https://github.com/yogex)) 75 | - Update mocha to version 3.0.0 🚀 [\#16](https://github.com/feathersjs-ecosystem/feathers-batch/pull/16) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 76 | - feathers-memory@0.7.3 untested ⚠️ [\#15](https://github.com/feathersjs-ecosystem/feathers-batch/pull/15) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 77 | - mocha@2.5.0 breaks build 🚨 [\#7](https://github.com/feathersjs-ecosystem/feathers-batch/pull/7) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 78 | - Update babel-plugin-add-module-exports to version 0.2.0 🚀 [\#6](https://github.com/feathersjs-ecosystem/feathers-batch/pull/6) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 79 | - Update feathers-memory to version 0.7.0 🚀 [\#4](https://github.com/feathersjs-ecosystem/feathers-batch/pull/4) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 80 | - Update feathers-commons to version 0.7.1 🚀 [\#3](https://github.com/feathersjs-ecosystem/feathers-batch/pull/3) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 81 | - Update all dependencies 🌴 [\#2](https://github.com/feathersjs-ecosystem/feathers-batch/pull/2) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot)) 82 | 83 | ## [v0.1.1](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v0.1.1) (2015-08-22) 84 | 85 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/v0.1.0...v0.1.1) 86 | 87 | **Closed issues:** 88 | 89 | - Endless wait instead of response [\#1](https://github.com/feathersjs-ecosystem/feathers-batch/issues/1) 90 | 91 | ## [v0.1.0](https://github.com/feathersjs-ecosystem/feathers-batch/tree/v0.1.0) (2015-05-17) 92 | 93 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-batch/compare/71e890610a4e93acc5fb11ff8908dee8ba025d2b...v0.1.0) 94 | 95 | 96 | 97 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Feathers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

feathers-batch

2 | 3 | [![CI](https://github.com/feathersjs-ecosystem/feathers-batch/workflows/CI/badge.svg)](https://github.com/feathersjs-ecosystem/feathers-batch/actions?query=workflow%3ACI) 4 | [![Dependency Status](https://img.shields.io/david/feathersjs-ecosystem/feathers-batch.svg?style=flat-square)](https://david-dm.org/feathersjs-ecosystem/feathers-batch) 5 | [![Download Status](https://img.shields.io/npm/dm/feathers-batch.svg?style=flat-square)](https://www.npmjs.com/package/feathers-batch) 6 | [![Slack Status](http://slack.feathersjs.com/badge.svg)](http://slack.feathersjs.com) 7 | 8 | > Batch multiple Feathers service calls into one 9 | 10 | 11 | 12 | - [About](#about) 13 | - [Service](#service) 14 | - [Usage](#usage) 15 | - [Batch calls](#batch-calls) 16 | - [Authentication](#authentication) 17 | - [Client](#client) 18 | - [Usage](#usage-1) 19 | - [Options](#options) 20 | - [Parallelizing requests](#parallelizing-requests) 21 | - [License](#license) 22 | 23 | 24 | 25 | ## About 26 | 27 | `feathers-batch` allows to batch multiple service requests into one. This is useful for minimizing client side requests to any Feathers API and can additionally speed up batched requests by only [performing authentication once](#authentication). 28 | 29 | It also comes with a client side module that automatically collects API requests from a [Feathers client]() into a batch. 30 | 31 | `feathers-batch` consists of two parts: 32 | 33 | - The server side [batch service](#service) to execute batch calls 34 | - The client side [batch client](#client) to collect parallel requests from a [Feathers client]() into a batch service request 35 | 36 | ``` 37 | npm install feathers-batch --save 38 | ``` 39 | 40 | ## Service 41 | 42 | The batch service is a normal Feathers service that executes the batch calls. 43 | 44 | ### Usage 45 | 46 | It can be registered by adding the following to your `src/services/index.js|ts`: 47 | 48 | ```js 49 | const { BatchService } = require('feathers-batch'); 50 | 51 | module.exports = function (app) { 52 | // ... 53 | app.use('batch', new BatchService(app)); 54 | } 55 | ``` 56 | 57 | ### Batch calls 58 | 59 | Now multiple service calls can be made by sending a `create` (`POST`) call to `/batch` with a `{ calls: [] }` property. `calls` is an array in the same format as the [Socket.io direct connection](https://docs.feathersjs.com/api/client/socketio.html#direct-connection) events: 60 | 61 | ```js 62 | { 63 | "calls": [ 64 | [ "method", "serviceName", /* list of parameters */ ], 65 | ... 66 | ] 67 | } 68 | ``` 69 | 70 | > __Note:__ When using a Feathers client with the [batch client](#client) this will be done automatically. 71 | 72 | For example, the following will execute a batch call to `app.service('users').get(1, { query: { active: true } })` and `app.service('messages').find({ query: { userId } })`: 73 | 74 | ```js 75 | { 76 | "calls": [ 77 | [ "get", "users", 1, { active: true } ], 78 | [ "find", "messages", { userId } ] 79 | ] 80 | } 81 | ``` 82 | 83 | The return value will be the information as returned by [Promise.allSettled](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled): 84 | 85 | ```js 86 | [ 87 | { 88 | "status": : "fulfilled", 89 | "value": { /* user object returned by app.service('users').get(1) */ } 90 | }, { 91 | "status": : "fulfilled", 92 | "value": { /* page returned by app.service('messages').find({ query: { userId } }) */ } 93 | } 94 | ] 95 | ``` 96 | 97 | If an error happened: 98 | 99 | ```js 100 | [ 101 | { 102 | "status": : "fulfilled", 103 | "value": { /* user object returned by app.service('users').get(1) */ } 104 | }, { 105 | "status": : "rejected", 106 | "reason": { /* error JSON or object with error message */ } 107 | } 108 | ] 109 | ``` 110 | 111 | ### Authentication 112 | 113 | If you are batching authenticated requests, it is possible to perform the authentication step only once (instead of on every service call) in a batch by adding the [authenticate hook](https://docs.feathersjs.com/api/authentication/hook.html) to the batch service `create` method: 114 | 115 | ```js 116 | app.service('batch').hooks({ 117 | before: { 118 | create: [ authenticate('jwt') ] 119 | } 120 | }); 121 | ``` 122 | 123 | ## Client 124 | 125 | `feathers-batch` also exports a client side module that can be used with [Feathers on the client](https://docs.feathersjs.com/api/client.html) that automatically collects multiple requests that are made at the same time into a single batch call. This works for any transport mechanism (REST, Socket.io etc.). 126 | 127 | ### Usage 128 | 129 | Batching on the client can be enabled like this: 130 | 131 | ```js 132 | // If your module loader supports the `browser` package.json field 133 | import { batchClient } from 'feathers-batch'; 134 | // Alternatively 135 | import { batchClient } from 'feathers-batch/client'; 136 | 137 | const client = feathers(); 138 | // configure Feathers client here 139 | 140 | // `batchClient` should be configured *after* 141 | // any other application level hooks 142 | client.configure(batchClient({ 143 | batchService: 'batch' 144 | })); 145 | ``` 146 | 147 | Now you can continue to make normal service calls and whenever possible they will be automatically combined into a batch (see [parallelizing requests](#parallelizing-requests) for more information). 148 | 149 | ### Options 150 | 151 | The following options are available for the `batchClient`: 152 | 153 | - `batchService` (*required*) - The name of the batch service 154 | - `exclude` (*optional*) - An array of service names that should be excluded from batching 155 | - `timeout` (*optional*) (default: `50`) - The number of milliseconds to wait when collecting parallel requests. 156 | 157 | ### Parallelizing requests 158 | 159 | At the same time means e.g. multiple components making requests to the API in parallel. The following example will __NOT__ be collected into a batch since the calls run sequentially using `await`: 160 | 161 | ```js 162 | const user = await client.service('users').get(userId); 163 | const messages = await client.service('messages').find({ 164 | query: { userId } 165 | }); 166 | ``` 167 | 168 | If the requests are not dependent on each other and you want to batch them, [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) needs to be used: 169 | 170 | ```js 171 | const [ user, messages ] = await Promise.all([ 172 | client.service('users').get(userId), 173 | client.service('messages').find({ 174 | query: { userId } 175 | }) 176 | ]); 177 | ``` 178 | 179 | ## License 180 | 181 | Copyright (c) 2020 Feathers contributors 182 | 183 | Licensed under the [MIT license](LICENSE). 184 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | const { convert } = require('@feathersjs/errors'); 2 | 3 | class BatchManager { 4 | constructor (app, options) { 5 | this.app = app; 6 | this.batches = []; 7 | this.timeout = null; 8 | this.options = options; 9 | } 10 | 11 | addBatchCall (batch) { 12 | this.batches.push(batch); 13 | 14 | if (this.timeout === null) { 15 | this.timeout = setTimeout(() => 16 | this.flush() 17 | , this.options.timeout || 50); 18 | } 19 | } 20 | 21 | async flush () { 22 | const currentBatches = this.batches; 23 | 24 | this.batches = []; 25 | this.timeout = null; 26 | 27 | const { batchService } = this.options; 28 | const results = await this.app.service(batchService).create({ 29 | calls: currentBatches.map(({ payload }) => payload) 30 | }); 31 | 32 | currentBatches.forEach((batch, index) => { 33 | const callResult = results[index]; 34 | 35 | if (callResult.status === 'fulfilled') { 36 | batch.resolve(callResult.value); 37 | } else { 38 | batch.reject(convert(callResult.reason)); 39 | } 40 | }); 41 | } 42 | } 43 | 44 | const makeArguments = context => { 45 | const { query = {} } = context.params; 46 | 47 | switch (context.method) { 48 | case 'get': 49 | case 'remove': 50 | return [context.id, query]; 51 | case 'update': 52 | case 'patch': 53 | return [context.id, context.data, query]; 54 | case 'create': 55 | return [context.data, query]; 56 | default: 57 | return [query]; 58 | } 59 | }; 60 | 61 | const batchClient = options => app => { 62 | if (typeof options.batchService !== 'string') { 63 | throw new Error('`batchService` name option must be passed to batchClient'); 64 | } 65 | 66 | const excludes = (options.exclude || []).concat(options.batchService); 67 | const manager = new BatchManager(app, options); 68 | const collectBatches = async context => { 69 | const { method, path } = context; 70 | 71 | if (excludes.includes(path)) { 72 | return context; 73 | } 74 | 75 | const args = makeArguments(context); 76 | const payload = [method, path, ...args]; 77 | const batchPromise = new Promise((resolve, reject) => manager.addBatchCall({ 78 | resolve, 79 | reject, 80 | payload 81 | })); 82 | 83 | context.result = await batchPromise; 84 | 85 | return context; 86 | }; 87 | 88 | app.hooks({ 89 | before: collectBatches 90 | }); 91 | }; 92 | 93 | exports.BatchManager = BatchManager; 94 | exports.makeArguments = makeArguments; 95 | exports.batchClient = batchClient; 96 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const { NotFound, BadRequest } = require('@feathersjs/errors'); 2 | 3 | const paramsPositions = { 4 | find: 0, 5 | get: 1, 6 | remove: 1, 7 | create: 1, 8 | update: 2, 9 | patch: 2 10 | }; 11 | 12 | exports.BatchService = class BatchService { 13 | constructor (app) { 14 | this.app = app; 15 | } 16 | 17 | async create (data, params) { 18 | const { calls = [] } = data; 19 | const settledPromises = await Promise.allSettled(calls.map(async payload => { 20 | const [method, serviceName, ...args] = payload; 21 | const paramPosition = paramsPositions[method]; 22 | const query = args[paramPosition] || {}; 23 | const serviceParams = { 24 | ...params, 25 | query 26 | }; 27 | 28 | const service = this.app.service(serviceName); 29 | 30 | if (!service) { 31 | throw new NotFound(`Invalid service ${serviceName}`); 32 | } 33 | 34 | if (paramPosition === undefined || typeof service[method] !== 'function') { 35 | throw new BadRequest(`Invalid method ${method} on ${serviceName}`); 36 | } 37 | 38 | args[paramPosition] = serviceParams; 39 | 40 | return service[method](...args); 41 | })); 42 | 43 | return settledPromises.map(current => { 44 | if (current.status === 'rejected') { 45 | const convertedError = typeof current.reason.toJSON === 'function' 46 | ? current.reason.toJSON() 47 | : { message: current.reason.message }; 48 | 49 | return { 50 | ...current, 51 | reason: convertedError 52 | }; 53 | } 54 | 55 | return current; 56 | }); 57 | } 58 | 59 | setup () { 60 | if (typeof this.publish === 'function') { 61 | this.publish(() => false); 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-batch", 3 | "description": "Batch multiple Feathers service calls into one", 4 | "version": "1.1.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/feathersjs-ecosystem/feathers-batch.git" 8 | }, 9 | "license": "MIT", 10 | "bugs": { 11 | "url": "https://github.com/feathersjs-ecosystem/feathers-batch/issues" 12 | }, 13 | "homepage": "https://github.com/feathersjs-ecosystem/feathers-batch", 14 | "keywords": [ 15 | "feathers", 16 | "feathers-plugin", 17 | "batch", 18 | "rest", 19 | "api" 20 | ], 21 | "author": "David Luecke (https://feathersjs.com)", 22 | "contributors": [], 23 | "engines": { 24 | "node": ">= 12" 25 | }, 26 | "main": "lib/", 27 | "types": "types/", 28 | "scripts": { 29 | "lint": "semistandard --fix", 30 | "mocha": "mocha --exit --recursive test/", 31 | "coverage": "nyc npm run mocha", 32 | "test": "npm run lint && npm run dtslint && npm run coverage", 33 | "dtslint": "dtslint types", 34 | "update-dependencies": "ncu -u" 35 | }, 36 | "browser": "./client/index.js", 37 | "semistandard": { 38 | "env": [ 39 | "mocha" 40 | ] 41 | }, 42 | "directories": { 43 | "lib": "lib" 44 | }, 45 | "files": [ 46 | "CHANGELOG.md", 47 | "LICENSE", 48 | "README.md", 49 | "lib/**", 50 | "client/**", 51 | "types/**", 52 | "*.d.ts", 53 | "*.js" 54 | ], 55 | "release": { 56 | "branches": [ 57 | "release" 58 | ], 59 | "plugins": [ 60 | "@semantic-release/commit-analyzer", 61 | "@semantic-release/release-notes-generator", 62 | "@semantic-release/changelog", 63 | "@semantic-release/npm", 64 | "@semantic-release/git" 65 | ] 66 | }, 67 | "dependencies": { 68 | "@feathersjs/errors": "^4.5.15" 69 | }, 70 | "devDependencies": { 71 | "@feathersjs/express": "^4.5.15", 72 | "@feathersjs/feathers": "^4.5.15", 73 | "@feathersjs/rest-client": "^4.5.15", 74 | "@feathersjs/socketio": "^4.5.15", 75 | "@semantic-release/changelog": "^6.0.1", 76 | "@semantic-release/git": "^10.0.1", 77 | "@types/node": "^18.7.14", 78 | "axios": "^0.27.2", 79 | "dtslint": "^4.2.1", 80 | "feathers-memory": "^4.1.0", 81 | "mocha": "^10.0.0", 82 | "npm-check-updates": "^16.0.6", 83 | "nyc": "^15.1.0", 84 | "semistandard": "^16.0.1", 85 | "typescript": "^4.8.2" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/fixture.js: -------------------------------------------------------------------------------- 1 | const feathers = require('@feathersjs/feathers'); 2 | const express = require('@feathersjs/express'); 3 | const socketio = require('@feathersjs/socketio'); 4 | const { NotAcceptable } = require('@feathersjs/errors'); 5 | const memory = require('feathers-memory'); 6 | 7 | const { BatchService } = require('../lib'); 8 | 9 | const app = express(feathers()); 10 | 11 | app.configure(socketio()); 12 | app.configure(express.rest()); 13 | app.use(express.json()); 14 | app.use('/dummy', { 15 | async get (id) { 16 | if (id === 'feathers-error') { 17 | throw new NotAcceptable('No!'); 18 | } 19 | 20 | if (id === 'error') { 21 | throw Error('This did not work'); 22 | } 23 | 24 | return { id }; 25 | } 26 | }); 27 | app.use('/people', memory()); 28 | 29 | app.use('/batch', new BatchService(app)); 30 | 31 | exports.app = app; 32 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const { app } = require('./fixture'); 3 | 4 | describe('feathers-batch', () => { 5 | it('initialized the service', () => { 6 | assert.ok(app.service('batch')); 7 | }); 8 | 9 | it('makes a batch call', async () => { 10 | const results = await app.service('batch').create({ 11 | calls: [ 12 | ['get', 'dummy', 'testing'], 13 | ['get', 'dummy', 'testing 2'], 14 | ['get', 'dummy', 'testing 3'] 15 | ] 16 | }); 17 | 18 | assert.deepStrictEqual(results, [ 19 | { status: 'fulfilled', value: { id: 'testing' } }, 20 | { status: 'fulfilled', value: { id: 'testing 2' } }, 21 | { status: 'fulfilled', value: { id: 'testing 3' } } 22 | ]); 23 | }); 24 | 25 | it('works with no batch calls', async () => { 26 | const results = await app.service('batch').create({ 27 | dummy: true 28 | }); 29 | 30 | assert.deepStrictEqual(results, []); 31 | }); 32 | 33 | it('makes batch calls with errors', async () => { 34 | const results = await app.service('batch').create({ 35 | calls: [ 36 | ['get', 'dummy', 'error'], 37 | ['get', 'dummy', 'testing 2'], 38 | ['get', 'dummy', 'feathers-error'], 39 | ['get', 'foobar', 1], 40 | ['blabla', 'dummy', 1] 41 | ] 42 | }); 43 | 44 | assert.deepStrictEqual(results, [ 45 | { status: 'rejected', reason: { message: 'This did not work' } }, 46 | { status: 'fulfilled', value: { id: 'testing 2' } }, 47 | { 48 | status: 'rejected', 49 | reason: { 50 | name: 'NotAcceptable', 51 | message: 'No!', 52 | code: 406, 53 | className: 'not-acceptable', 54 | data: undefined, 55 | errors: {} 56 | } 57 | }, { 58 | status: 'rejected', 59 | reason: { 60 | name: 'NotFound', 61 | message: 'Invalid service foobar', 62 | code: 404, 63 | className: 'not-found', 64 | data: undefined, 65 | errors: {} 66 | } 67 | }, { 68 | status: 'rejected', 69 | reason: { 70 | name: 'BadRequest', 71 | message: 'Invalid method blabla on dummy', 72 | code: 400, 73 | className: 'bad-request', 74 | data: undefined, 75 | errors: {} 76 | } 77 | } 78 | ]); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/integration.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const axios = require('axios'); 4 | const feathers = require('@feathersjs/feathers'); 5 | const restClient = require('@feathersjs/rest-client'); 6 | 7 | const { app } = require('./fixture'); 8 | const { batchClient } = require('../client'); 9 | 10 | describe('feathers-batch client', () => { 11 | const client = feathers(); 12 | 13 | client.configure(restClient('http://localhost:7865').axios(axios)); 14 | client.configure(batchClient({ 15 | batchService: 'batch' 16 | })); 17 | 18 | before(async () => { 19 | await new Promise(resolve => { 20 | app.listen(7865).once('listening', () => resolve()); 21 | }); 22 | }); 23 | 24 | it('errors with wrong options', () => { 25 | assert.throws(() => feathers().configure(batchClient({})), { 26 | message: '`batchService` name option must be passed to batchClient' 27 | }); 28 | }); 29 | 30 | it('does a batch call', async () => { 31 | const result = await client.service('batch').create({ 32 | calls: [ 33 | ['get', 'dummy', 'testing'] 34 | ] 35 | }); 36 | 37 | assert.deepStrictEqual(result, [ 38 | { status: 'fulfilled', value: { id: 'testing' } } 39 | ]); 40 | }); 41 | 42 | it('collect batches of multiple calls', async () => { 43 | const batchPromise = new Promise(resolve => { 44 | app.service('batch').hooks({ 45 | after: { 46 | create: context => { 47 | resolve(context.result); 48 | return context; 49 | } 50 | } 51 | }); 52 | }); 53 | 54 | const results = await Promise.all([ 55 | client.service('dummy').get('test 1'), 56 | client.service('dummy').get('test 2'), 57 | client.service('dummy').get('test 3') 58 | ]); 59 | 60 | assert.deepStrictEqual(results, [ 61 | { id: 'test 1' }, 62 | { id: 'test 2' }, 63 | { id: 'test 3' } 64 | ]); 65 | 66 | assert.deepStrictEqual(await batchPromise, [ 67 | { status: 'fulfilled', value: { id: 'test 1' } }, 68 | { status: 'fulfilled', value: { id: 'test 2' } }, 69 | { status: 'fulfilled', value: { id: 'test 3' } } 70 | ]); 71 | }); 72 | 73 | it('collects single batch with error', async () => { 74 | try { 75 | await client.service('dummy').get('feathers-error'); 76 | assert.fail('Should never get here'); 77 | } catch (error) { 78 | assert.deepStrictEqual(error.toJSON(), { 79 | name: 'NotAcceptable', 80 | message: 'No!', 81 | code: 406, 82 | className: 'not-acceptable', 83 | data: undefined, 84 | errors: {} 85 | }); 86 | } 87 | }); 88 | 89 | it('works with all service methods', async () => { 90 | const people = client.service('people'); 91 | const person = await people.create({ 92 | name: 'Dave' 93 | }); 94 | 95 | const [otherPerson, patchedPerson] = await Promise.all([ 96 | people.create({ name: 'Other Dave' }), 97 | people.patch(person.id, { name: 'Davester' }) 98 | ]); 99 | 100 | assert.deepStrictEqual(otherPerson, { name: 'Other Dave', id: 1 }); 101 | assert.deepStrictEqual(patchedPerson, { name: 'Davester', id: 0 }); 102 | 103 | const [gotPerson, allPeople] = await Promise.all([ 104 | people.get(person.id), 105 | people.find() 106 | ]); 107 | 108 | assert.deepStrictEqual(gotPerson, patchedPerson); 109 | assert.deepStrictEqual(allPeople, [ 110 | { name: 'Davester', id: 0 }, 111 | { name: 'Other Dave', id: 1 } 112 | ]); 113 | 114 | await Promise.all([ 115 | people.remove(person.id), 116 | people.remove(otherPerson.id) 117 | ]); 118 | 119 | assert.deepStrictEqual(await people.find(), []); 120 | }); 121 | 122 | it('does resolve and reject from a batch', async () => { 123 | const results = await Promise.allSettled([ 124 | client.service('dummy').get('testing'), 125 | client.service('dummy').get('error') 126 | ]); 127 | 128 | assert.deepStrictEqual(results[0].value, { id: 'testing' }); 129 | assert.strictEqual(results[1].reason.message, 'This did not work'); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | // TypeScript Version: 4.0 2 | import { Application, Id, Query, ServiceMethods } from '@feathersjs/feathers'; 3 | 4 | export type BatchCall = [ method: 'find', service: string, query?: Query ] | 5 | [ method: 'get', service: string, id: Id, query?: Query ] | 6 | [ method: 'remove', service: string, id: Id, query?: Query ] | 7 | [ method: 'update', service: string, id: Id, data: any, query?: Query ] | 8 | [ method: 'patch', service: string, id: Id, data: any, query?: Query ] | 9 | [ method: 'create', service: string, data: any, query?: Query ]; 10 | 11 | export interface BatchData { 12 | calls: BatchCall[]; 13 | } 14 | 15 | export class BatchService implements Partial> { 16 | constructor(app: Application); 17 | create(data: BatchData): Promise; 18 | } 19 | 20 | export interface BatchClientOptions { 21 | batchService: string; 22 | timeout?: number; 23 | exclude?: string[]; 24 | } 25 | 26 | export function batchClient(options: BatchClientOptions): (app: Application) => void; 27 | -------------------------------------------------------------------------------- /types/index.test.ts: -------------------------------------------------------------------------------- 1 | import feathers from '@feathersjs/feathers'; 2 | import { BatchService } from 'feathers-batch'; 3 | import { batchClient } from 'index'; 4 | 5 | const app = feathers(); 6 | 7 | app.use('/batch', new BatchService(app)); 8 | 9 | const service: BatchService = app.service('batch'); 10 | 11 | service.create({ 12 | calls: [ 13 | [ 'find', 'messages', {} ], 14 | [ 'get', 'user', 1 ] 15 | ] 16 | }); 17 | 18 | app.configure(batchClient({ 19 | batchService: 'batch' 20 | })); 21 | -------------------------------------------------------------------------------- /types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es6", "ES2020"], 5 | "target": "ES2015", 6 | "noImplicitAny": true, 7 | "noImplicitThis": true, 8 | "strictNullChecks": true, 9 | "strictFunctionTypes": true, 10 | "noEmit": true, 11 | 12 | // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index". 13 | // If the library is global (cannot be imported via `import` or `require`), leave this out. 14 | "baseUrl": ".", 15 | "paths": { "feathers-batch": ["."] } 16 | } 17 | } -------------------------------------------------------------------------------- /types/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "dtslint/dtslint.json", // Or "dtslint/dt.json" if on DefinitelyTyped 3 | "rules": { 4 | "indent": [true, "spaces"] 5 | } 6 | } --------------------------------------------------------------------------------