Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/resource builder #2

Merged
merged 26 commits into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
208892d
Create first saga handler for update action + start to create action …
Apr 22, 2018
0d4a72a
Handle read list resource and basic store it into redux store
Apr 29, 2018
4b85dc5
Add flow to project
Apr 29, 2018
3abd164
Add test stack
May 1, 2018
4867997
Change wording to better match data
May 1, 2018
3f43fc8
Add a test for readlist saga
May 1, 2018
af54843
Fix eslint recommandations
May 1, 2018
fbafb07
Add jsonapi resource reader with basic sort/filter/sideload operations
Startouf May 6, 2018
74c6dd7
Fix bad conception and implement paramsAsObject
Startouf May 6, 2018
e6b69da
Add code and tests for resource reads
Startouf May 31, 2018
040c4a6
Add jsonapi writer resources and circleci config
Startouf May 31, 2018
b01ebee
Update readme, add badges
Startouf May 31, 2018
d3154fd
Disable test watching for CI
Startouf May 31, 2018
3741ea9
Add better circleCI interface, skip some pending tests
Startouf May 31, 2018
1e57d17
Change write association linking DSL to better reflect toOne vs toMany
Startouf May 31, 2018
f57fa75
Implement review
Startouf Aug 2, 2018
c9ea94d
Fix eslint errors
Startouf Aug 4, 2018
6df0c7d
Update doc to handle collections
Startouf Aug 4, 2018
096bc51
Add URL stuff, improve existing code
Startouf Aug 4, 2018
00c099e
Handle API definitions
Startouf Aug 4, 2018
f9950ec
update API usage
Startouf Aug 4, 2018
55a9c43
WIP on review
Startouf Aug 7, 2018
de5883a
wip on review continued
Startouf Aug 7, 2018
8d9e9b3
Fix more ESLint errors
Startouf Aug 15, 2018
ac97b95
Add missing comment
Startouf Aug 15, 2018
e913e86
Implement review
Startouf Aug 15, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
"env",
"flow"
],
"plugins": [
"transform-runtime",
"transform-object-rest-spread"
]
}
41 changes: 41 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:7.10

# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4

working_directory: ~/repo

steps:
- checkout

# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-

- run: yarn install

- save_cache:
paths:
- node_modules
key: v1-dependencies-{{ checksum "package.json" }}

# run tests!
- run:
name: Jest Suite
command: yarn jest tests --ci --testResultsProcessor="jest-junit"
environment:
JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml"
12 changes: 11 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{
"extends": "airbnb-base"
"parser": "babel-eslint",
"extends": "airbnb-base",
"env": {
"browser": true,
"jest": true
},
"rules": {
"no-underscore-dangle": [1, {
"allowAfterThis": true
}]
}
}
11 changes: 11 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[ignore]

[include]

[libs]

[lints]

[options]

[strict]
147 changes: 147 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Jsonapi Orchestrator

Building better jsonapi-compliant code

