Skip to content

Commit

Permalink
graphql: Added new package providing a graphql based backend
Browse files Browse the repository at this point in the history
  • Loading branch information
maxott committed Sep 13, 2019
1 parent 65ef062 commit 0b0dc40
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist
build
lib
**/lib
yarn-error.log
14 changes: 14 additions & 0 deletions packages/graphql/.babelrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { NODE_ENV, BABEL_ENV } = process.env
const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs'
const loose = true

module.exports = {
presets: [['@babel/env', { loose, modules: false }]],
plugins: [
['@babel/proposal-decorators', { legacy: true }],
['@babel/proposal-object-rest-spread', { loose }],
'@babel/transform-react-jsx',
cjs && ['@babel/transform-modules-commonjs', { loose }],
['@babel/transform-runtime', { useESModules: !cjs }],
].filter(Boolean),
}
8 changes: 8 additions & 0 deletions packages/graphql/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "airbnb",
"rules": {
"react/jsx-props-no-spreading": "off",
"import/prefer-default-export": "off",
"react/prop-types": "off",
},
}
96 changes: 96 additions & 0 deletions packages/graphql/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"name": "@pihanga/graphql",
"version": "0.1.0",
"description": "Implemnts a graphql based backend for local state management",
"homepage": "https://github.com/n1analytics/pihanga#readme",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/n1analytics/pihanga.git"
},
"bugs": "https://github.com/n1analytics/pihanga/issues",
"keywords": [
"graphql",
"framework"
],
"contributors": [
"Max Ott <[email protected]> (http://linkedin.com/in/max-ott)",
"Pihanga Team"
],
"license": "MIT",
"unpkg": "dist/pihanga-graphql.min.js",
"module": "lib/index.js",
"files": [
"dist",
"lib",
"src"
],
"dependencies": {
"apollo-cache-inmemory": "^1.6.0",
"apollo-client": "^2.6.0",
"apollo-link": "^1.2.0",
"apollo-link-error": "^1.1.0",
"apollo-link-http": "^1.5.0",
"graphql": "^14.5.0",
"graphql-tag": "^2.10.0"
},
"peerDependencies": {
"@pihanga/core": "^0.3.0"
},
"devDependencies": {
"@pihanga/core": "^0.3.0",
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.0",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/plugin-proposal-object-rest-spread": "^7.5.0",
"@babel/plugin-transform-react-display-name": "^7.2.0",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/plugin-transform-runtime": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"babel-core": "^6.23.0",
"babel-eslint": "^10.0.2",
"babel-jest": "^24.9.0",
"codecov": "^3.5.0",
"cross-env": "^5.2.0",
"cross-spawn": "^6.0.5",
"es3ify": "^0.2.0",
"eslint": "^6.1.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-plugin-import": "^2.18.0",
"eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-react": "^7.14.0",
"eslint-plugin-react-hooks": "^1.7.0",
"glob": "^7.1.4",
"jest": "^24.9.0",
"jest-dom": "^3.5.0",
"npm-run": "^5.0.1",
"prettier": "^1.18.2",
"rimraf": "^2.7.1",
"rollup": "^1.19.4",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.4",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^4.0.4",
"semver": "^5.7.1"
},
"scripts": {
"build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib && touch lib/DO_NOT_EDIT",
"build:es": "babel src --out-dir dist/es",
"build:umd": "cross-env NODE_ENV=development rollup -c -o dist/pihanga-core.js",
"build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/pihanga-core.min.js",
"xxx-build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min",
"build": "npm run build:commonjs && npm run build:es",
"clean": "rimraf lib dist coverage",
"format": "prettier --write \"{src,test}/**/*.{js,ts}\" index.d.ts \"docs/**/*.md\"",
"lint": "eslint src test/utils test/components",
"prepare": "npm run clean && npm run build",
"pretest": "npm run lint",
"test": "node ./test/run-tests.js",
"coverage": "codecov",
"publish": "npm publish --tag latest --access=public",
"x-prepublishOnly": "npm run build",
"x-prepublish": "yarn run lint && yarn run test && yarn run build"
},
"gitHead": "aa43c27ff6344d6ead75a83e1d23ff69a7f4feb4"
}
61 changes: 61 additions & 0 deletions packages/graphql/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import nodeResolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'
import replace from 'rollup-plugin-replace'
import commonjs from 'rollup-plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
import pkg from './package.json'

const env = process.env.NODE_ENV

