Comparing version 0.1.14 to 1.0.0
{ | ||
"name": "redux-nl", | ||
"version": "0.1.14", | ||
"_from": "redux-nl", | ||
"_id": "redux-nl@0.1.14", | ||
"_inBundle": false, | ||
"_integrity": "sha512-nH4sbHMnUjdo8UcqYL5TWHTueoBQhiiFN73XJ96lzM6oHSOw9B6FtrdVcFO0vejYbIsmGqHl2vfdRSIYAzgfFQ==", | ||
"_location": "/redux-nl", | ||
"_phantomChildren": {}, | ||
"_requested": { | ||
"type": "tag", | ||
"registry": true, | ||
"raw": "redux-nl", | ||
"name": "redux-nl", | ||
"escapedName": "redux-nl", | ||
"rawSpec": "", | ||
"saveSpec": null, | ||
"fetchSpec": "latest" | ||
}, | ||
"_requiredBy": [ | ||
"#USER", | ||
"/" | ||
], | ||
"_resolved": "https://registry.npmjs.org/redux-nl/-/redux-nl-0.1.14.tgz", | ||
"_shasum": "9b65f8916c80f5be264dea9c5afa39f14a23c7ed", | ||
"_spec": "redux-nl", | ||
"_where": "/Users/user/Desktop/Projects/redux-nl-example", | ||
"author": { | ||
"name": "Luke Brandon Farrell" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/aspect-apps/redux-nl/issues" | ||
}, | ||
"bundleDependencies": false, | ||
"dependencies": { | ||
"axios": "^0.19.2", | ||
"lodash.castarray": "^4.4.0", | ||
"lodash.hasin": "^4.5.2", | ||
"lodash.mapkeys": "^4.6.0", | ||
"lodash.omit": "^4.5.0", | ||
"lodash.snakecase": "^4.1.1", | ||
"lodash.startcase": "^4.4.0" | ||
}, | ||
"deprecated": false, | ||
"description": "A GraphQL inspired rest client side network layer", | ||
"homepage": "https://github.com/aspect-apps/redux-nl#readme", | ||
"keywords": [ | ||
@@ -14,2 +54,9 @@ "react-native", | ||
], | ||
"license": "MIT", | ||
"main": "src/index.js", | ||
"name": "redux-nl", | ||
"peerDependencies": { | ||
"redux": ">= 4.x", | ||
"redux-saga": ">= 1.x" | ||
}, | ||
"repository": { | ||
@@ -19,23 +66,8 @@ "type": "git", | ||
}, | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\"", | ||
"build": "flow-remove-types src/ -d lib/", | ||
"flow": "flow", | ||
"build": "flow-remove-types src/ -d lib/" | ||
"test": "echo \"Error: no test specified\"" | ||
}, | ||
"author": "Luke Brandon Farrell", | ||
"license": "MIT", | ||
"readme": "README.md", | ||
"dependencies": { | ||
"axios": "^0.19.2", | ||
"lodash.castarray": "^4.4.0", | ||
"lodash.mapkeys": "^4.6.0", | ||
"lodash.omit": "^4.5.0", | ||
"lodash.snakecase": "^4.1.1", | ||
"lodash.camelcase": "^4.1.1" | ||
}, | ||
"peerDependencies": { | ||
"redux": ">= 4.x", | ||
"redux-saga": ">= 1.x" | ||
} | ||
"version": "1.0.0" | ||
} |
162
README.md
<p align="center"> | ||
<img src="https://raw.githubusercontent.com/aspect-apps/redux-ql/master/assets/thumbnail-dark-redux-ql.png" width="190" height="190"> | ||
<br /> | ||
<a href="https://enjoy.gitstore.app/repositories/aspect-apps/redux-nl"><img src="https://enjoy.gitstore.app/repositories/badge-aspect-apps/redux-nl.svg"></a> | ||
<hr /> | ||
@@ -10,7 +9,7 @@ </p> | ||
Redux Network Layer is a network layer for your application powered by redux and redux-saga heavily inspired by GraphQL clients such as react-relay. | ||
Redux Network Layer is a network layer for your application powered by redux and redux-saga heavily inspired by GraphQL clients such as react-relay. This libary follows the [Flux Standard Action](https://github.com/redux-utilities/flux-standard-action) specification for redux actions. | ||
## Prerequisites | ||
This requires that you have both [redux](https://redux.js.org/) and [redux-saga](https://redux-saga.js.org/) setup in your project. | ||
This library requires that you have both [redux](https://redux.js.org/) and [redux-saga](https://redux-saga.js.org/) setup in your project. | ||
@@ -23,16 +22,22 @@ ## Setup | ||
For ReduxNL to work correctly you need to setup both the redux-nl saga and action reducer. | ||
For ReduxNL to work correctly you need to run the `ReduxNL.setup` and add the action reduccer. Redux NL is powered by [redux-saga](https://redux-saga.js.org/), below is a basic setup for redux-saga and your redux store. | ||
The ReduxNL saga powers your networks layer. This can be spawned into your root reducer with the secound parameter of the spawn function as your API base url e.g. `https://my-example-api/`. Configuring the URL in this manner allows you to setup different URLs for local, sandbox and production environments. The third parameter is your API specification, example below. **We are looking at ways to auto-generate the API specification at compile-time from a `/spec` endpoint defined in your API**. | ||
```js | ||
import ReduxNLSaga from "redux-nl"; | ||
import { spawn } from "redux-saga"; | ||
import APISpecification from "../example-api.js"; | ||
import { ReduxNL } from "redux-nl"; | ||
import {applyMiddleware, combineReducers, createStore} from 'redux'; | ||
import createSagaMiddleware from 'redux-saga'; | ||
export function* rootSaga() { | ||
yield all([ | ||
// ReduxNL Network Sagas | ||
spawn(ReduxNLSaga, ApiUrl, APISpecification), | ||
const reducers = combineReducers({ | ||
action: ActionReducer, | ||
... | ||
}); | ||
const middleware = applyMiddleware(...[sagaMiddleware]); | ||
const sagaMiddleware = createSagaMiddleware(); | ||
const store = createStore(reducers, middleware); | ||
ReduxNL.setup(store, sagaMiddleware, { | ||
delay: 1000, // <--- adds a network delay for testing slow connections | ||
isDev: false // <--- Things like delay and console.warns will be ignored when this is false | ||
defaultErrorMessage: ".." // <--- Custom fallback message | ||
}); | ||
``` | ||
@@ -45,3 +50,3 @@ | ||
const rootReducer = combineReducers({ | ||
const reducers = combineReducers({ | ||
action: ActionReducer, | ||
@@ -51,17 +56,2 @@ ... | ||
Lastly, we need to configure ReduxNL by running the setup function after you intialie your store. This takes a few parameters which will allow you to customise your network layer. | ||
```js | ||
const store = createStore(rootReducer, middleware); | ||
ReduxNL.setup({ | ||
store, | ||
delay: 1000, <--- adds a network delay for testing slow connections | ||
isDev: false <--- Things like delay and console.warns will be ignored when this is false | ||
errorMessage: ".." <--- Custom fallback message | ||
}); | ||
``` | ||
**Note: the ideal in the future is to unify the above three steps into a single step redux middleware** | ||
## Usage | ||
@@ -71,7 +61,6 @@ | ||
The below example allows you to update your component in response to the fired request. You can pass paramters via the `payload` and `meta` properties, these will be used in your network request, more details below. The libary follows the [Flux Standard Action](https://github.com/redux-utilities/flux-standard-action) specification for redux actions. | ||
The below example allows you to update your component in response to the fired request. You can pass paramters via the `payload` and `meta` properties, these will be used in your network request, more details below. | ||
```js | ||
ReduxNL.post("/user/brands/{slug}", { | ||
ReduxNL.post("/user/brands/slug", { | ||
payload: { slug }, | ||
@@ -94,3 +83,3 @@ meta: { | ||
ReduxNl.promise.post("/user/brands/{slug}").then(...).catch(); | ||
ReduxNl.promise.post("/user/brands/slug").then(...).catch(); | ||
@@ -100,3 +89,3 @@ // OR... | ||
try { | ||
const action = await ReduxNl.promise.post("/user/brands/{slug}"); | ||
const action = await ReduxNl.promise.post("/user/brands/slug"); | ||
} catch(action){ | ||
@@ -110,6 +99,6 @@ // Handle error | ||
The above example will dispatch a `CREATE_USER_BRANDS_SLUG_RESPONSE` to the store once the request has completed (success or failure). You can listen to these actions in your reducer by using some redux-ql utilities: | ||
The above example will dispatch a `CreateUserBrandSlugResponse` to the store once the request has completed (success or failure). You can listen to these actions in your reducer by using the redux-nl utilities: | ||
```js | ||
const CreateBrandResponse = ReduxNL.response.type.post("/user/brands/{slug}"); | ||
const CreateBrandResponse = ReduxNL.response.type.post("/user/brands/slug"); | ||
const InitialState = { | ||
@@ -129,26 +118,42 @@ data: [], | ||
```js | ||
const CreateBrandResponse = ReduxNL.response.type.post("/user/brands/{slug}") -> CREATE_USER_BRANDS_SLUG_RESPONSE | ||
const UpdateBrandResponse = ReduxNL.response.type.patch("/user/brands/{slug}") -> UPDATE_USER_BRANDS_SLUG_RESPONSE | ||
const DeleteBrandResponse = ReduxNL.response.type.delete("/user/brands/{slug}") -> DELETE_USER_BRANDS_SLUG_RESPONSE | ||
const FetchBrandResponse = ReduxNL.response.type.get("/user/brands/{slug}") -> FETCH_USER_BRANDS_SLUG_RESPONSE | ||
const CreateBrandResponse = ReduxNL.response.type.post("/user/brands/{slug}") -> CreateUserBrandsSlugResponse | ||
const UpdateBrandResponse = ReduxNL.response.type.patch("/user/brands/{slug}") -> UpdateUserBrandsSlugResponse | ||
const DeleteBrandResponse = ReduxNL.response.type.delete("/user/brands/{slug}") -> DeleteUserBrandsSlugResponse | ||
const FetchBrandResponse = ReduxNL.response.type.get("/user/brands/{slug}") -> FetchUserBrandsSlugResponse | ||
const CreateBrandRequest = ReduxNL.request.type.post("/user/brands/{slug}") -> CREATE_USER_BRANDS_SLUG_REQUEST | ||
const UpdateBrandRequest = ReduxNL.request.type.patch("/user/brands/{slug}") -> UPDATE_USER_BRANDS_SLUG_REQUEST | ||
const DeleteBrandRequest = ReduxNL.request.type.delete("/user/brands/{slug}") -> DELETE_USER_BRANDS_SLUG_REQUEST | ||
const FetchBrandRequest = ReduxNL.request.type.get("/user/brands/{slug}") -> FETCH_USER_BRANDS_SLUG_REQUEST | ||
const CreateBrandRequest = ReduxNL.request.type.post("/user/brands/{slug}") -> CreateUserBrandsSlugRequest | ||
const UpdateBrandRequest = ReduxNL.request.type.patch("/user/brands/{slug}") -> UpdateUserBrandsSlugRequest | ||
const DeleteBrandRequest = ReduxNL.request.type.delete("/user/brands/{slug}") -> DeleteUserBrandsSlugRequest | ||
const FetchBrandRequest = ReduxNL.request.type.get("/user/brands/{slug}") -> FetchUserBrandsSlugRequest | ||
``` | ||
### Building URLs | ||
#### Request Parameters | ||
All paramters in `payload` are passed as data to POST, UPDATE and DELETE request. | ||
All paramters in `payload` are passed as data to POST, UPDATE and DELETE requests. These are automatically mapped to snake_case. i.e. | ||
#### Query Parameters | ||
```js | ||
ReduxNl.post("/user/brands", { | ||
payload: { | ||
hasCredit: false, | ||
}, | ||
meta: { | ||
apiToken: "...", | ||
}, | ||
}); | ||
``` | ||
Query parameters e.g. `https://my-example-api/user?api_token=..."` are automatically added to the URL via the `meta` key. | ||
Will be mapped into the request as such (the case is transformed to snake_case): | ||
#### Route Parameters | ||
``` | ||
POST https://my-example-api//user/brands?api_token=... | ||
{ | ||
has_credit: true | ||
} | ||
``` | ||
Route parameters are defined in your api specification as so `/user/brands/{slug}`. In this example, when a value with the key of `slug` is passed thorugh then it is automatically replaced in the URL e.g. `/user/brands/{slug}` -> `/user/brands/apple`. | ||
#### Request Headers | ||
#### Headers | ||
You can add headers in the meta object of your request. | ||
@@ -166,32 +171,39 @@ ``` | ||
## API Spec Example | ||
#### Query Parameters | ||
Query parameters e.g. `https://my-example-api/user?api_token=..."` are automatically added to the URL via the `meta` key. i.e. | ||
```js | ||
// example-api.js | ||
ReduxNl.post("/user/brands", { | ||
meta: { | ||
date: "2020-11-21", | ||
token: "rAHO82BrgJmshpIHJ8mpTVz2vvPyp1c0X1gjsn6UYDx", | ||
}, | ||
}); | ||
``` | ||
export default [ | ||
{ path: "/auth/login", method: "POST" }, | ||
{ path: "/auth/password-reset", method: "POST" }, | ||
{ path: "/user/verification", method: "POST" }, | ||
{ path: "/user", method: "PATCH" }, | ||
{ path: "/user", method: "GET" }, | ||
{ path: "/organisations/{slug}", method: "GET" }, | ||
{ path: "/user/brands/all", method: "GET" }, | ||
{ path: "/user/credits", method: "GET" }, | ||
{ path: "/user/brands/{slug}", method: "POST" }, | ||
{ path: "/user/brands/{slug}", method: "DELETE" }, | ||
{ path: "/user/orders", method: "GET" }, | ||
{ path: "/user/orders/{id}", method: "GET" }, | ||
{ path: "/checkout/{slug}/stripe", method: "POST" }, | ||
{ path: "/user/orders/{id}", method: "PATCH" }, | ||
{ path: "/user/stripe/card", method: "GET" }, | ||
{ path: "/user/stripe/card", method: "POST" }, | ||
{ path: "/user/stripe/card", method: "DELETE" }, | ||
{ path: "/user/subscription", method: "GET" }, | ||
]; | ||
The call above will result in a URL as such `/user/brands?date=2020-11-21&token=rAHO82BrgJmshpIHJ8mpTVz2vvPyp1c0X1gjsn6UYDx`. | ||
#### Route Parameters | ||
Route parameters are dynamically replaced. tTake the following path: `/user/brands/id`, when a value with the key of `id` is passed thorugh the `payload`, then it is automatically replaced in the URL e.g. `/user/brands/id` -> `/user/brands/34`. | ||
#### Local Chaining | ||
In some cases, you may want to pass values from a Request through to a response, so it can be used in your reducer. To support this the network saga supports a `chain` method. i.e. | ||
```js | ||
ReduxNl.post("/user/brands", { | ||
meta: { | ||
chain: { | ||
... | ||
} | ||
} | ||
}); | ||
``` | ||
## Limitations | ||
Any data inside the `chain` object will be passed through to the `CreateUserBrandsResponse`. | ||
- Only supports a single API connection. | ||
- Only supports single path parameter e.g. `"/{user}/orders/{id}"` would break the module. | ||
### Resources | ||
See [extract-your-api-to-a-client-side-library-using-redux-saga](https://medium.com/@lukebrandonfarrell/network-layer-extract-your-api-to-a-client-side-library-using-redux-saga-514fecfe34a7) to learn more about the architecture behind this library. |
@@ -39,2 +39,3 @@ /** | ||
const data = yield call(api, baseUrl, payload, meta); | ||
console.log({ data, payload }); | ||
@@ -41,0 +42,0 @@ yield put({ |
/** | ||
* @author Luke Brandon Farrell | ||
* @description | ||
* @description This build our action type | ||
* for a specific path with the Request suffix | ||
* i.e. GET /fetch -> FetchFactsRequest | ||
*/ | ||
import { getActionType } from "./get-action-type"; | ||
import { getReduxActionType } from "./get-redux-action-type"; | ||
/** | ||
* Gets our reducer value key from type for REQUEST | ||
* Gets our reducer value key from type for Request | ||
* | ||
@@ -17,3 +19,3 @@ * @param {string} verb | ||
export function getRequestType(verb, path) { | ||
return getActionType(verb, path, "REQUEST"); | ||
return getReduxActionType(verb, path, "Request"); | ||
} |
/** | ||
* @author Luke Brandon Farrell | ||
* @description | ||
* @description This build our action type | ||
* for a specific path with the Response suffix | ||
* i.e. GET /fetch -> FetchFactsResponse | ||
*/ | ||
import { getActionType } from "./get-action-type"; | ||
import { getReduxActionType } from "./get-redux-action-type"; | ||
/** | ||
* Gets our reducer value key from type for RESPONSE | ||
* Gets our reducer value key from type for Response | ||
* | ||
@@ -17,3 +19,3 @@ * @param {string} verb | ||
export function getResponseType(verb, path) { | ||
return getActionType(verb, path, "RESPONSE"); | ||
return getReduxActionType(verb, path, "Response"); | ||
} |
@@ -6,4 +6,3 @@ /** | ||
export * from "./redux-nl-saga"; | ||
export * from "./redux-nl"; | ||
export * from "./action-reducer"; |
@@ -12,2 +12,4 @@ /** | ||
import { config } from "./config"; | ||
import { RestVerbs } from "./rest-verbs"; | ||
import { listenForReduxNLActions } from "./listen-for-redux-nl-actions"; | ||
/* Constants */ | ||
@@ -17,3 +19,3 @@ let CurrentStore = null; | ||
export const ReduxNL = { | ||
setup: ({ store, delay, defaultErrorMessage, isDev = false }) => { | ||
setup: (store, sagaMiddleware, { delay, defaultUrl, defaultErrorMessage, isDev = false }) => { | ||
CurrentStore = store; | ||
@@ -25,2 +27,4 @@ | ||
if(defaultErrorMessage) config.errorMessage = defaultErrorMessage; | ||
sagaMiddleware.run(listenForReduxNLActions, defaultUrl); | ||
}, | ||
@@ -30,3 +34,3 @@ | ||
ReduxNL.dispatch({ | ||
verb: "post", | ||
verb: RestVerbs.Post, | ||
path, | ||
@@ -43,3 +47,3 @@ payload, | ||
ReduxNL.dispatch({ | ||
verb: "patch", | ||
verb: RestVerbs.Patch, | ||
path, | ||
@@ -56,3 +60,3 @@ payload, | ||
ReduxNL.dispatch({ | ||
verb: "get", | ||
verb: RestVerbs.Get, | ||
path, | ||
@@ -69,3 +73,3 @@ payload, | ||
ReduxNL.dispatch({ | ||
verb: "delete", | ||
verb: RestVerbs.Delete, | ||
path, | ||
@@ -186,12 +190,12 @@ payload, | ||
get: (path) => { | ||
return getRequestType("GET", path); | ||
return getRequestType(RestVerbs.Get, path); | ||
}, | ||
post: (path) => { | ||
return getRequestType("POST", path); | ||
return getRequestType(RestVerbs.Post, path); | ||
}, | ||
patch: (path) => { | ||
return getRequestType("PATCH", path); | ||
return getRequestType(RestVerbs.Patch, path); | ||
}, | ||
delete: (path) => { | ||
return getRequestType("DELETE", path); | ||
return getRequestType(RestVerbs.Delete, path); | ||
}, | ||
@@ -203,12 +207,12 @@ }, | ||
get: (path) => { | ||
return getResponseType("GET", path); | ||
return getResponseType(RestVerbs.Get, path); | ||
}, | ||
post: (path) => { | ||
return getResponseType("POST", path); | ||
return getResponseType(RestVerbs.Post, path); | ||
}, | ||
patch: (path) => { | ||
return getResponseType("PATCH", path); | ||
return getResponseType(RestVerbs.Patch, path); | ||
}, | ||
delete: (path) => { | ||
return getResponseType("DELETE", path); | ||
return getResponseType(RestVerbs.Delete, path); | ||
}, | ||
@@ -215,0 +219,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
36216
19
588
0
1
200
9
+ Addedlodash.hasin@^4.5.2
+ Addedlodash.startcase@^4.4.0
+ Addedlodash.hasin@4.5.2(transitive)
+ Addedlodash.startcase@4.4.0(transitive)
- Removedlodash.camelcase@^4.1.1
- Removedlodash.camelcase@4.3.0(transitive)