[![BCH compliance](https://bettercodehub.com/edge/badge/MyJobGlasses/jsonapi-orchestrator?branch=master)](https://bettercodehub.com/)

[![CircleCI](https://circleci.com/gh/MyJobGlasses/jsonapi-orchestrator.svg?style=svg)](https://circleci.com/gh/MyJobGlasses/jsonapi-orchestrator)

# Why an orchestrator

Jsonapi Orchestrator aims to help you to deal with various stages of json:api based API interaction with (for now) redux-based libs and frameworks

Ever wanted to use...
- json:api communication with your APIs
- Redux based storage of resources retrieved from the API
- Side effects with redux-saga posting to your APIs
- Caching expensive requests to alleviate load on your APIs
- Create/update things in one request (Sidepost)
- Be more explicit about what you're doing (association/disassociation VS creation/deletion)

**Jsonapi Orchestrator is For You 🎁 🎉**

# Features & Compatibility oj Jsonapi Orchestrator

Our version Jsonapi Orchestrator is currently made to work with json:api 1.0

Apart from the basic Json:api specification, we aim to support interesting extensions (some of which are projected for json:api v1.1)

- ✅ [The Sideposting Draft](https://github.com/json-api/json-api/pull/1197)
- 🏗 Supporting `method` in PATCH to distinguish creation VS association
- 🏗 Support for temporary IDs or `lid`s (see https://github.com/json-api/json-api/pull/1244)
- 🏗 Support for easy caching of resources

# HOW TO

## Building json:api Requests

A json:api request can be categorized either as a READ request (GET request) or a WRITE request (POST/PATCH/PUT). At this point we're not sure how we want to perform DELETEs

Building those requests can be difficult because of the format of filters, sorting, and includes, in addition to specifying the endpoint. In addition, if you ever want to handle caching of requests, you would need to be ablte to specify metadata such as data freshness to decide later if you actually want to fire the query or reuse existing data already fetched.

Our Jsonapi builders will let you store this metadata so it can be reused with cache managers.

Here are some examples of basic read and writes

### APIs

You can manage a list of multiple APIs easily

```javascript
// my-apis.js
const EMPLOYEE_API_V1 = new API({
name: 'Employee API v1', url: 'https://employee.example.com/api/v1'
})
const EMPLOYEE_API_V2 = new API({
name: 'Employee API v2', url: process.env.EMPLOYEE_API_V1_URL
})
const METRICS_API_V2 = new API({
name: 'Metrics API v2', url: 'https://metrics.example.com/api/v2'
})

export default { METRICS_API_V2, EMPLOYEE_API_V1, EMPLOYEE_API_V2 }

// somewhere
import APIs from 'my-apis'
```

### Basic READ of single document

```javascript
employeeReader = new JsonapiResourceReader({ type: 'employee'})
employeeReader.sideload({ company: { admins: true } })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No possibilities to pass an array of string ? (like : ['company.admin', 'profil']) to be more flexible with sideloading building

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.sideload({ company: { admins: true }, profile: true }) marche

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

['company.admin', 'profil'] c'est une forme qui n'est pas flexible au niveau JS, alors qu'un Object on peut facilement le merge, rajouter des keys, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

employeeReader.filter({ in_vacation: false })
employeeReader.sort({ next_holiday_at: 'asc' })

requestBuilder = new JsonapiRequestBuilder({
resource: employeeReader,
path: '/employee/profile/:id',
params: { id: employeeId }
api: APIs.EMPLOYEE_API_V1,
})

yield put(requestBuilder.asReduxAction())
```

### Basic POST of single document with sideposting

Let's POST / Create an employee profile linked to an existing user account

```javascript

employeeWriter = new JsonapiResourceWriter({ type: 'employee/profile' })
# Note, since we did not provide any ID the HttpMethod is inferred to be POST
# Had we provided ({ type: 'employee/profile', id: 'cafebabe' }) it would be inferred to be a PATCH
resourceBuilder.setAttributes(...asReduxAction.attributes)

// Sidepost the user
userBuilder = new JsonapiResourceWriter({
type: 'user',
id: 'cafebabe',
attributes: currentUser.attributes
})
employeeWriter.sidepost({
relationship: 'user',
method: 'update', # other methods include create/associate/disassociate, refer to the sideposting draft
userBuilder
)

// Sidepost educations
educationBuilders = action.educations.forEach( (e) => {
educationWriter = new JsonapiResourceWriter({ type: 'education', attributes: e.attributes })
// e.method should return 'create' 'update' or 'destroy' or 'disassociate'
employeeWriter.sidepost({ relationship: 'educations', method: e.method, educationWriter)
})

requestBuilder = new JsonapiRequestBuilder({
builder: employeeWriter,
method: 'POST',
api: APIs.EMPLOYEE_API_V1,
attributes: existingEmployee.attributes
})
requestBuilder.endpointPath = '/employee/profile'
requestBuilder.addMeta({ invitation_token: invitation_token }) // SHould merge with existing metas

yield put(requestBuilder.asReduxAction())
```

You can find more advanced examples, including redux-saga based examples, [in the /examples folder](./examples/)

## Feeding the json:api responses to your Redux state

TODO by Dimitri

## Handle caching of requests

For a later version of JO

# Credits

- Dimitri DOMEY (Grand Bidou) @DOMEYD
- Cyril Duchon-Doris (Nickname-yet-to-be-found) @Startouf

# Contribute

Well, please
- Open an Issue describing your feature/bug/whatever addition you want to make,
- If you feel 💪 enough, open a PR with some commits and reference your issue number inside. If you're also using ZenHub (💕) you can attach your PR to your issue !
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing api documentation to explain how to use methods like sideload, filter, sort

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

La doc est au niveau de la def des méthodes cf src/builders/JsonapiResourceReader.js

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sinon cf #4

18 changes: 8 additions & 10 deletions examples/building_and_dispatching_requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ function * initEmployeePageView() {
method: 'GET',
path: '/employee/profile/:id',
params: { id: employeeId } // Should be smart substituted
api: APIs.HERMES // Helps set the base URL
api: APIs.EMPLOYEE_API // Helps set the base URL
})

yield put(requestBuilder.action)
yield put(requestBuilder.asReduxAction())
return yield race({
success: take(READ_EMPLOYEE_RESOURCE_SUCCESS),
error: take(READ_EMPLOYEE_RESOURCE_ERROR),
Expand All @@ -54,9 +54,7 @@ function * initEmployeePageView() {
*/

function * initConversationsPageView() {
conversationsReader = new JsonapiResourceListReader({
type: 'messaging/conversation',
})
conversationsReader = new JsonapiResourceListReader({ type: 'messaging/conversation' })

/* Data expiration
* On READ Lists, it should generate an index whose ID is a hash of
Expand All @@ -82,10 +80,10 @@ function * initConversationsPageView() {
resource: conversationsReader,
method: 'GET',
path: '/conversations',
api: APIs.HERMES
api: APIs.CONVERSATIONS_API_V1
})

yield put(requestBuilder.action)
yield put(requestBuilder.asReduxAction())
return yield race({
success: take(READ_CONVERSATION_RESOURCE_LIST_SUCCESS),
error: take(READ_CONVERSATION_RESOURCE_LIST_ERROR),
Expand All @@ -107,7 +105,7 @@ function* createProfessional(action) {

// Build the resource from the action / form
employeeWriter = new JsonapiResourceWriter({ type: 'employee/profile' })
resourceBuilder.setAttributes(...action.attributes)
resourceBuilder.setAttributes(...asReduxAction.attributes)

// Sidepost the user
userBuilder = new JsonapiResourceWriter({ type: 'user', id: '', attributes: currentUser.attributes })
Expand All @@ -123,13 +121,13 @@ function* createProfessional(action) {
requestBuilder = new JsonapiRequestBuilder({
builder: employeeWriter,
method: 'POST',
api: process.env.HERMES_API_URL,
api: APIs.EMPLOYEE_API,
attributes: existingEmployee.attributes
})
requestBuilder.endpointPath = '/employee/profile'
requestBuilder.addMeta({ invitation_token: invitation_token }) // SHould merge with existing metas

yield put(requestBuilder.action)
yield put(requestBuilder.asReduxAction())
return yield race({
success: take(WRITE_EMPLOYEE_RESOURCE_SUCCESS),
error: take(WRITE_EMPLOYEE_RESOURCE_ERROR),
Expand Down
21 changes: 18 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Jsonapi powered framework to harness the power of jsonapi with redux",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest --notify --watch"
},
"repository": {
"type": "git",
Expand All @@ -21,12 +21,27 @@
},
"homepage": "https://github.com/MyJobGlasses/jsonapi-orchestrator#readme",
"dependencies": {
"json-api-normalizer": "^0.4.10",
"lodash": "^4.17.10",
"redux": "^3.7.2",
"redux-saga": "^0.16.0"
"redux-saga": "^0.16.0",
"uuid": "^3.2.1"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.6",
"babel-jest": "^22.4.3",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
"babel-preset-flow": "^6.23.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.9.0"
"eslint-plugin-import": "^2.11.0",
"flow-bin": "^0.71.0",
"jest": "^22.4.3",
"jest-junit": "^4.0.0",
"redux-saga-test-plan": "^3.6.0",
"regenerator-runtime": "^0.11.1"
}
}
Loading