const config = {
input: 'src/index.js',
external: Object.keys(pkg.peerDependencies || {}),
output: {
format: 'umd',
name: 'ReactRedux',
globals: {
react: 'React',
redux: 'Redux'
}
},
plugins: [
nodeResolve({
mainFields: ['module', 'main'],
//jsnext: true,
extensions: ['.js', '.jsx', '.json']
}),
babel({
exclude: '**/node_modules/**',
runtimeHelpers: true
}),
replace({
'process.env.NODE_ENV': JSON.stringify(env)
}),
commonjs({
namedExports: {
'node_modules/react-is/index.js': [
'isValidElementType',
'isContextConsumer'
],
'node_modules/@pihanga/core/lib/index.js': [
'dispatch'
]
}
})
]
}

if (env === 'production') {
config.plugins.push(
terser({
compress: {
pure_getters: true,
unsafe: true,
unsafe_comps: true,
warnings: false
}
})
)
}

export default config
145 changes: 145 additions & 0 deletions packages/graphql/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { ApolloLink } from 'apollo-link';
import { registerActions, dispatch, dispatchFromReducer } from '@pihanga/core';
import gql from 'graphql-tag';
import { visit } from 'graphql';


const Domain = 'GRAPHQL';
export const ACTION_TYPES = registerActions(Domain,
['QUERY_SUBMITTED', 'QUERY_RESULT', 'QUERY_ERROR', 'QL_ERROR', 'NETWORK_ERROR', 'NOT_INITIALISED']);

let client;
let registerReducer;

/**
* Standard pihanga init function to initialize an Apollo client.
*
* Expects all relevant configuration options to be found in
* `register.environment.GRAPHQL_URI` (needs most likely more).
*
* @param {*} register
*/
export function init(register) {
const uri = register.environment.GRAPHQL_URI || '/graphql';
client = createClient(uri);
registerReducer = register.reducer;
}

/**
* Register a GraphQL query and all related processing steps.
*
* The argument to this function is a map with the following key/value pairs.
*
* * query: The GraphQL query to be issued
* * trigger: The Redux action type to potentially trigger this query
* * request: A function expected to return a map of the variable assignmets for this query.
* If undefined is returned, no query is issued. The function is called with paramters:
* triggering action, current redux state, and a map describing the declared variables
* and their respective types.
* * reply: A function expected to return a possibly updated redux state in respond to a
* successful query result. The function is called witht paramters: the current redux state
* and the 'data' element of the returned query.
* * error: An optional function expected to return a possibly updated redux state in respond to a
* failed query. The function is called witht paramters: the current redux state
* and the error condition.
*
*
* @param {*} opts
*/
export function registerQuery({query, trigger, request, reply, error}) {
const {name, variables, doc} = parseQuery(query);
if (!trigger) {
throw Error('Missing "trigger"');
}
if (!request) {
throw Error('Missing "request"');
}
if (!reply) {
throw Error('Missing "reply"');
}

const submitType = `${ACTION_TYPES.QUERY_SUBMITTED}:${name}`;
const resultType = `${ACTION_TYPES.QUERY_RESULT}:${name}`;
const errorType = `${ACTION_TYPES.QUERY_ERROR}:${name}`;

registerReducer(trigger, (state, action) => {
const vars = request(action, state, variables);
if (vars) {
runQuery(name, doc, vars, resultType, errorType);
dispatchFromReducer({type: submitType, queryID: name, vars});
}
return state;
});

registerReducer(resultType, (state, action) => {
return reply(state, action.data);
});

if (error) {
registerReducer(errorType, (state, action) => {
return reply(state, error);
});
}
}

function parseQuery(query) {
const doc = gql(query);
let name = null;
const variables = {};
const visitor = {
enter: {
OperationDefinition: (node) => {
name = node.name.value;
return undefined;
},
VariableDefinition: (node) => {
const type = node.type.name.value;
const name = node.variable.name.value;
const defValue = node.defaultValue ? node.defaultValue.value : null;
variables[name] = {name, type, defValue};
return undefined;
},
}
};
visit(doc, visitor);
return {name, variables, doc, query};
}

function runQuery(queryID, query, variables, resultType, errorType) {
if (client === null) {
dispatch({ type: ACTION_TYPES.NOT_INITIALISED, queryID });
}
client.query({query, variables})
.then(data => {
dispatch({ type: resultType, queryID, data: data.data });
})
.catch(error => {
dispatch({ type: errorType, queryID, error });
});
}

function createClient(uri) {
return new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
dispatch({ type: ACTION_TYPES.QL_ERROR, message, locations, path });
});
}
if (networkError) {
dispatch({ type: ACTION_TYPES.NETWORK_ERROR, networkError });
}
}),
new HttpLink({
uri,
// credentials: 'same-origin'
})
]),
cache: new InMemoryCache()
});
}

0 comments on commit 0b0dc40

Please sign in to comment.