├── .mocharc.json ├── .npmrc ├── .gitignore ├── .babelrc ├── src ├── index.js ├── authClient.js └── restClient.js ├── .editorconfig ├── tsconfig.json ├── .circleci └── config.yml ├── .eslintrc ├── LICENSE ├── test ├── types │ └── index.test-d.ts ├── authClient.spec.js └── restClient.spec.js ├── package.json ├── .all-contributorsrc ├── README.md └── CHANGELOG.md /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": "5000" 3 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | engine-strict=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | .npm 4 | coverage 5 | .nyc_output 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": [ 4 | "add-module-exports" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as authClient } from './authClient'; 2 | export { default as restClient } from './restClient'; 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*" 4 | ], 5 | "compilerOptions": { 6 | "allowJs": true, 7 | "moduleResolution": "node", 8 | "target": "ES2015", 9 | "declaration": true, 10 | "isolatedModules": true, 11 | "emitDeclarationOnly": true, 12 | "outDir": "lib", 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "skipLibCheck": true 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: cimg/node:18.20.7 6 | working_directory: ~/ra-data-feathers 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: mdph-{{ .Branch }}-{{ checksum "yarn.lock" }} 11 | - run: 12 | name: Install Dependencies 13 | command: yarn install 14 | - save_cache: 15 | key: mdph-{{ .Branch }}-{{ checksum "yarn.lock" }} 16 | paths: 17 | - "/root/.cache/yarn" 18 | - run: 19 | name: Test 20 | command: yarn test 21 | - run: 22 | name: Build 23 | command: yarn build 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": "airbnb-base", 6 | "parser": "babel-eslint", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | globals: { 11 | "after": true, 12 | "afterEach": true, 13 | "before": true, 14 | "beforeEach": true, 15 | "describe": true, 16 | "expect": true, 17 | "it": true, 18 | "localStorage": true 19 | }, 20 | "rules": { 21 | "consistent-return": "warn", 22 | "implicit-arrow-linebreak": "warn", 23 | "func-names": "off", 24 | "no-case-declarations": "off", 25 | "no-fallthrough": "warn", 26 | "no-unused-expressions": "warn", 27 | "prefer-arrow-callback": "off", 28 | "prefer-promise-reject-errors": "warn" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Cambá 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 | -------------------------------------------------------------------------------- /test/types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { restClient, authClient } from "../../lib"; 2 | import { expectType, expectError } from 'tsd'; 3 | 4 | interface IRestClientResult { 5 | (type: any, resource: any, params: any): any 6 | } 7 | 8 | interface IAuthClientResult { 9 | (type: any, params: any): any 10 | } 11 | 12 | const configIdandPatch = { 13 | id: "_id", // In this example, the database uses '_id' rather than 'id' 14 | usePatch: true, // Use PATCH instead of PUT for updates 15 | } 16 | 17 | expectType(restClient(null, configIdandPatch)); 18 | 19 | const configFull = { 20 | id: 'id', // If your database uses an id field other than 'id'. Optional. 21 | usePatch: false, // Use PATCH instead of PUT for UPDATE requests. Optional. 22 | my_resource: { // Options for individual resources can be set by adding an object with the same name. Optional. 23 | id: 'id', // If this specific table uses an id field other than 'id'. Optional. 24 | }, 25 | /* Allows to use custom query operators from various feathers-database-adapters in GET_MANY calls. 26 | * Will be merged with the default query operators ['$gt', '$gte', '$lt', '$lte', '$ne', '$sort', '$or', '$nin', '$in'] 27 | */ 28 | customQueryOperators: [] 29 | }; 30 | 31 | expectType(restClient(null, configFull)); 32 | 33 | // this will be ignored and use default value instead 34 | const configServices = { 35 | randomly: { id: "ok" } 36 | }; 37 | 38 | expectType(restClient(null, configServices)); 39 | 40 | // this will be false 41 | const configInvalid = { 42 | id: 1 43 | }; 44 | 45 | expectError(restClient(null, configInvalid)); 46 | 47 | const allAuthOptions = { 48 | storageKey: 'feathers-jwt', // The key in localStorage used to store the authentication token 49 | authenticate: { // Options included in calls to Feathers client.authenticate 50 | strategy: 'local', // The authentication strategy Feathers should use 51 | }, 52 | permissionsKey: 'permissions', // The key in localStorage used to store permissions from decoded JWT 53 | permissionsField: 'roles', // The key in the decoded JWT containing the user's role 54 | passwordField: 'password', // The key used to provide the password to Feathers client.authenticate 55 | usernameField: 'email', // The key used to provide the username to Feathers client.authenticate 56 | redirectTo: '/login', // Redirect to this path if an AUTH_CHECK fails. Uses the react-admin default of '/login' if omitted. 57 | logoutOnForbidden: true, // Logout when response status code is 403 58 | } 59 | 60 | expectType(authClient(null, allAuthOptions)); 61 | 62 | const someOptions = { 63 | storageKey: 'feathers-jwt', // The key in localStorage used to store the authentication token 64 | authenticate: { // Options included in calls to Feathers client.authenticate 65 | strategy: 'local', // The authentication strategy Feathers should use 66 | }, 67 | } 68 | 69 | expectType(authClient(null, someOptions)); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ra-data-feathers", 3 | "version": "3.0.1", 4 | "description": "A feathers client for react-admin", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "engines": { 8 | "node": ">=18.20.7" 9 | }, 10 | "scripts": { 11 | "pretest": "npm run build", 12 | "test": "npm run coverage && npm run test:type", 13 | "test:type": "tsd", 14 | "build": "cross-env NODE_ENV=production ./node_modules/.bin/babel ./src -d lib --ignore '*.spec.js' && tsc", 15 | "build:types": "tsc", 16 | "clean": "shx rm -rf lib", 17 | "prepublish": "cross-env NODE_ENV=production ./node_modules/.bin/babel ./src -d lib --ignore '*.spec.js' && tsc", 18 | "publish": "git push origin --tags && npm run changelog && git push origin", 19 | "changelog": "github_changelog_generator -u josx -p ra-data-feathers && git add CHANGELOG.md && git commit -am \"Updating changelog\"", 20 | "release:patch": "npm version patch && npm publish", 21 | "release:minor": "npm version minor && npm publish", 22 | "release:major": "npm version major && npm publish", 23 | "coverage": "nyc node_modules/mocha/bin/_mocha -- --require babel-register", 24 | "mocha": "cross-env DEBUG=ra-data-feathers:* mocha --require babel-register" 25 | }, 26 | "files": [ 27 | "LICENSE", 28 | "*.md", 29 | "lib", 30 | "src" 31 | ], 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/josx/ra-data-feathers.git" 35 | }, 36 | "keywords": [ 37 | "reactjs", 38 | "react", 39 | "rest", 40 | "feathers", 41 | "local", 42 | "react-admin" 43 | ], 44 | "author": "Jose Luis Di Biase", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/josx/ra-data-feathers/issues" 48 | }, 49 | "homepage": "https://github.com/josx/ra-data-feathers#readme", 50 | "peerDependencies": { 51 | "@feathersjs/client": "^3.5.3 || ^4.3.0 || ^5.0.0", 52 | "react-admin": "^2.1.1 || ^3.0.0|| ^4.0.0 || ^5.0.0" 53 | }, 54 | "dependencies": { 55 | "debug": "^3.0.0", 56 | "jwt-decode": "^2.2.0", 57 | "object-diff": "^0.0.4" 58 | }, 59 | "devDependencies": { 60 | "all-contributors-cli": "^6.16.1", 61 | "babel-cli": "^6.18.0", 62 | "babel-eslint": "^10.0.1", 63 | "babel-plugin-add-module-exports": "^0.2.1", 64 | "babel-preset-es2015": "^6.18.0", 65 | "babel-preset-stage-0": "^6.16.0", 66 | "babel-register": "^6.26.0", 67 | "chai": "^4.3.4", 68 | "chai-as-promised": "^7.1.1", 69 | "cross-env": "^7.0.3", 70 | "eslint": "^5.7.0", 71 | "eslint-config-airbnb-base": "^13.1.0", 72 | "eslint-plugin-import": "^2.14.0", 73 | "eslint-plugin-node": "^7.0.1", 74 | "mocha": "^8.4.0", 75 | "nyc": "^15.1.0", 76 | "react": "^16.4.1", 77 | "react-admin": "^4.7.2", 78 | "react-dom": "^16.4.1", 79 | "shx": "^0.3.3", 80 | "sinon": "^11.1.1", 81 | "tsd": "^0.17.0", 82 | "typescript": "^4.3.2" 83 | }, 84 | "tsd": { 85 | "directory": "test" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/authClient.js: -------------------------------------------------------------------------------- 1 | import { 2 | AUTH_LOGIN, 3 | AUTH_LOGOUT, 4 | AUTH_CHECK, 5 | AUTH_ERROR, 6 | AUTH_GET_PERMISSIONS, 7 | } from 'react-admin'; 8 | import decodeJwt from 'jwt-decode'; 9 | 10 | /** 11 | * @param {{storageKey?: string, authenticate?: {strategy: string}, permissionsKey?: string, permissionsField?: string, passwordField?: string, usernameField?: string, redirectTo?: string, logoutOnForbidden?: boolean}} options 12 | */ 13 | export default (client, options = {}) => (type, params) => { 14 | const { 15 | storageKey, 16 | authenticate, 17 | permissionsKey, 18 | permissionsField, 19 | passwordField, 20 | usernameField, 21 | redirectTo, 22 | logoutOnForbidden, 23 | } = Object.assign({}, { 24 | storageKey: 'feathers-jwt', 25 | authenticate: { strategy: 'local' }, 26 | permissionsKey: 'permissions', 27 | permissionsField: 'roles', 28 | passwordField: 'password', 29 | usernameField: 'email', 30 | logoutOnForbidden: true, 31 | }, options); 32 | 33 | switch (type) { 34 | case AUTH_LOGIN: 35 | const { username, password } = params; 36 | return client.authenticate({ 37 | ...authenticate, 38 | [usernameField]: username, 39 | [passwordField]: password, 40 | }); 41 | case AUTH_LOGOUT: 42 | return client.logout().then(() => localStorage.removeItem(permissionsKey)); 43 | case AUTH_CHECK: 44 | const hasJwtInStorage = !!localStorage.getItem(storageKey); 45 | const hasReAuthenticate = Object.getOwnPropertyNames(client).includes('reAuthenticate') 46 | && typeof client.reAuthenticate === 'function'; 47 | 48 | if (hasJwtInStorage && hasReAuthenticate) { 49 | return client 50 | .reAuthenticate() 51 | .then(() => Promise.resolve()) 52 | .catch(() => Promise.reject({ redirectTo })); 53 | } 54 | 55 | return hasJwtInStorage ? Promise.resolve() : Promise.reject({ redirectTo }); 56 | case AUTH_ERROR: 57 | const { code } = params; 58 | if (code === 401 || (logoutOnForbidden && code === 403)) { 59 | localStorage.removeItem(storageKey); 60 | localStorage.removeItem(permissionsKey); 61 | return Promise.reject(); 62 | } 63 | return Promise.resolve(); 64 | case AUTH_GET_PERMISSIONS: 65 | /* 66 | JWT token may be provided by oauth, 67 | so that's why the permissions are decoded here and not in AUTH_LOGIN. 68 | */ 69 | // Get the permissions from localstorage if any. 70 | const localStoragePermissions = JSON.parse(localStorage.getItem(permissionsKey)); 71 | // If any, provide them. 72 | if (localStoragePermissions) { 73 | return Promise.resolve(localStoragePermissions); 74 | } 75 | // Or find them from the token, save them and provide them. 76 | try { 77 | const jwtToken = localStorage.getItem(storageKey); 78 | const decodedToken = decodeJwt(jwtToken); 79 | const jwtPermissions = decodedToken[permissionsField] ? decodedToken[permissionsField] : []; 80 | localStorage.setItem(permissionsKey, JSON.stringify(jwtPermissions)); 81 | return Promise.resolve(jwtPermissions); 82 | } catch (e) { 83 | return Promise.reject(); 84 | } 85 | 86 | default: 87 | return Promise.reject(`Unsupported FeathersJS authClient action type ${type}`); 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /test/authClient.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const chai = require('chai'); 3 | const chaiAsPromised = require('chai-as-promised'); 4 | const sinon = require('sinon'); 5 | const debug = require('debug'); 6 | const aorAuthClient = require('../lib').authClient; 7 | 8 | const { expect } = chai; 9 | chai.use(chaiAsPromised); 10 | 11 | 12 | debug('ra-data-feathers:test'); 13 | 14 | const fakeClient = { 15 | // authenticate: ({ email, password }) => { ... } 16 | }; 17 | 18 | let authClient; 19 | 20 | const options = { 21 | storageKey: 'storageKey', 22 | }; 23 | 24 | function setupClient(clientOptions = {}) { 25 | authClient = aorAuthClient(fakeClient, clientOptions); 26 | } 27 | 28 | 29 | describe('Auth Client', function () { 30 | describe('when called with AUTH_CHECK', function () { 31 | describe('when [storageKey] is not set', function () { 32 | beforeEach(function () { 33 | setupClient(options); 34 | global.localStorage = { 35 | getItem: sinon.stub().returns(), 36 | }; 37 | }); 38 | 39 | afterEach(function () { 40 | delete global.localStorage; 41 | }); 42 | 43 | it('should reject', function () { 44 | return expect(authClient('AUTH_CHECK', {})).to.be.rejected; 45 | }); 46 | 47 | describe('when redirectTo is set', function () { 48 | const testOptions = Object.assign({}, options, { redirectTo: '/someurl' }); 49 | beforeEach(function () { 50 | setupClient(testOptions); 51 | }); 52 | 53 | it('should reject with the redirectTo URL', function () { 54 | return expect(authClient('AUTH_CHECK', {})).to.be.rejectedWith({ redirectTo: testOptions.redirectTo }); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('when [storageKey] is set', function () { 60 | before(function () { 61 | global.localStorage = { 62 | getItem: sinon.stub().returns('somedata'), 63 | }; 64 | }); 65 | after(function () { 66 | delete global.localStorage; 67 | }); 68 | 69 | it('should resolve', function () { 70 | return expect(authClient('AUTH_CHECK', {})).to.be.fulfilled; 71 | }); 72 | }); 73 | 74 | describe('when client.reAuthenticate() is available', function () { 75 | before(function () { 76 | global.localStorage = { 77 | getItem: sinon.stub().returns('somedata'), 78 | }; 79 | }); 80 | after(function () { 81 | delete global.localStorage; 82 | }); 83 | 84 | it('should call client.reAuthenticate if is a function', function () { 85 | const customClient = { 86 | reAuthenticate: sinon.stub().returns(new Promise(() => {})), 87 | }; 88 | 89 | const customAuthClient = aorAuthClient(customClient, options); 90 | 91 | customAuthClient('AUTH_CHECK', {}); 92 | return expect(customClient.reAuthenticate.called); 93 | }); 94 | }); 95 | }); 96 | 97 | describe('when called with an invalid type', function () { 98 | beforeEach(function () { 99 | setupClient(); 100 | }); 101 | 102 | it('should throw an error', function () { 103 | const errorRes = new Error('Unsupported FeathersJS authClient action type WRONG_TYPE'); 104 | try { 105 | return authClient('WRONG_TYPE', 'posts', {}) 106 | .then(() => { 107 | throw new Error('client must reject'); 108 | }) 109 | .catch(() => {}); 110 | } catch (err) { 111 | expect(err).to.deep.equal(errorRes); 112 | } 113 | }); 114 | }); 115 | 116 | describe('when logoutOnForbidden is set', () => { 117 | beforeEach(function () { 118 | setupClient(); 119 | }); 120 | 121 | before(function () { 122 | global.localStorage = { 123 | removeItem: sinon.stub(), 124 | }; 125 | }); 126 | 127 | after(function () { 128 | delete global.localStorage; 129 | }); 130 | 131 | it('should throw when true', function () { 132 | return expect(authClient('AUTH_ERROR', { code: 403 })).to 133 | .be.rejected; 134 | }); 135 | 136 | it('should not throw when false', function () { 137 | const testOptions = Object.assign({}, options, { logoutOnForbidden: false }); 138 | setupClient(testOptions); 139 | return expect(authClient('AUTH_ERROR', { code: 403 })).to 140 | .be.fulfilled; 141 | }); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /src/restClient.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_MANY, 3 | GET_MANY_REFERENCE, 4 | GET_LIST, 5 | GET_ONE, 6 | CREATE, 7 | UPDATE, 8 | UPDATE_MANY, 9 | DELETE, 10 | DELETE_MANY, 11 | } from 'react-admin'; 12 | import debug from 'debug'; 13 | import diff from 'object-diff'; 14 | 15 | const dbg = debug('ra-data-feathers:rest-client'); 16 | 17 | const defaultIdKey = 'id'; 18 | 19 | const queryOperators = ['$gt', '$gte', '$lt', '$lte', '$ne', '$sort', '$or', '$nin', '$in']; 20 | 21 | function flatten(object, prefix = '', stopKeys = []) { 22 | return Object.keys(object).reduce( 23 | (prev, element) => { 24 | const hasNextLevel = object[element] 25 | && typeof object[element] === 'object' 26 | && !Array.isArray(object[element]) 27 | && !Object.keys(object[element]).some(key => stopKeys.includes(key)); 28 | return hasNextLevel 29 | ? { ...prev, ...flatten(object[element], `${prefix}${element}.`, stopKeys) } 30 | : { ...prev, ...{ [`${prefix}${element}`]: object[element] } }; 31 | }, 32 | {}, 33 | ); 34 | } 35 | 36 | function getIdKey({ resource, options }) { 37 | return (options[resource] && options[resource].id) || options.id || defaultIdKey; 38 | } 39 | 40 | function deleteProp(obj, prop) { 41 | const res = Object.assign({}, obj); 42 | delete res[prop]; 43 | return res; 44 | } 45 | 46 | /** 47 | * @param {{usePatch?: boolean, customQueryOperators?: string[], useMulti?: boolean, id?: string} | {[Key: string]: {id: string}}} options 48 | */ 49 | export default (client, options = {}) => { 50 | const usePatch = !!options.usePatch; 51 | const mapRequest = (type, resource, params) => { 52 | const idKey = getIdKey({ resource, options }); 53 | dbg('type=%o, resource=%o, params=%o, idKey=%o', type, resource, params, idKey); 54 | const service = client.service(resource); 55 | const query = {}; 56 | 57 | switch (type) { 58 | case GET_MANY: 59 | const ids = params.ids || []; 60 | query[idKey] = { $in: ids }; 61 | query.$limit = ids.length; 62 | return service.find({ query }); 63 | case GET_MANY_REFERENCE: 64 | if (params.target && params.id) { 65 | query[params.target] = params.id; 66 | } 67 | case GET_LIST: 68 | const { page, perPage } = params.pagination || {}; 69 | const { field, order } = params.sort || {}; 70 | const additionalQueryOperators = Array.isArray(options.customQueryOperators) ? options.customQueryOperators : []; 71 | const allUniqueQueryOperators = [...new Set(queryOperators.concat(additionalQueryOperators))] 72 | dbg('field=%o, order=%o', field, order); 73 | if (perPage && page) { 74 | query.$limit = perPage; 75 | query.$skip = perPage * (page - 1); 76 | } 77 | if (order) { 78 | query.$sort = { 79 | [field === defaultIdKey ? idKey : field]: order === 'DESC' ? -1 : 1, 80 | }; 81 | } 82 | Object.assign(query, (params.filter) ? flatten(params.filter, '', allUniqueQueryOperators) : {}); 83 | dbg('query=%o', query); 84 | return service.find({ query }); 85 | case GET_ONE: 86 | const restParams = deleteProp(params, defaultIdKey); 87 | return service.get(params.id, restParams); 88 | case UPDATE: 89 | if (usePatch) { 90 | const data = params.previousData ? diff(params.previousData, params.data) : params.data; 91 | return service.patch(params.id, data); 92 | } 93 | const data = (idKey !== defaultIdKey) 94 | ? deleteProp(params.data, defaultIdKey) : params.data; 95 | return service.update(params.id, data); 96 | 97 | case UPDATE_MANY: 98 | if (usePatch) { 99 | const dataPatch = params.previousData 100 | ? diff(params.previousData, params.data) : params.data; 101 | return Promise.all(params.ids.map(id => (service.patch(id, dataPatch)))); 102 | } 103 | const dataUpdate = (idKey !== defaultIdKey) 104 | ? deleteProp(params.data, defaultIdKey) : params.data; 105 | return Promise.all(params.ids.map(id => (service.update(id, dataUpdate)))); 106 | 107 | case CREATE: 108 | return service.create(params.data); 109 | case DELETE: 110 | return service.remove(params.id); 111 | case DELETE_MANY: 112 | if (!!options.useMulti && service.options.multi) { 113 | return service.remove(null, { 114 | query: { 115 | [idKey]: { 116 | $in: params.ids, 117 | }, 118 | }, 119 | }); 120 | } 121 | return Promise.all(params.ids.map(id => (service.remove(id)))); 122 | default: 123 | return Promise.reject(`Unsupported FeathersJS restClient action type ${type}`); 124 | } 125 | }; 126 | 127 | const mapResponse = (response, type, resource, params) => { 128 | const idKey = getIdKey({ resource, options }); 129 | switch (type) { 130 | case GET_ONE: 131 | case UPDATE: 132 | case DELETE: 133 | return { data: { ...response, id: response[idKey] } }; 134 | case UPDATE_MANY: 135 | case DELETE_MANY: 136 | return { data: response.map(record => record[idKey]) }; 137 | case CREATE: 138 | return { data: { ...params.data, ...response, id: response[idKey] } }; 139 | case GET_MANY_REFERENCE: // fix GET_MANY_REFERENCE missing id 140 | case GET_MANY: // fix GET_MANY missing id 141 | case GET_LIST: 142 | let res; 143 | // support paginated and non paginated services 144 | if (!response.data) { 145 | response.total = response.length; 146 | res = response; 147 | } else { 148 | res = response.data; 149 | } 150 | response.data = res.map((_item) => { 151 | const item = _item; 152 | if (idKey !== defaultIdKey) { 153 | item.id = _item[idKey]; 154 | } 155 | return _item; 156 | }); 157 | return response; 158 | default: 159 | return response; 160 | } 161 | }; 162 | 163 | return (type, resource, params) => 164 | mapRequest(type, resource, params) 165 | .then(response => mapResponse(response, type, resource, params)); 166 | }; 167 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "ra-data-feathers", 3 | "projectOwner": "josx", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 40, 10 | "commit": true, 11 | "commitConvention": "none", 12 | "contributors": [ 13 | { 14 | "login": "josx", 15 | "name": "José Luis Di Biase", 16 | "avatar_url": "https://avatars1.githubusercontent.com/u/791137?v=4", 17 | "profile": "http://www.camba.coop/", 18 | "contributions": [ 19 | "code", 20 | "doc" 21 | ] 22 | }, 23 | { 24 | "login": "nicholasnelson", 25 | "name": "Nicholas Nelson", 26 | "avatar_url": "https://avatars1.githubusercontent.com/u/7485405?v=4", 27 | "profile": "http://nicknelson.io/", 28 | "contributions": [ 29 | "code", 30 | "doc" 31 | ] 32 | }, 33 | { 34 | "login": "fonzarely", 35 | "name": "F.C", 36 | "avatar_url": "https://avatars3.githubusercontent.com/u/707217?v=4", 37 | "profile": "http://www.champigny.name/", 38 | "contributions": [ 39 | "code", 40 | "doc" 41 | ] 42 | }, 43 | { 44 | "login": "AmrN", 45 | "name": "Amr Noman", 46 | "avatar_url": "https://avatars3.githubusercontent.com/u/11032286?v=4", 47 | "profile": "https://github.com/AmrN", 48 | "contributions": [ 49 | "code", 50 | "doc" 51 | ] 52 | }, 53 | { 54 | "login": "lijoantony", 55 | "name": "Lijo Antony", 56 | "avatar_url": "https://avatars2.githubusercontent.com/u/1202940?v=4", 57 | "profile": "https://github.com/lijoantony", 58 | "contributions": [ 59 | "code", 60 | "doc" 61 | ] 62 | }, 63 | { 64 | "login": "tony-kerz", 65 | "name": "tony kerz", 66 | "avatar_url": "https://avatars0.githubusercontent.com/u/1223231?v=4", 67 | "profile": "https://github.com/tony-kerz", 68 | "contributions": [ 69 | "code", 70 | "doc" 71 | ] 72 | }, 73 | { 74 | "login": "vonagam", 75 | "name": "Dmitrii Maganov", 76 | "avatar_url": "https://avatars1.githubusercontent.com/u/5790814?v=4", 77 | "profile": "https://github.com/vonagam", 78 | "contributions": [ 79 | "code", 80 | "doc" 81 | ] 82 | }, 83 | { 84 | "login": "dreamrace", 85 | "name": "Dream", 86 | "avatar_url": "https://avatars3.githubusercontent.com/u/3618360?v=4", 87 | "profile": "https://github.com/dreamrace", 88 | "contributions": [ 89 | "code", 90 | "doc" 91 | ] 92 | }, 93 | { 94 | "login": "wedneyyuri", 95 | "name": "Wédney Yuri", 96 | "avatar_url": "https://avatars3.githubusercontent.com/u/7511692?v=4", 97 | "profile": "https://github.com/wedneyyuri", 98 | "contributions": [ 99 | "code", 100 | "doc" 101 | ] 102 | }, 103 | { 104 | "login": "7flash", 105 | "name": "Igor Berlenko", 106 | "avatar_url": "https://avatars2.githubusercontent.com/u/4569866?v=4", 107 | "profile": "https://github.com/7flash", 108 | "contributions": [ 109 | "code", 110 | "doc" 111 | ] 112 | }, 113 | { 114 | "login": "tb", 115 | "name": "Tomasz Bak", 116 | "avatar_url": "https://avatars2.githubusercontent.com/u/71683?v=4", 117 | "profile": "https://www.softkraft.co/", 118 | "contributions": [ 119 | "code", 120 | "doc" 121 | ] 122 | }, 123 | { 124 | "login": "DanStorm", 125 | "name": "Dan Stevens", 126 | "avatar_url": "https://avatars3.githubusercontent.com/u/5097089?v=4", 127 | "profile": "https://github.com/DanStorm", 128 | "contributions": [ 129 | "code", 130 | "doc" 131 | ] 132 | }, 133 | { 134 | "login": "dprentis", 135 | "name": "Daniel Prentis", 136 | "avatar_url": "https://avatars2.githubusercontent.com/u/1877008?v=4", 137 | "profile": "https://github.com/dprentis", 138 | "contributions": [ 139 | "code", 140 | "doc" 141 | ] 142 | }, 143 | { 144 | "login": "FacundoMainere", 145 | "name": "Facundo Mainere", 146 | "avatar_url": "https://avatars2.githubusercontent.com/u/1382608?v=4", 147 | "profile": "https://camba.coop/", 148 | "contributions": [ 149 | "code", 150 | "doc" 151 | ] 152 | }, 153 | { 154 | "login": "kfern", 155 | "name": "Fernando Navarro", 156 | "avatar_url": "https://avatars2.githubusercontent.com/u/1898891?v=4", 157 | "profile": "https://github.com/kfern", 158 | "contributions": [ 159 | "code", 160 | "doc" 161 | ] 162 | }, 163 | { 164 | "login": "loming", 165 | "name": "LoMing", 166 | "avatar_url": "https://avatars3.githubusercontent.com/u/4988275?v=4", 167 | "profile": "https://github.com/loming", 168 | "contributions": [ 169 | "code", 170 | "doc" 171 | ] 172 | }, 173 | { 174 | "login": "TheHyphen", 175 | "name": "Mohammed Faizuddin", 176 | "avatar_url": "https://avatars1.githubusercontent.com/u/10869488?v=4", 177 | "profile": "https://faizudd.in/", 178 | "contributions": [ 179 | "code", 180 | "doc" 181 | ] 182 | }, 183 | { 184 | "login": "Ryanthegiantlion", 185 | "name": "Ryan Harmuth", 186 | "avatar_url": "https://avatars2.githubusercontent.com/u/1843898?v=4", 187 | "profile": "https://github.com/Ryanthegiantlion", 188 | "contributions": [ 189 | "code", 190 | "doc" 191 | ] 192 | }, 193 | { 194 | "login": "sgobotta", 195 | "name": "Santiago Botta", 196 | "avatar_url": "https://avatars2.githubusercontent.com/u/17838461?v=4", 197 | "profile": "https://github.com/sgobotta", 198 | "contributions": [ 199 | "code", 200 | "doc" 201 | ] 202 | }, 203 | { 204 | "login": "taylorgoodallau", 205 | "name": "Taylor Goodall", 206 | "avatar_url": "https://avatars2.githubusercontent.com/u/47597450?v=4", 207 | "profile": "https://github.com/taylorgoodallau", 208 | "contributions": [ 209 | "code", 210 | "doc" 211 | ] 212 | }, 213 | { 214 | "login": "alex-all3dp", 215 | "name": "Alexander Friedl", 216 | "avatar_url": "https://avatars2.githubusercontent.com/u/52403795?v=4", 217 | "profile": "https://craftcloud3d.com/", 218 | "contributions": [ 219 | "code", 220 | "doc" 221 | ] 222 | }, 223 | { 224 | "login": "fabioptoi", 225 | "name": "Fábio Toi", 226 | "avatar_url": "https://avatars3.githubusercontent.com/u/11648668?v=4", 227 | "profile": "https://github.com/fabioptoi", 228 | "contributions": [ 229 | "code", 230 | "doc" 231 | ] 232 | }, 233 | { 234 | "login": "jvke", 235 | "name": "jvke", 236 | "avatar_url": "https://avatars2.githubusercontent.com/u/3538455?v=4", 237 | "profile": "http://jvke.co/", 238 | "contributions": [ 239 | "code", 240 | "doc" 241 | ] 242 | }, 243 | { 244 | "login": "nhkhanh", 245 | "name": "nhkhanh", 246 | "avatar_url": "https://avatars0.githubusercontent.com/u/4928520?v=4", 247 | "profile": "https://github.com/nhkhanh", 248 | "contributions": [ 249 | "code", 250 | "doc" 251 | ] 252 | }, 253 | { 254 | "login": "ucokfm", 255 | "name": "Ucok Freddy", 256 | "avatar_url": "https://avatars.githubusercontent.com/u/1414833?v=4", 257 | "profile": "https://github.com/ucokfm", 258 | "contributions": [ 259 | "code" 260 | ] 261 | }, 262 | { 263 | "login": "olosegres", 264 | "name": "Sergei Solo", 265 | "avatar_url": "https://avatars.githubusercontent.com/u/3730589?v=4", 266 | "profile": "https://github.com/olosegres/jsona", 267 | "contributions": [ 268 | "code" 269 | ] 270 | }, 271 | { 272 | "login": "berviantoleo", 273 | "name": "Bervianto Leo Pratama", 274 | "avatar_url": "https://avatars.githubusercontent.com/u/15927349?v=4", 275 | "profile": "https://berviantoleo.my.id/", 276 | "contributions": [ 277 | "code" 278 | ] 279 | }, 280 | { 281 | "login": "JPStrydom", 282 | "name": "JP Strydom", 283 | "avatar_url": "https://avatars.githubusercontent.com/u/25905330?v=4", 284 | "profile": "https://github.com/JPStrydom", 285 | "contributions": [ 286 | "code" 287 | ] 288 | } 289 | ], 290 | "contributorsPerLine": 7 291 | } 292 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ra-data-feathers 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-28-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | > Feathers data provider for [react-admin](https://github.com/marmelab/react-admin) 7 | 8 | The perfect match to build Backend and Frontend Admin, based on REST services. 9 | For using [Feathers](https://www.feathersjs.com) with [react-admin](https://github.com/marmelab/react-admin). 10 | 11 | If you are searching for admin-on-rest (older react-admin version), please use [1.0.0 version](https://github.com/josx/ra-data-feathers/releases/tag/v1.0.0) 12 | 13 | ## Supported react-admin request types 14 | 15 | ra-data-feathers currently supports the following request types. More information on react-admin request types is available for [data providers](https://marmelab.com/react-admin/DataProviders.html#request-format) and [auth providers](https://marmelab.com/react-admin/Authorization.html) in the react-admin documentation. 16 | 17 | * **Data Provider** 18 | * GET_LIST 19 | * GET_ONE 20 | * CREATE 21 | * UPDATE 22 | * UPDATE_MANY 23 | * DELETE 24 | * DELETE_MANY 25 | * GET_MANY 26 | * GET_MANY_REFERENCE 27 | * **Auth Provider** 28 | * AUTH_LOGIN 29 | * AUTH_LOGOUT 30 | * AUTH_CHECK 31 | * AUTH_ERROR 32 | * AUTH_GET_PERMISSIONS 33 | 34 | ## Installation 35 | 36 | In your react-admin app just add ra-data-feathers dependency: 37 | 38 | ```sh 39 | npm install ra-data-feathers --save 40 | ``` 41 | 42 | ## Usage 43 | 44 | ### Feathers Client 45 | Both `restClient` and `authClient` depend on a configured Feathers client instance. 46 | 47 | Configuration of the Feathers client is beyond the scope of this document. See the Feathers documentation for more information on configuring the Feathers client. Both of the following need to be configured in the Feathers client for use with ra-data-feathers. 48 | * A client type such as [REST](https://docs.feathersjs.com/api/client/rest.html), [Socket.io](https://docs.feathersjs.com/api/client/socketio.html), or [Primus](https://docs.feathersjs.com/api/client/primus.html) 49 | * [Authentication](https://docs.feathersjs.com/api/authentication/client.html) 50 | 51 | 52 | ### Data Provider (restClient) 53 | The ra-data-feathers data provider (restClient) accepts two arguments: `client` and `options`. 54 | 55 | `client`should be a [configured Feathers client instance](#Feathers-Client). This argument is required. 56 | 57 | `options` contains configurable options for the ra-data-feathers restClient. The `options` argument is optional and can be omitted. In this case, defaults will be used. 58 | ```js 59 | const options = { 60 | id: 'id', // If your database uses an id field other than 'id'. Optional. 61 | usePatch: false, // Use PATCH instead of PUT for UPDATE requests. Optional. 62 | my_resource: { // Options for individual resources can be set by adding an object with the same name. Optional. 63 | id: 'id', // If this specific table uses an id field other than 'id'. Optional. 64 | }, 65 | /* Allows to use custom query operators from various feathers-database-adapters in GET_MANY calls. 66 | * Will be merged with the default query operators ['$gt', '$gte', '$lt', '$lte', '$ne', '$sort', '$or', '$nin', '$in'] 67 | */ 68 | customQueryOperators: [] 69 | } 70 | ``` 71 | `Performant Bulk Actions` can be used by enabling multi options in the feathers application 72 | 73 | ### Auth Provider (authClient) 74 | `authClient` also accepts two parameters. `client` and `options`. 75 | 76 | `client`should be a [configured Feathers client instance](#Feathers-Client). This argument is required. 77 | 78 | `options` contains configurable options for the ra-data-feathers authClient. The `options` argument is optional and can be omitted. In this case, defaults shown below will be used. 79 | 80 | ```js 81 | const options = { 82 | storageKey: 'feathers-jwt', // The key in localStorage used to store the authentication token 83 | authenticate: { // Options included in calls to Feathers client.authenticate 84 | strategy: 'local', // The authentication strategy Feathers should use 85 | }, 86 | permissionsKey: 'permissions', // The key in localStorage used to store permissions from decoded JWT 87 | permissionsField: 'roles', // The key in the decoded JWT containing the user's role 88 | passwordField: 'password', // The key used to provide the password to Feathers client.authenticate 89 | usernameField: 'email', // The key used to provide the username to Feathers client.authenticate 90 | redirectTo: '/login', // Redirect to this path if an AUTH_CHECK fails. Uses the react-admin default of '/login' if omitted. 91 | logoutOnForbidden: true, // Logout when response status code is 403 92 | } 93 | ``` 94 | 95 | ### Usage with the react-admin `` component 96 | 97 | ra-data-feathers can be used by passing the `restClient` and `authClient` to the react-admin `` component as the `dataProvider` and `authProvider` params respectively: 98 | ```jsx 99 | 103 | ``` 104 | 105 | ## Example 106 | 107 | This example assumes the following: 108 | * A [configured Feathers client](#feathers-client) is available at `./feathersClient` 109 | * The Feathers authentication service includes a field called `userroles` 110 | * List components for `AResource` and `AnotherResource` are available in `./resources` 111 | 112 | ```js 113 | import { Admin, Resource } from 'react-admin'; 114 | import feathersClient from './feathersClient'; 115 | import { AResourceList } from './resources/AResource/List'; 116 | import { AnotherResourceList } from './resources/AnotherResourceList'; 117 | import { restClient, authClient } from 'ra-data-feathers'; 118 | 119 | const restClientOptions = { 120 | id: '_id', // In this example, the database uses '_id' rather than 'id' 121 | usePatch: true // Use PATCH instead of PUT for updates 122 | }; 123 | 124 | const authClientOptions = { 125 | usernameField: 'username', // Our example database might use 'username' rather than 'email' 126 | permissionsField: 'userroles', // Use the 'userroles' field on the JWT as the users role 127 | redirectTo: '/signin', // Our example login form might be at '/signin', redirect here if AUTH_CHECK fails 128 | } 129 | 130 | const App = () => ( 131 | 136 | {permissions => [ 137 | 141 | permissions === 'admin' ? // Only show this resource if the user role is 'admin' 142 | : null; 146 | ]} 147 | 148 | ); 149 | 150 | ``` 151 | 152 | > Note: the permissions restriction above **only** affects whether a given resource is visible or not, and will not prevent users from accessing your API directly. In most projects, this option would be used with user/role restriction hooks on the server side such as [feathers-authentication-hooks](https://github.com/feathersjs-ecosystem/feathers-authentication-hooks). 153 | 154 | You can find a complete example in [https://github.com/kfern/feathers-aor-test-integration](https://github.com/kfern/feathers-aor-test-integration) 155 | 156 | ## Running tests 157 | 158 | Tests for the ra-data-feathers module are available from the root directory of the module with: 159 | ```sh 160 | npm run test 161 | ``` 162 | 163 | ## License 164 | 165 | This software is licensed under the [MIT Licence](LICENSE), and sponsored by [Cambá](https://www.camba.coop). 166 | 167 | ## Contributors ✨ 168 | 169 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 |

José Luis Di Biase

💻 📖

Nicholas Nelson

💻 📖

F.C

💻 📖

Amr Noman

💻 📖

Lijo Antony

💻 📖

tony kerz

💻 📖

Dmitrii Maganov

💻 📖

Dream

💻 📖

Wédney Yuri

💻 📖

Igor Berlenko

💻 📖

Tomasz Bak

💻 📖

Dan Stevens

💻 📖

Daniel Prentis

💻 📖

Facundo Mainere

💻 📖

Fernando Navarro

💻 📖

LoMing

💻 📖

Mohammed Faizuddin

💻 📖

Ryan Harmuth

💻 📖

Santiago Botta

💻 📖

Taylor Goodall

💻 📖

Alexander Friedl

💻 📖

Fábio Toi

💻 📖

jvke

💻 📖

nhkhanh

💻 📖

Ucok Freddy

💻

Sergei Solo

💻

Bervianto Leo Pratama

💻

JP Strydom

💻
212 | 213 | 214 | 215 | 216 | 217 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 218 | -------------------------------------------------------------------------------- /test/restClient.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | GET_MANY, 4 | GET_MANY_REFERENCE, 5 | GET_LIST, 6 | GET_ONE, 7 | CREATE, 8 | UPDATE, 9 | UPDATE_MANY, 10 | DELETE, 11 | DELETE_MANY, 12 | } from 'react-admin'; 13 | 14 | const { expect } = require('chai'); 15 | const sinon = require('sinon'); 16 | const debug = require('debug'); 17 | const { restClient } = require('../src'); 18 | 19 | debug('ra-data-feathers:test'); 20 | 21 | const findResult = { 22 | total: 1, 23 | data: [ 24 | { id: 1 }, 25 | ], 26 | }; 27 | const getResult = { id: 1, title: 'gotten' }; 28 | const updateResult = { id: 1, title: 'updated' }; 29 | const createResult = { id: 1, title: 'created' }; 30 | const removeResult = { id: 1, title: 'deleted' }; 31 | const removeManyResult = [{ id: 1, title: 'deleted' }, { id: 2, title: 'deleted' }]; 32 | const authenticateResult = {}; 33 | 34 | let aorClient; 35 | let fakeClient; 36 | let fakeService; 37 | 38 | function setupClient(options = {}) { 39 | const { useMulti, multi } = options || false; 40 | fakeService = { 41 | find: sinon.stub().returns(Promise.resolve(findResult)), 42 | get: sinon.stub().returns(Promise.resolve(getResult)), 43 | update: sinon.stub().callsFake((id, data) => Promise.resolve( 44 | Object.assign({}, data, { id }), 45 | )), 46 | patch: sinon.stub().callsFake((id, data) => Promise.resolve( 47 | Object.assign({}, data, { id }), 48 | )), 49 | create: sinon.stub().returns(Promise.resolve(createResult)), 50 | remove: useMulti && multi 51 | ? sinon.stub().returns(Promise.resolve(removeManyResult)) 52 | : sinon.stub().callsFake(id => Promise.resolve( 53 | Object.assign({}, removeResult, { id }), 54 | )), 55 | options, 56 | }; 57 | 58 | // sinon.spy(fakeService.find) 59 | 60 | fakeClient = { 61 | service: () => fakeService, 62 | authenticate: () => Promise.resolve(authenticateResult), 63 | }; 64 | 65 | aorClient = restClient(fakeClient, options); 66 | } 67 | 68 | 69 | describe('Rest Client', function () { 70 | let asyncResult; 71 | describe('when called with GET_MANY', function () { 72 | const ids = [1, 2, 3]; 73 | beforeEach(function () { 74 | setupClient(); 75 | asyncResult = aorClient(GET_MANY, 'posts', { ids }); 76 | }); 77 | 78 | it("calls the client's find method", function () { 79 | return asyncResult.then(() => { 80 | expect(fakeService.find.calledOnce).to.be.true; 81 | }); 82 | }); 83 | 84 | it('returns the data returned by the client', function () { 85 | return asyncResult.then((result) => { 86 | expect(result).to.deep.equal(findResult); 87 | }); 88 | }); 89 | 90 | it("converts ids in it's params into a query and pass it to client", function () { 91 | const query = { 92 | id: { $in: ids }, 93 | $limit: ids.length, 94 | }; 95 | return asyncResult.then(() => { 96 | expect(fakeService.find.calledWith({ 97 | query, 98 | })).to.be.true; 99 | }); 100 | }); 101 | }); 102 | 103 | describe('when called with GET_MANY_REFERENCE', function () { 104 | const params = { 105 | pagination: { 106 | page: 10, 107 | perPage: 20, 108 | }, 109 | sort: { 110 | field: 'id', 111 | order: 'DESC', 112 | }, 113 | filter: { 114 | name: 'john', 115 | }, 116 | id: '1', 117 | target: '_userId', 118 | }; 119 | beforeEach(function () { 120 | setupClient(); 121 | asyncResult = aorClient(GET_MANY_REFERENCE, 'posts', params); 122 | }); 123 | 124 | it("calls the client's find method", function () { 125 | return asyncResult.then(() => { 126 | expect(fakeService.find.calledOnce).to.be.true; 127 | }); 128 | }); 129 | 130 | it('returns the data returned by the client', function () { 131 | return asyncResult.then((result) => { 132 | expect(result).to.deep.equal(findResult); 133 | }); 134 | }); 135 | 136 | it('formats params into a query and pass it to client', function () { 137 | const query = { 138 | $limit: 20, 139 | $skip: 20 * 9, 140 | $sort: { 141 | id: -1, 142 | }, 143 | name: 'john', 144 | _userId: '1', 145 | }; 146 | return asyncResult.then(() => { 147 | expect(fakeService.find.calledWith({ 148 | query, 149 | })).to.be.true; 150 | }); 151 | }); 152 | }); 153 | 154 | describe('when called with GET_LIST', function () { 155 | const params = { 156 | pagination: { 157 | page: 10, 158 | perPage: 20, 159 | }, 160 | sort: { 161 | field: 'id', 162 | order: 'DESC', 163 | }, 164 | filter: { 165 | name: 'john', 166 | address: { 167 | city: 'London', 168 | }, 169 | age: { 170 | $gt: 20, 171 | }, 172 | rating: { 173 | score: { 174 | $gte: 4, 175 | }, 176 | }, 177 | authors: { 178 | $in: ['auth1', 'auth2'], 179 | }, 180 | }, 181 | }; 182 | beforeEach(function () { 183 | setupClient(); 184 | asyncResult = aorClient(GET_LIST, 'posts', params); 185 | }); 186 | 187 | it("calls the client's find method", function () { 188 | return asyncResult.then(() => { 189 | expect(fakeService.find.calledOnce).to.be.true; 190 | }); 191 | }); 192 | 193 | it('returns the data returned by the client', function () { 194 | return asyncResult.then((result) => { 195 | expect(result).to.deep.equal(findResult); 196 | }); 197 | }); 198 | 199 | it('formats params into a query and pass it to client', function () { 200 | const query = { 201 | $limit: 20, 202 | $skip: 20 * 9, 203 | $sort: { 204 | id: -1, 205 | }, 206 | name: 'john', 207 | 'address.city': 'London', 208 | age: { 209 | $gt: 20, 210 | }, 211 | 'rating.score': { 212 | $gte: 4, 213 | }, 214 | authors: { 215 | $in: ['auth1', 'auth2'], 216 | }, 217 | }; 218 | return asyncResult.then(() => { 219 | expect(fakeService.find.calledWith({ 220 | query, 221 | })).to.be.true; 222 | }); 223 | }); 224 | }); 225 | 226 | describe('no:params when called with GET_LIST', function () { 227 | const params = {}; 228 | 229 | beforeEach(function () { 230 | setupClient(); 231 | asyncResult = aorClient(GET_LIST, 'posts', params); 232 | }); 233 | 234 | it("no:params calls the client's find method", function () { 235 | return asyncResult.then(() => { 236 | expect(fakeService.find.calledOnce).to.be.true; 237 | }); 238 | }); 239 | 240 | it('no:params returns the data returned by the client', function () { 241 | return asyncResult.then((result) => { 242 | expect(result).to.deep.equal(findResult); 243 | }); 244 | }); 245 | }); 246 | 247 | describe('id-option: when called with GET_LIST', function () { 248 | const params = { 249 | pagination: { 250 | page: 10, 251 | perPage: 20, 252 | }, 253 | sort: { 254 | field: 'id', 255 | order: 'DESC', 256 | }, 257 | filter: { 258 | name: 'john', 259 | }, 260 | }; 261 | 262 | it("id-option: calls the client's find method", function () { 263 | setupClient({ id: '_id' }); 264 | asyncResult = aorClient(GET_LIST, 'posts', params); 265 | return asyncResult.then(() => { 266 | expect(fakeService.find.calledOnce).to.be.true; 267 | }); 268 | }); 269 | 270 | it('id-option: returns the data returned by the client', function () { 271 | return asyncResult.then((result) => { 272 | expect(result).to.deep.equal(findResult); 273 | }); 274 | }); 275 | 276 | it('id-option: formats params into a query and pass it to client', function () { 277 | const query = { 278 | $limit: 20, 279 | $skip: 20 * 9, 280 | $sort: { 281 | _id: -1, 282 | }, 283 | name: 'john', 284 | }; 285 | return asyncResult.then(() => { 286 | expect(fakeService.find.calledWith({ 287 | query, 288 | })).to.be.true; 289 | }); 290 | }); 291 | }); 292 | 293 | describe('customQueryOperators-option: when called with GET_LIST', function () { 294 | const params = { 295 | pagination: { 296 | page: 10, 297 | perPage: 20, 298 | }, 299 | sort: { 300 | field: 'id', 301 | order: 'DESC', 302 | }, 303 | filter: { 304 | name: { $regexp: 'john' }, 305 | }, 306 | }; 307 | 308 | it("customQueryOperators-option: calls the client's find method", function () { 309 | setupClient({ customQueryOperators: ['$regexp'] }); 310 | asyncResult = aorClient(GET_LIST, 'posts', params); 311 | return asyncResult.then(() => { 312 | expect(fakeService.find.calledOnce).to.be.true; 313 | }); 314 | }); 315 | 316 | it('customQueryOperators-option: returns the data returned by the client', function () { 317 | return asyncResult.then((result) => { 318 | expect(result).to.deep.equal(findResult); 319 | }); 320 | }); 321 | 322 | it('customQueryOperators-option: correctly formats custom query operators in the query passed to the client', function () { 323 | const query = { 324 | $limit: 20, 325 | $skip: 180, 326 | $sort: { id: -1 }, 327 | name: { $regexp: 'john' }, 328 | }; 329 | return asyncResult.then(() => { 330 | expect(fakeService.find.calledWith({ query })).to.be.true; 331 | }); 332 | }); 333 | }); 334 | 335 | describe('resource-id-option: when called with GET_LIST', function () { 336 | const params = { 337 | pagination: { 338 | page: 10, 339 | perPage: 20, 340 | }, 341 | sort: { 342 | field: 'id', 343 | order: 'DESC', 344 | }, 345 | filter: { 346 | name: 'john', 347 | }, 348 | }; 349 | 350 | it("resource-id-option: calls the client's find method", function () { 351 | setupClient({ posts: { id: '_id' } }); 352 | asyncResult = aorClient(GET_LIST, 'posts', params); 353 | return asyncResult.then(() => { 354 | expect(fakeService.find.calledOnce).to.be.true; 355 | }); 356 | }); 357 | 358 | it('resource-id-option: returns the data returned by the client', function () { 359 | return asyncResult.then((result) => { 360 | expect(result).to.deep.equal(findResult); 361 | }); 362 | }); 363 | 364 | it('resource-id-option: formats params into a query and pass it to client', function () { 365 | const query = { 366 | $limit: 20, 367 | $skip: 20 * 9, 368 | $sort: { 369 | _id: -1, 370 | }, 371 | name: 'john', 372 | }; 373 | return asyncResult.then(() => { 374 | expect(fakeService.find.calledWith({ 375 | query, 376 | })).to.be.true; 377 | }); 378 | }); 379 | 380 | it("resource-id-option: calls the client's find method for default handled resource", function () { 381 | setupClient({ widgets: { id: '_id' } }); 382 | asyncResult = aorClient(GET_LIST, 'posts', params); 383 | return asyncResult.then(() => { 384 | expect(fakeService.find.calledOnce).to.be.true; 385 | }); 386 | }); 387 | 388 | it('resource-id-option: returns the data returned by the client for default handled resource', function () { 389 | return asyncResult.then((result) => { 390 | expect(result).to.deep.equal(findResult); 391 | }); 392 | }); 393 | 394 | it('resource-id-option: formats params into a query and pass it to client for default handled resource', function () { 395 | const query = { 396 | $limit: 20, 397 | $skip: 20 * 9, 398 | $sort: { 399 | id: -1, 400 | }, 401 | name: 'john', 402 | }; 403 | return asyncResult.then(() => { 404 | expect(fakeService.find.calledWith({ 405 | query, 406 | })).to.be.true; 407 | }); 408 | }); 409 | }); 410 | 411 | describe('when called with GET_ONE', function () { 412 | const params = { id: 1 }; 413 | beforeEach(function () { 414 | setupClient(); 415 | asyncResult = aorClient(GET_ONE, 'posts', params); 416 | }); 417 | 418 | it("calls the client's get method with the id in params", function () { 419 | return asyncResult.then(() => { 420 | expect(fakeService.get.calledOnce).to.be.true; 421 | expect(fakeService.get.calledWith(1)); 422 | }); 423 | }); 424 | 425 | it('returns the data returned by the client in a "data" object', function () { 426 | return asyncResult.then((result) => { 427 | expect(result).to.deep.equal({ data: getResult }); 428 | }); 429 | }); 430 | }); 431 | 432 | describe('when called with UPDATE', function () { 433 | const params = { 434 | id: 1, 435 | data: { 436 | title: 'updated', 437 | }, 438 | }; 439 | beforeEach(function () { 440 | setupClient(); 441 | asyncResult = aorClient(UPDATE, 'posts', params); 442 | }); 443 | 444 | it("calls the client's update method with the id and data in params", function () { 445 | return asyncResult.then(() => { 446 | expect(fakeService.update.calledOnce).to.be.true; 447 | expect(fakeService.update.calledWith(1, { title: 'updated' })); 448 | }); 449 | }); 450 | 451 | it('returns the data returned by the client in a "data" object', function () { 452 | return asyncResult.then((result) => { 453 | expect(result).to.deep.equal({ data: updateResult }); 454 | }); 455 | }); 456 | }); 457 | 458 | describe('when called with UPDATE_MANY', function () { 459 | const params = { 460 | ids: [1, 2], 461 | data: { 462 | title: 'updated', 463 | }, 464 | }; 465 | 466 | describe('when options.usePatch is false', function () { 467 | const options = { usePatch: false }; 468 | 469 | beforeEach(function () { 470 | setupClient(options); 471 | asyncResult = aorClient(UPDATE_MANY, 'posts', params); 472 | }); 473 | 474 | it('calls the client\'s update method once for each ID', function () { 475 | expect(fakeService.update.calledTwice).to.be.true; 476 | expect(fakeService.update.firstCall.calledWith(1, params.data)); 477 | expect(fakeService.update.secondCall.calledWith(2, params.data)); 478 | }); 479 | 480 | it('returns the ids of the records returned by the client', function () { 481 | return asyncResult.then((result) => { 482 | expect(result).to.deep.equal({ data: params.ids }); 483 | }); 484 | }); 485 | }); 486 | 487 | describe('when options.usePatch is true', function () { 488 | const options = { usePatch: true }; 489 | 490 | beforeEach(function () { 491 | setupClient(options); 492 | asyncResult = aorClient(UPDATE_MANY, 'posts', params); 493 | }); 494 | 495 | it('calls the client\'s patch method once for each ID', function () { 496 | expect(fakeService.patch.calledTwice).to.be.true; 497 | expect(fakeService.patch.firstCall.calledWith(1, params.data)); 498 | expect(fakeService.patch.secondCall.calledWith(2, params.data)); 499 | }); 500 | 501 | it('returns the ids of the records returned by the client', function () { 502 | return asyncResult.then((result) => { 503 | expect(result).to.deep.equal({ data: params.ids }); 504 | }); 505 | }); 506 | }); 507 | }); 508 | 509 | describe('when called with CREATE', function () { 510 | const params = { 511 | data: { 512 | title: 'created', 513 | }, 514 | }; 515 | beforeEach(function () { 516 | setupClient(); 517 | asyncResult = aorClient(CREATE, 'posts', params); 518 | }); 519 | 520 | it("calls the client's create method with the data in params", function () { 521 | return asyncResult.then(() => { 522 | expect(fakeService.create.calledOnce).to.be.true; 523 | expect(fakeService.create.calledWith({ title: 'created' })); 524 | }); 525 | }); 526 | 527 | it('returns the data returned by the client in a "data" object', function () { 528 | return asyncResult.then((result) => { 529 | expect(result).to.deep.equal({ data: createResult }); 530 | }); 531 | }); 532 | }); 533 | 534 | describe('when called with DELETE', function () { 535 | const params = { id: 1 }; 536 | beforeEach(function () { 537 | setupClient(); 538 | asyncResult = aorClient(DELETE, 'posts', params); 539 | }); 540 | 541 | it("calls the client's remove method with the id in params", function () { 542 | return asyncResult.then(() => { 543 | expect(fakeService.remove.calledOnce).to.be.true; 544 | expect(fakeService.remove.calledWith(1)); 545 | }); 546 | }); 547 | 548 | it('returns the data returned by the client', function () { 549 | return asyncResult.then((result) => { 550 | expect(result).to.deep.equal({ data: removeResult }); 551 | }); 552 | }); 553 | }); 554 | 555 | describe('when called with DELETE_MANY, global useMulti is true, service multi is true', function () { 556 | const params = { ids: [1, 2] }; 557 | beforeEach(function () { 558 | setupClient({ useMulti: true, multi: true }); 559 | asyncResult = aorClient(DELETE_MANY, 'posts', params); 560 | }); 561 | 562 | it("calls the client's remove method once with the ids in params", function () { 563 | return asyncResult.then(() => { 564 | expect(fakeService.remove.calledOnce).to.be.true; 565 | expect(fakeService.remove.firstCall.calledWith({ 566 | query: { 567 | id: { 568 | $in: [1, 2], 569 | }, 570 | }, 571 | })); 572 | }); 573 | }); 574 | 575 | it('returns the ids of the records returned by the client', function () { 576 | return asyncResult.then((result) => { 577 | expect(result).to.deep.equal({ data: removeManyResult.map(record => record.id) }); 578 | }); 579 | }); 580 | }); 581 | 582 | describe('when called with DELETE_MANY, global useMulti is true, service multi is false', function () { 583 | const params = { ids: [1, 2] }; 584 | beforeEach(function () { 585 | setupClient({ useMulti: true, multi: false }); 586 | asyncResult = aorClient(DELETE_MANY, 'posts', params); 587 | }); 588 | 589 | it("calls the client's remove method once with the ids in params, and twice with individual id", function () { 590 | return asyncResult.then(() => { 591 | expect(fakeService.remove.calledTwice).to.be.true; 592 | expect(fakeService.remove.firstCall.calledWith(1)); 593 | expect(fakeService.remove.secondCall.calledWith(2)); 594 | }); 595 | }); 596 | 597 | it('returns the ids of the records returned by the client', function () { 598 | return asyncResult.then((result) => { 599 | expect(result).to.deep.equal({ data: removeManyResult.map(record => record.id) }); 600 | }); 601 | }); 602 | }); 603 | 604 | describe('when called with DELETE_MANY, global useMulti is false, service multi is false', function () { 605 | const params = { ids: [1, 2] }; 606 | beforeEach(function () { 607 | setupClient({ useMulti: false, multi: false }); 608 | asyncResult = aorClient(DELETE_MANY, 'posts', params); 609 | }); 610 | 611 | it("calls the client's remove method once with the ids in params, and twice with individual id", function () { 612 | return asyncResult.then(() => { 613 | expect(fakeService.remove.calledTwice).to.be.true; 614 | expect(fakeService.remove.firstCall.calledWith(1)); 615 | expect(fakeService.remove.secondCall.calledWith(2)); 616 | }); 617 | }); 618 | 619 | it('returns the ids of the records returned by the client', function () { 620 | return asyncResult.then((result) => { 621 | expect(result).to.deep.equal({ data: removeManyResult.map(record => record.id) }); 622 | }); 623 | }); 624 | }); 625 | 626 | describe('when called with an invalid type', function () { 627 | beforeEach(function () { 628 | setupClient(); 629 | }); 630 | 631 | it('should throw an error', function () { 632 | const errorRes = new Error('Unsupported FeathersJS restClient action type WRONG_TYPE'); 633 | try { 634 | return aorClient('WRONG_TYPE', 'posts', {}) 635 | .then(() => { 636 | throw new Error('client must reject'); 637 | }) 638 | .catch(() => {}); 639 | } catch (err) { 640 | expect(err).to.deep.equal(errorRes); 641 | } 642 | }); 643 | }); 644 | }); 645 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased](https://github.com/josx/ra-data-feathers/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v3.0.0...HEAD) 6 | 7 | **Merged pull requests:** 8 | 9 | - Added React Admin v5 as a valid peer dependency [\#200](https://github.com/josx/ra-data-feathers/pull/200) ([josx](https://github.com/josx)) 10 | - Circleci: Move from node 14 to 18 [\#199](https://github.com/josx/ra-data-feathers/pull/199) ([josx](https://github.com/josx)) 11 | 12 | ## [v3.0.0](https://github.com/josx/ra-data-feathers/tree/v3.0.0) (2025-03-26) 13 | 14 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.9.4...v3.0.0) 15 | 16 | **Closed issues:** 17 | 18 | - Circleci not passing [\#193](https://github.com/josx/ra-data-feathers/issues/193) 19 | 20 | ## [v2.9.4](https://github.com/josx/ra-data-feathers/tree/v2.9.4) (2023-05-15) 21 | 22 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.9.3...v2.9.4) 23 | 24 | **Closed issues:** 25 | 26 | - Broken `peerDependencies` \(`react-admin` `v2.x`\) [\#184](https://github.com/josx/ra-data-feathers/issues/184) 27 | - `react-admin` `v4` support [\#182](https://github.com/josx/ra-data-feathers/issues/182) 28 | - How to add custom headers? [\#181](https://github.com/josx/ra-data-feathers/issues/181) 29 | - total count / `X-Total-Count` / partial pagination missing [\#180](https://github.com/josx/ra-data-feathers/issues/180) 30 | - Production build: `Uncaught (in promise) Error: Failed to execute 'fetch' on 'Window': Illegal invocation` [\#177](https://github.com/josx/ra-data-feathers/issues/177) 31 | - `createMany` unsupported [\#174](https://github.com/josx/ra-data-feathers/issues/174) 32 | 33 | **Merged pull requests:** 34 | 35 | - Support legacy reac admin [\#196](https://github.com/josx/ra-data-feathers/pull/196) ([josx](https://github.com/josx)) 36 | - Bump node-fetch and react-admin [\#191](https://github.com/josx/ra-data-feathers/pull/191) ([dependabot[bot]](https://github.com/apps/dependabot)) 37 | - Bump minimist and mkdirp [\#190](https://github.com/josx/ra-data-feathers/pull/190) ([dependabot[bot]](https://github.com/apps/dependabot)) 38 | - Bump ua-parser-js from 0.7.28 to 0.7.33 [\#187](https://github.com/josx/ra-data-feathers/pull/187) ([dependabot[bot]](https://github.com/apps/dependabot)) 39 | - Bump qs from 6.5.2 to 6.5.3 [\#186](https://github.com/josx/ra-data-feathers/pull/186) ([dependabot[bot]](https://github.com/apps/dependabot)) 40 | - Bump decode-uri-component from 0.2.0 to 0.2.2 [\#185](https://github.com/josx/ra-data-feathers/pull/185) ([dependabot[bot]](https://github.com/apps/dependabot)) 41 | - Bump async from 3.2.0 to 3.2.3 [\#183](https://github.com/josx/ra-data-feathers/pull/183) ([dependabot[bot]](https://github.com/apps/dependabot)) 42 | - Bump lodash-es from 4.17.14 to 4.17.21 [\#178](https://github.com/josx/ra-data-feathers/pull/178) ([dependabot[bot]](https://github.com/apps/dependabot)) 43 | - Bump ajv from 6.10.0 to 6.12.6 [\#172](https://github.com/josx/ra-data-feathers/pull/172) ([dependabot[bot]](https://github.com/apps/dependabot)) 44 | - fix: permissionsKey and permissionsField in authClient options types [\#171](https://github.com/josx/ra-data-feathers/pull/171) ([ucokfm](https://github.com/ucokfm)) 45 | 46 | ## [v2.9.3](https://github.com/josx/ra-data-feathers/tree/v2.9.3) (2022-01-25) 47 | 48 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.9.2...v2.9.3) 49 | 50 | ## [v2.9.2](https://github.com/josx/ra-data-feathers/tree/v2.9.2) (2022-01-25) 51 | 52 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.9.1...v2.9.2) 53 | 54 | **Closed issues:** 55 | 56 | - Example feathers client? [\#169](https://github.com/josx/ra-data-feathers/issues/169) 57 | - Authorization Error [\#167](https://github.com/josx/ra-data-feathers/issues/167) 58 | - Encrypting password from authProvider [\#166](https://github.com/josx/ra-data-feathers/issues/166) 59 | - File upload [\#163](https://github.com/josx/ra-data-feathers/issues/163) 60 | 61 | **Merged pull requests:** 62 | 63 | - Bump shelljs from 0.8.4 to 0.8.5 [\#170](https://github.com/josx/ra-data-feathers/pull/170) ([dependabot[bot]](https://github.com/apps/dependabot)) 64 | - Fixes default storageKey value. [\#165](https://github.com/josx/ra-data-feathers/pull/165) ([olosegres](https://github.com/olosegres)) 65 | - Bump path-parse from 1.0.6 to 1.0.7 [\#164](https://github.com/josx/ra-data-feathers/pull/164) ([dependabot[bot]](https://github.com/apps/dependabot)) 66 | 67 | ## [v2.9.1](https://github.com/josx/ra-data-feathers/tree/v2.9.1) (2021-06-04) 68 | 69 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.9.0...v2.9.1) 70 | 71 | **Closed issues:** 72 | 73 | - Typescript error [\#161](https://github.com/josx/ra-data-feathers/issues/161) 74 | 75 | **Merged pull requests:** 76 | 77 | - Fix type another props should be not required [\#162](https://github.com/josx/ra-data-feathers/pull/162) ([berviantoleo](https://github.com/berviantoleo)) 78 | 79 | ## [v2.9.0](https://github.com/josx/ra-data-feathers/tree/v2.9.0) (2021-06-02) 80 | 81 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.8.1...v2.9.0) 82 | 83 | **Closed issues:** 84 | 85 | - No redirect to login after API returns 401 [\#155](https://github.com/josx/ra-data-feathers/issues/155) 86 | - Typescript support [\#153](https://github.com/josx/ra-data-feathers/issues/153) 87 | - Investigate adding ability to build/test easily in Windows [\#93](https://github.com/josx/ra-data-feathers/issues/93) 88 | 89 | **Merged pull requests:** 90 | 91 | - Bump ua-parser-js from 0.7.17 to 0.7.28 [\#160](https://github.com/josx/ra-data-feathers/pull/160) ([dependabot[bot]](https://github.com/apps/dependabot)) 92 | - Bump y18n from 4.0.0 to 4.0.3 [\#159](https://github.com/josx/ra-data-feathers/pull/159) ([dependabot[bot]](https://github.com/apps/dependabot)) 93 | - Bump lodash from 4.17.19 to 4.17.21 [\#158](https://github.com/josx/ra-data-feathers/pull/158) ([dependabot[bot]](https://github.com/apps/dependabot)) 94 | - Bump hosted-git-info from 2.7.1 to 2.8.9 [\#157](https://github.com/josx/ra-data-feathers/pull/157) ([dependabot[bot]](https://github.com/apps/dependabot)) 95 | - Update project compatibility for build/test at Windows [\#156](https://github.com/josx/ra-data-feathers/pull/156) ([berviantoleo](https://github.com/berviantoleo)) 96 | - Add types [\#154](https://github.com/josx/ra-data-feathers/pull/154) ([berviantoleo](https://github.com/berviantoleo)) 97 | 98 | ## [v2.8.1](https://github.com/josx/ra-data-feathers/tree/v2.8.1) (2020-07-31) 99 | 100 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.8.0...v2.8.1) 101 | 102 | **Closed issues:** 103 | 104 | - Error using GET\_LIST with empty set of parameters [\#151](https://github.com/josx/ra-data-feathers/issues/151) 105 | 106 | **Merged pull requests:** 107 | 108 | - Fix \#151 - Error using GET\_LIST with empty set of parameters [\#152](https://github.com/josx/ra-data-feathers/pull/152) ([josx](https://github.com/josx)) 109 | - Bump lodash from 4.17.15 to 4.17.19 [\#150](https://github.com/josx/ra-data-feathers/pull/150) ([dependabot[bot]](https://github.com/apps/dependabot)) 110 | - All contributors [\#149](https://github.com/josx/ra-data-feathers/pull/149) ([josx](https://github.com/josx)) 111 | 112 | ## [v2.8.0](https://github.com/josx/ra-data-feathers/tree/v2.8.0) (2020-07-10) 113 | 114 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.7.0...v2.8.0) 115 | 116 | **Merged pull requests:** 117 | 118 | - \[RFR\] fix auth client logout action issue [\#147](https://github.com/josx/ra-data-feathers/pull/147) ([FacundoMainere](https://github.com/FacundoMainere)) 119 | - Bump acorn from 6.1.1 to 6.4.1 [\#144](https://github.com/josx/ra-data-feathers/pull/144) ([dependabot[bot]](https://github.com/apps/dependabot)) 120 | 121 | ## [v2.7.0](https://github.com/josx/ra-data-feathers/tree/v2.7.0) (2020-03-12) 122 | 123 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.6.0...v2.7.0) 124 | 125 | **Closed issues:** 126 | 127 | - $in query operator not properly supported [\#140](https://github.com/josx/ra-data-feathers/issues/140) 128 | - react admin v3 alpha has breaking changes any plans to update ? [\#110](https://github.com/josx/ra-data-feathers/issues/110) 129 | 130 | **Merged pull requests:** 131 | 132 | - Add support for custom query operators in GET\_MANY [\#142](https://github.com/josx/ra-data-feathers/pull/142) ([alex-all3dp](https://github.com/alex-all3dp)) 133 | - Fix in and nin support [\#141](https://github.com/josx/ra-data-feathers/pull/141) ([josx](https://github.com/josx)) 134 | - add global option "useMulti" for configuring bulk actions [\#139](https://github.com/josx/ra-data-feathers/pull/139) ([loming](https://github.com/loming)) 135 | 136 | ## [v2.6.0](https://github.com/josx/ra-data-feathers/tree/v2.6.0) (2020-02-18) 137 | 138 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.5.0...v2.6.0) 139 | 140 | **Closed issues:** 141 | 142 | - Query filter not working for nested fields [\#137](https://github.com/josx/ra-data-feathers/issues/137) 143 | - Logout problem react-admin 3, feathers 4 [\#132](https://github.com/josx/ra-data-feathers/issues/132) 144 | 145 | **Merged pull requests:** 146 | 147 | - fix nested fields filtering [\#138](https://github.com/josx/ra-data-feathers/pull/138) ([fabioptoi](https://github.com/fabioptoi)) 148 | - Bump handlebars from 4.1.2 to 4.5.3 [\#136](https://github.com/josx/ra-data-feathers/pull/136) ([dependabot[bot]](https://github.com/apps/dependabot)) 149 | 150 | ## [v2.5.0](https://github.com/josx/ra-data-feathers/tree/v2.5.0) (2019-12-10) 151 | 152 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.4.1...v2.5.0) 153 | 154 | **Closed issues:** 155 | 156 | - GET\_ONE should accept params [\#131](https://github.com/josx/ra-data-feathers/issues/131) 157 | 158 | **Merged pull requests:** 159 | 160 | - fix\(GET\_ONE\): avoid send id as param on service.get [\#135](https://github.com/josx/ra-data-feathers/pull/135) ([josx](https://github.com/josx)) 161 | - fix\(linter\): avoid linter errors [\#134](https://github.com/josx/ra-data-feathers/pull/134) ([josx](https://github.com/josx)) 162 | - Feather: pass params on GET\_ONE to service.get [\#133](https://github.com/josx/ra-data-feathers/pull/133) ([josx](https://github.com/josx)) 163 | - fix\(feathers-4\): call reAuthenticate in AUTH\_CHECK if available [\#130](https://github.com/josx/ra-data-feathers/pull/130) ([jvke](https://github.com/jvke)) 164 | 165 | ## [v2.4.1](https://github.com/josx/ra-data-feathers/tree/v2.4.1) (2019-10-28) 166 | 167 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.4.0...v2.4.1) 168 | 169 | **Closed issues:** 170 | 171 | - Socketio [\#128](https://github.com/josx/ra-data-feathers/issues/128) 172 | 173 | **Merged pull requests:** 174 | 175 | - Move chai-as-promised to devDependencies [\#129](https://github.com/josx/ra-data-feathers/pull/129) ([vonagam](https://github.com/vonagam)) 176 | 177 | ## [v2.4.0](https://github.com/josx/ra-data-feathers/tree/v2.4.0) (2019-09-18) 178 | 179 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.3.0...v2.4.0) 180 | 181 | **Closed issues:** 182 | 183 | - ra-data-feathers doesn't work with feathers v4 [\#124](https://github.com/josx/ra-data-feathers/issues/124) 184 | - upgrade feathers-client to v4 [\#123](https://github.com/josx/ra-data-feathers/issues/123) 185 | 186 | **Merged pull requests:** 187 | 188 | - Undo softDelete option and support v4 [\#125](https://github.com/josx/ra-data-feathers/pull/125) ([vonagam](https://github.com/vonagam)) 189 | 190 | ## [v2.3.0](https://github.com/josx/ra-data-feathers/tree/v2.3.0) (2019-09-02) 191 | 192 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.2.1...v2.3.0) 193 | 194 | **Closed issues:** 195 | 196 | - softDelete and react-admin, how do I send extra params to the api during update requests [\#120](https://github.com/josx/ra-data-feathers/issues/120) 197 | - Consolidated place to discuss the 'call authenticate on every request' related changes [\#98](https://github.com/josx/ra-data-feathers/issues/98) 198 | 199 | **Merged pull requests:** 200 | 201 | - Bump eslint-utils from 1.3.1 to 1.4.2 [\#122](https://github.com/josx/ra-data-feathers/pull/122) ([dependabot[bot]](https://github.com/apps/dependabot)) 202 | - Adds softDelete as an option. If true, adds $ignoreDeletedAt: true to… [\#121](https://github.com/josx/ra-data-feathers/pull/121) ([DanStorm](https://github.com/DanStorm)) 203 | 204 | ## [v2.2.1](https://github.com/josx/ra-data-feathers/tree/v2.2.1) (2019-08-02) 205 | 206 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.2.0...v2.2.1) 207 | 208 | **Merged pull requests:** 209 | 210 | - Bump diff from 3.2.0 to 3.5.0 [\#119](https://github.com/josx/ra-data-feathers/pull/119) ([dependabot[bot]](https://github.com/apps/dependabot)) 211 | - Bump js-yaml from 3.9.0 to 3.13.1 [\#118](https://github.com/josx/ra-data-feathers/pull/118) ([dependabot[bot]](https://github.com/apps/dependabot)) 212 | - Bump fstream from 1.0.11 to 1.0.12 [\#117](https://github.com/josx/ra-data-feathers/pull/117) ([dependabot[bot]](https://github.com/apps/dependabot)) 213 | - Bump react-dom from 16.4.1 to 16.4.2 [\#116](https://github.com/josx/ra-data-feathers/pull/116) ([dependabot[bot]](https://github.com/apps/dependabot)) 214 | - Bump sshpk from 1.13.0 to 1.16.1 [\#115](https://github.com/josx/ra-data-feathers/pull/115) ([dependabot[bot]](https://github.com/apps/dependabot)) 215 | - Bump extend from 3.0.1 to 3.0.2 [\#114](https://github.com/josx/ra-data-feathers/pull/114) ([dependabot[bot]](https://github.com/apps/dependabot)) 216 | - Bump tough-cookie from 2.3.2 to 2.3.4 [\#113](https://github.com/josx/ra-data-feathers/pull/113) ([dependabot[bot]](https://github.com/apps/dependabot)) 217 | 218 | ## [v2.2.0](https://github.com/josx/ra-data-feathers/tree/v2.2.0) (2019-08-02) 219 | 220 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.1.0...v2.2.0) 221 | 222 | **Closed issues:** 223 | 224 | - Logging out on 403 errors [\#111](https://github.com/josx/ra-data-feathers/issues/111) 225 | - Missing Authorization headers after login success [\#107](https://github.com/josx/ra-data-feathers/issues/107) 226 | 227 | **Merged pull requests:** 228 | 229 | - Add option to logout on forbidden response code [\#112](https://github.com/josx/ra-data-feathers/pull/112) ([TheHyphen](https://github.com/TheHyphen)) 230 | - Bump lodash from 4.17.4 to 4.17.15 [\#109](https://github.com/josx/ra-data-feathers/pull/109) ([dependabot[bot]](https://github.com/apps/dependabot)) 231 | - Bump lodash-es from 4.17.4 to 4.17.14 [\#108](https://github.com/josx/ra-data-feathers/pull/108) ([dependabot[bot]](https://github.com/apps/dependabot)) 232 | - Bump handlebars from 4.0.12 to 4.1.2 [\#106](https://github.com/josx/ra-data-feathers/pull/106) ([dependabot[bot]](https://github.com/apps/dependabot)) 233 | 234 | ## [v2.1.0](https://github.com/josx/ra-data-feathers/tree/v2.1.0) (2019-06-21) 235 | 236 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.8...v2.1.0) 237 | 238 | **Closed issues:** 239 | 240 | - id property in put request payload [\#102](https://github.com/josx/ra-data-feathers/issues/102) 241 | - Working with non-paginated feathers services [\#101](https://github.com/josx/ra-data-feathers/issues/101) 242 | - Improvement: Performant bulk-actions [\#81](https://github.com/josx/ra-data-feathers/issues/81) 243 | 244 | **Merged pull requests:** 245 | 246 | - Bump stringstream from 0.0.5 to 0.0.6 [\#105](https://github.com/josx/ra-data-feathers/pull/105) ([dependabot[bot]](https://github.com/apps/dependabot)) 247 | - Support non paginated services [\#104](https://github.com/josx/ra-data-feathers/pull/104) ([josx](https://github.com/josx)) 248 | - Remove id prop [\#103](https://github.com/josx/ra-data-feathers/pull/103) ([josx](https://github.com/josx)) 249 | - Issue \#81 Performant bulk actions [\#100](https://github.com/josx/ra-data-feathers/pull/100) ([taylorgoodallau](https://github.com/taylorgoodallau)) 250 | - Correct default authenticate field name [\#99](https://github.com/josx/ra-data-feathers/pull/99) ([nhkhanh](https://github.com/nhkhanh)) 251 | 252 | ## [v2.0.8](https://github.com/josx/ra-data-feathers/tree/v2.0.8) (2019-02-12) 253 | 254 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.7...v2.0.8) 255 | 256 | **Closed issues:** 257 | 258 | - Use only a single package manager [\#96](https://github.com/josx/ra-data-feathers/issues/96) 259 | - ReferenceField is sending wrong request [\#92](https://github.com/josx/ra-data-feathers/issues/92) 260 | - Nested query filter support is now broken [\#90](https://github.com/josx/ra-data-feathers/issues/90) 261 | - Column doesn't exist. [\#89](https://github.com/josx/ra-data-feathers/issues/89) 262 | - Fix yarn issue for codeship [\#88](https://github.com/josx/ra-data-feathers/issues/88) 263 | - Support for feathers v3? [\#86](https://github.com/josx/ra-data-feathers/issues/86) 264 | - Bugfix: $sort field set incorrectly in GET\_LIST handler [\#84](https://github.com/josx/ra-data-feathers/issues/84) 265 | - Possible Improvement: Code Style [\#79](https://github.com/josx/ra-data-feathers/issues/79) 266 | - improve indentation of code in readme [\#77](https://github.com/josx/ra-data-feathers/issues/77) 267 | - Support UPDATE\_MANY [\#76](https://github.com/josx/ra-data-feathers/issues/76) 268 | - Improve Documentation [\#74](https://github.com/josx/ra-data-feathers/issues/74) 269 | - Suggestion: Change package name to reflect upgrade from AOR to RA [\#71](https://github.com/josx/ra-data-feathers/issues/71) 270 | - Add redirectTo option to readme [\#70](https://github.com/josx/ra-data-feathers/issues/70) 271 | 272 | **Merged pull requests:** 273 | 274 | - Avoid two lock file, just using yarn [\#97](https://github.com/josx/ra-data-feathers/pull/97) ([josx](https://github.com/josx)) 275 | - Update Node version in CircleCI config [\#95](https://github.com/josx/ra-data-feathers/pull/95) ([nicholasnelson](https://github.com/nicholasnelson)) 276 | - Fix Nested Queries in GET\_LIST [\#94](https://github.com/josx/ra-data-feathers/pull/94) ([nicholasnelson](https://github.com/nicholasnelson)) 277 | - Fix formatting of query.$sort [\#85](https://github.com/josx/ra-data-feathers/pull/85) ([nicholasnelson](https://github.com/nicholasnelson)) 278 | - \#79 code style improvement [\#82](https://github.com/josx/ra-data-feathers/pull/82) ([sgobotta](https://github.com/sgobotta)) 279 | - Add tests and code for UPDATE\_MANY method [\#80](https://github.com/josx/ra-data-feathers/pull/80) ([nicholasnelson](https://github.com/nicholasnelson)) 280 | - Improve Indentation in README.md [\#78](https://github.com/josx/ra-data-feathers/pull/78) ([nicholasnelson](https://github.com/nicholasnelson)) 281 | - RR: Update README.md [\#75](https://github.com/josx/ra-data-feathers/pull/75) ([nicholasnelson](https://github.com/nicholasnelson)) 282 | - Add basic documentation of redirectTo option [\#73](https://github.com/josx/ra-data-feathers/pull/73) ([nicholasnelson](https://github.com/nicholasnelson)) 283 | 284 | ## [v2.0.7](https://github.com/josx/ra-data-feathers/tree/v2.0.7) (2018-09-26) 285 | 286 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.6...v2.0.7) 287 | 288 | **Merged pull requests:** 289 | 290 | - Renaming aor-feathers-client to ra-data-feathers [\#72](https://github.com/josx/ra-data-feathers/pull/72) ([josx](https://github.com/josx)) 291 | 292 | ## [v2.0.6](https://github.com/josx/ra-data-feathers/tree/v2.0.6) (2018-09-26) 293 | 294 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.5...v2.0.6) 295 | 296 | **Closed issues:** 297 | 298 | - on AUTH\_CHECK add custom redirect to [\#59](https://github.com/josx/ra-data-feathers/issues/59) 299 | - Trouble accessing the "\_id" value of the loggedin user [\#54](https://github.com/josx/ra-data-feathers/issues/54) 300 | - Missing depency 'feathers-client' [\#28](https://github.com/josx/ra-data-feathers/issues/28) 301 | 302 | ## [v2.0.5](https://github.com/josx/ra-data-feathers/tree/v2.0.5) (2018-09-13) 303 | 304 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.4...v2.0.5) 305 | 306 | **Closed issues:** 307 | 308 | - Add DELETE\_MANY support [\#65](https://github.com/josx/ra-data-feathers/issues/65) 309 | - AuthClient: check if not any type [\#60](https://github.com/josx/ra-data-feathers/issues/60) 310 | 311 | **Merged pull requests:** 312 | 313 | - Update istanbul version [\#69](https://github.com/josx/ra-data-feathers/pull/69) ([josx](https://github.com/josx)) 314 | - Add redirectTo to options [\#68](https://github.com/josx/ra-data-feathers/pull/68) ([nicholasnelson](https://github.com/nicholasnelson)) 315 | - Replace throw with Promise.reject [\#67](https://github.com/josx/ra-data-feathers/pull/67) ([nicholasnelson](https://github.com/nicholasnelson)) 316 | - Implement DELETE\_MANY [\#66](https://github.com/josx/ra-data-feathers/pull/66) ([nicholasnelson](https://github.com/nicholasnelson)) 317 | 318 | ## [v2.0.4](https://github.com/josx/ra-data-feathers/tree/v2.0.4) (2018-08-10) 319 | 320 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.3...v2.0.4) 321 | 322 | **Merged pull requests:** 323 | 324 | - Kill calling feathers authenticate for every service [\#64](https://github.com/josx/ra-data-feathers/pull/64) ([josx](https://github.com/josx)) 325 | 326 | ## [v2.0.3](https://github.com/josx/ra-data-feathers/tree/v2.0.3) (2018-07-26) 327 | 328 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.2...v2.0.3) 329 | 330 | **Closed issues:** 331 | 332 | - on AUTH\_GET\_PERMISSIONS reject promise [\#61](https://github.com/josx/ra-data-feathers/issues/61) 333 | 334 | **Merged pull requests:** 335 | 336 | - CREATE: adding backend api response to dataprovider response [\#63](https://github.com/josx/ra-data-feathers/pull/63) ([josx](https://github.com/josx)) 337 | 338 | ## [v2.0.2](https://github.com/josx/ra-data-feathers/tree/v2.0.2) (2018-07-26) 339 | 340 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.1...v2.0.2) 341 | 342 | **Closed issues:** 343 | 344 | - On AUTH\_ERROR add rejection for 403 [\#58](https://github.com/josx/ra-data-feathers/issues/58) 345 | 346 | **Merged pull requests:** 347 | 348 | - Auth Promise Rejection: AUTH\_ERROR code 403, AUTH\_GET\_PERMISSIONS \(\#5… [\#62](https://github.com/josx/ra-data-feathers/pull/62) ([josx](https://github.com/josx)) 349 | 350 | ## [v2.0.1](https://github.com/josx/ra-data-feathers/tree/v2.0.1) (2018-07-13) 351 | 352 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v2.0.0...v2.0.1) 353 | 354 | **Merged pull requests:** 355 | 356 | - Authclient options: Custom username and password [\#57](https://github.com/josx/ra-data-feathers/pull/57) ([josx](https://github.com/josx)) 357 | 358 | ## [v2.0.0](https://github.com/josx/ra-data-feathers/tree/v2.0.0) (2018-07-05) 359 | 360 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v1.0.0...v2.0.0) 361 | 362 | **Merged pull requests:** 363 | 364 | - React admin [\#56](https://github.com/josx/ra-data-feathers/pull/56) ([josx](https://github.com/josx)) 365 | 366 | ## [v1.0.0](https://github.com/josx/ra-data-feathers/tree/v1.0.0) (2018-07-05) 367 | 368 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.9.0...v1.0.0) 369 | 370 | ## [v0.9.0](https://github.com/josx/ra-data-feathers/tree/v0.9.0) (2018-03-15) 371 | 372 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.8.0...v0.9.0) 373 | 374 | **Closed issues:** 375 | 376 | - Filtering by nested properties [\#52](https://github.com/josx/ra-data-feathers/issues/52) 377 | - Does this work with feathers v3? \[question\] [\#51](https://github.com/josx/ra-data-feathers/issues/51) 378 | 379 | **Merged pull requests:** 380 | 381 | - Fix filter query for nested properties [\#53](https://github.com/josx/ra-data-feathers/pull/53) ([dprentis](https://github.com/dprentis)) 382 | - usePatch: Do object-diff only when params.previousData is present [\#50](https://github.com/josx/ra-data-feathers/pull/50) ([lijoantony](https://github.com/lijoantony)) 383 | 384 | ## [v0.8.0](https://github.com/josx/ra-data-feathers/tree/v0.8.0) (2017-12-14) 385 | 386 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.7.1...v0.8.0) 387 | 388 | **Closed issues:** 389 | 390 | - Permission example in readme [\#47](https://github.com/josx/ra-data-feathers/issues/47) 391 | - Use PATCH instead of PUT [\#20](https://github.com/josx/ra-data-feathers/issues/20) 392 | 393 | **Merged pull requests:** 394 | 395 | - Use PATCH instead of PUT for UPDATE [\#49](https://github.com/josx/ra-data-feathers/pull/49) ([lijoantony](https://github.com/lijoantony)) 396 | - Permission example in readme \(related to \#47\) [\#48](https://github.com/josx/ra-data-feathers/pull/48) ([kfern](https://github.com/kfern)) 397 | 398 | ## [v0.7.1](https://github.com/josx/ra-data-feathers/tree/v0.7.1) (2017-10-09) 399 | 400 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.7.0...v0.7.1) 401 | 402 | **Merged pull requests:** 403 | 404 | - Add jwt-decode on dev deps [\#46](https://github.com/josx/ra-data-feathers/pull/46) ([josx](https://github.com/josx)) 405 | 406 | ## [v0.7.0](https://github.com/josx/ra-data-feathers/tree/v0.7.0) (2017-10-09) 407 | 408 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.6.0...v0.7.0) 409 | 410 | **Closed issues:** 411 | 412 | - I left a describe.only in unit tests. Sorry [\#44](https://github.com/josx/ra-data-feathers/issues/44) 413 | - Add AUTH\_GET\_PERMISSIONS to authClient [\#43](https://github.com/josx/ra-data-feathers/issues/43) 414 | - return object for restClient [\#40](https://github.com/josx/ra-data-feathers/issues/40) 415 | - feathers-jwt deleted on page reload [\#39](https://github.com/josx/ra-data-feathers/issues/39) 416 | - Add GET\_MANY\_REFERENCES support [\#7](https://github.com/josx/ra-data-feathers/issues/7) 417 | 418 | **Merged pull requests:** 419 | 420 | - AUTH\_GET\_PERMISSIONS [\#45](https://github.com/josx/ra-data-feathers/pull/45) ([fonzarely](https://github.com/fonzarely)) 421 | - GET MANY REFERENCE [\#42](https://github.com/josx/ra-data-feathers/pull/42) ([fonzarely](https://github.com/fonzarely)) 422 | 423 | ## [v0.6.0](https://github.com/josx/ra-data-feathers/tree/v0.6.0) (2017-09-01) 424 | 425 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.5.0...v0.6.0) 426 | 427 | **Closed issues:** 428 | 429 | - GET\_MANY does not return with id in mapResponse [\#37](https://github.com/josx/ra-data-feathers/issues/37) 430 | - Authorization header missing from requests [\#35](https://github.com/josx/ra-data-feathers/issues/35) 431 | 432 | **Merged pull requests:** 433 | 434 | - Fix mapresponse missing id handling [\#38](https://github.com/josx/ra-data-feathers/pull/38) ([dreamrace](https://github.com/dreamrace)) 435 | - Call authenticate method on refresh page [\#36](https://github.com/josx/ra-data-feathers/pull/36) ([7flash](https://github.com/7flash)) 436 | 437 | ## [v0.5.0](https://github.com/josx/ra-data-feathers/tree/v0.5.0) (2017-08-10) 438 | 439 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.4.0...v0.5.0) 440 | 441 | **Closed issues:** 442 | 443 | - Support AUTH\_ERROR [\#31](https://github.com/josx/ra-data-feathers/issues/31) 444 | - issues with primary-key/id not named 'id' [\#30](https://github.com/josx/ra-data-feathers/issues/30) 445 | - Cannot read property 'map' of undefined? [\#29](https://github.com/josx/ra-data-feathers/issues/29) 446 | - custom ids tests [\#27](https://github.com/josx/ra-data-feathers/issues/27) 447 | - Deprecated package - react-addons-transition-group [\#23](https://github.com/josx/ra-data-feathers/issues/23) 448 | - Write package description [\#17](https://github.com/josx/ra-data-feathers/issues/17) 449 | 450 | **Merged pull requests:** 451 | 452 | - Update contributors [\#34](https://github.com/josx/ra-data-feathers/pull/34) ([josx](https://github.com/josx)) 453 | - support for resource specific id field overrides [\#33](https://github.com/josx/ra-data-feathers/pull/33) ([tony-kerz](https://github.com/tony-kerz)) 454 | - Implements AUTH\_ERROR support [\#32](https://github.com/josx/ra-data-feathers/pull/32) ([lijoantony](https://github.com/lijoantony)) 455 | 456 | ## [v0.4.0](https://github.com/josx/ra-data-feathers/tree/v0.4.0) (2017-07-13) 457 | 458 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.3.1...v0.4.0) 459 | 460 | **Closed issues:** 461 | 462 | - Duplicated Presentation when using mongoose [\#25](https://github.com/josx/ra-data-feathers/issues/25) 463 | - Add circleCI to automate PR testing ? [\#22](https://github.com/josx/ra-data-feathers/issues/22) 464 | - Custom identifiers/primary keys for in resources [\#16](https://github.com/josx/ra-data-feathers/issues/16) 465 | - feathers-client in peerDependencies ? [\#15](https://github.com/josx/ra-data-feathers/issues/15) 466 | 467 | **Merged pull requests:** 468 | 469 | - Custom ids support [\#26](https://github.com/josx/ra-data-feathers/pull/26) ([josx](https://github.com/josx)) 470 | - Add circle ci [\#24](https://github.com/josx/ra-data-feathers/pull/24) ([fonzarely](https://github.com/fonzarely)) 471 | - Add feathers-client as peerDependencies [\#19](https://github.com/josx/ra-data-feathers/pull/19) ([fonzarely](https://github.com/fonzarely)) 472 | - Fix response mapping on delete routes [\#18](https://github.com/josx/ra-data-feathers/pull/18) ([wedneyyuri](https://github.com/wedneyyuri)) 473 | - add some tests for rest client [\#14](https://github.com/josx/ra-data-feathers/pull/14) ([AmrN](https://github.com/AmrN)) 474 | 475 | ## [v0.3.1](https://github.com/josx/ra-data-feathers/tree/v0.3.1) (2017-05-16) 476 | 477 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.3.0...v0.3.1) 478 | 479 | **Closed issues:** 480 | 481 | - Uncaught Error: injectTapEventPlugin\(\): Can only be called once per application lifecycle [\#9](https://github.com/josx/ra-data-feathers/issues/9) 482 | - Error for query filters [\#8](https://github.com/josx/ra-data-feathers/issues/8) 483 | 484 | **Merged pull requests:** 485 | 486 | - Deps [\#13](https://github.com/josx/ra-data-feathers/pull/13) ([josx](https://github.com/josx)) 487 | - remove unnecessary brackets that result in a broken query [\#12](https://github.com/josx/ra-data-feathers/pull/12) ([AmrN](https://github.com/AmrN)) 488 | - Fixed rest types import [\#11](https://github.com/josx/ra-data-feathers/pull/11) ([wedneyyuri](https://github.com/wedneyyuri)) 489 | - Update admin-on-rest version [\#10](https://github.com/josx/ra-data-feathers/pull/10) ([AmrN](https://github.com/AmrN)) 490 | 491 | ## [v0.3.0](https://github.com/josx/ra-data-feathers/tree/v0.3.0) (2017-03-27) 492 | 493 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.2.0...v0.3.0) 494 | 495 | **Closed issues:** 496 | 497 | - Rest client does not correctly handle GET\_MANY [\#5](https://github.com/josx/ra-data-feathers/issues/5) 498 | 499 | **Merged pull requests:** 500 | 501 | - Fixed GET\_MANY handler [\#6](https://github.com/josx/ra-data-feathers/pull/6) ([ryanthegiantlion](https://github.com/ryanthegiantlion)) 502 | 503 | ## [v0.2.0](https://github.com/josx/ra-data-feathers/tree/v0.2.0) (2017-03-14) 504 | 505 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/v0.1.0...v0.2.0) 506 | 507 | **Closed issues:** 508 | 509 | - Add authClient [\#3](https://github.com/josx/ra-data-feathers/issues/3) 510 | - make it compatible with admin-on-rest 0.9 [\#1](https://github.com/josx/ra-data-feathers/issues/1) 511 | 512 | **Merged pull requests:** 513 | 514 | - Rewrite with feathers-client [\#2](https://github.com/josx/ra-data-feathers/pull/2) ([tb](https://github.com/tb)) 515 | 516 | ## [v0.1.0](https://github.com/josx/ra-data-feathers/tree/v0.1.0) (2017-03-04) 517 | 518 | [Full Changelog](https://github.com/josx/ra-data-feathers/compare/27c9a72baea7a2ba2ff700d741caf82be066069e...v0.1.0) 519 | 520 | 521 | 522 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 523 | --------------------------------------------------------------------------------