Skip to content

Commit 1f69f74

Browse files
committed
Starting as Open Source
0 parents  commit 1f69f74

File tree

9 files changed

+4039
-0
lines changed

9 files changed

+4039
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/node_modules/
2+
3+
# IntelliJ
4+
/.idea/
5+
# Emacs
6+
*~

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0 - YYYY-MM-DD
2+
3+
First public version.

CONTRIBUTING.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Guidance on how to contribute
2+
3+
> All contributions to this project will be released under the MIT License.
4+
> By submitting a pull request or filing a bug, issue, or
5+
> feature request, you are agreeing to comply with this waiver of copyright interest.
6+
> Details can be found in [LICENSE](LICENSE).
7+
8+
9+
There are two primary ways to help:
10+
- Using the issue tracker, and
11+
- Changing the code-base.
12+
13+
14+
## Using the issue tracker
15+
16+
Use the issue tracker to suggest feature requests, report bugs, and ask questions.
17+
This is also a great way to connect with the developers of the project as well
18+
as others who are interested in this solution.
19+
20+
Use the issue tracker to find ways to contribute. Find a bug or a feature, mention in
21+
the issue that you will take on that effort, then follow the _Changing the code-base_
22+
guidance below.
23+
24+
25+
## Changing the code-base
26+
27+
Generally speaking, you should fork this repository, make changes in your
28+
own fork, and then submit a pull-request. All new code should have associated unit
29+
tests that validate implemented features and the presence or lack of defects.
30+
Additionally, the code should follow any stylistic and architectural guidelines
31+
prescribed by the project. In the absence of such guidelines, mimic the styles
32+
and patterns in the existing code-base.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Sveriges Television AB
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.

README.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
2+
# graphql-defragmentizer
3+
4+
*Library for building [GraphQL](https://github.com/graphql/graphql-js) queries
5+
from fragments in JavaScript.*
6+
7+
It combines the main query, the fragments and their sub-fragments into one
8+
valid query. Useful for React apps, if you want each component to specify
9+
its own data requirements, but still want to run a single GraphQL query.
10+
11+
It can be used on React components in a way similar to [`prop-types`](https://github.com/facebook/prop-types)
12+
declarations.
13+
14+
There's no dependency on React and can be used by all kinds of GraphQL clients.
15+
16+
### Status
17+
18+
We're using it in production at [SVT](https://www.svt.se/opensource/) and will likely fix problems if we encounter any.
19+
[Contributions](CONTRIBUTING.md) are welcome!
20+
21+
## API
22+
23+
### `` createQuery`...` ``
24+
25+
Creates a GraphQL `Document` from a template string.
26+
27+
Values are usually fragments (created by `createFragment`). Fragments must be placed where
28+
you would normally place the _name_ of a fragment.
29+
30+
If the values aren't fragments, they'll be appended to the query (with `String()` conversion).
31+
32+
The query and the fragments (and their sub-fragments) will be recursively combined into a
33+
valid GraphQL query, combining any duplicate fragments into one.
34+
35+
If you don't use fragments, it's mostly identical to the `` gql`...` `` function
36+
of [`graphql-tag`](https://github.com/apollographql/graphql-tag).
37+
38+
Usage:
39+
40+
const query = createQuery`
41+
query MyQuery {
42+
someQuery {
43+
... ${myFragment}
44+
}
45+
}`;
46+
47+
### `` createFragment`...` ``
48+
49+
Creates a fragment from a template string, for use by `createQuery` or a parent `createFragment` call.
50+
51+
The template string must be a valid GraphQL fragment definition, but without the _name_. It can have
52+
other fragments as values.
53+
54+
If the values aren't fragments, they'll be appended to the fragment (with `String()` conversion).
55+
56+
Usage:
57+
58+
const fragment = createFragment`
59+
... on MyThing {
60+
name
61+
friends {
62+
... ${friendFragment}
63+
}
64+
}`;
65+
66+
## Example with React
67+
68+
Showing a latest news list on a web page:
69+
70+
Main.js:
71+
72+
import { createQuery } from 'graphql-defragmentizer';
73+
import { graphql } from 'react-apollo';
74+
75+
const MainQuery = createQuery`
76+
query MainQuery {
77+
main {
78+
latestNews {
79+
...${LatestNews.fragments.latestNews}
80+
}
81+
mainTitle
82+
}
83+
}
84+
`;
85+
86+
function Main({ mainTitle, latestNews }) {
87+
return
88+
<div>
89+
<h1>{mainTitle}</h1>
90+
<p><LatestNews latestNews={latestNews} /></p>
91+
</div>
92+
}
93+
94+
export default graphql(MainQuery,
95+
props: props => ({
96+
latestNews: props.data.main.latestNews,
97+
mainTitle: props.data.main.mainTitle
98+
})
99+
)(Main);
100+
101+
LatestNews.js:
102+
103+
import { createFragment } from 'graphql-defragmentizer';
104+
105+
export default function LatestNews({ latestNews }) {
106+
return latestNews.map((item) => {
107+
<a href={url}>{title}</a>
108+
}
109+
}
110+
111+
LatestNews.fragments = {
112+
latestNews: createFragment`
113+
... on LatestNewsItem {
114+
title
115+
url
116+
}
117+
`
118+
};
119+
120+
This would build a query like this:
121+
122+
query MainQuery {
123+
main {
124+
latestNews {
125+
...fragment_fevfrc
126+
}
127+
mainTitle
128+
}
129+
}
130+
131+
fragment fragment_fevfrc on LatestNewsItem {
132+
title
133+
url
134+
}
135+
136+
The name "fragment_fevfrc" is created by hashing the contents and type of the
137+
fragment, so two identical fragments will be merged into one.
138+
139+
## License
140+
141+
Copyright (c) 2019 Sveriges Television AB.
142+
143+
graphql-defragmentizer is released under the [MIT License](LICENSE).
144+
145+
## Getting involved
146+
147+
Feel free to issue pull requests or file issues. For more details, see [CONTRIBUTING](CONTRIBUTING.md)
148+
149+
## Primary Maintainer
150+
151+
Anders Kindberg [https://github.com/ghostganz](https://github.com/ghostganz)
152+
153+
## Credits
154+
155+
Original implementation by [Emil Broman](https://github.com/emilniklas).

index.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* eslint no-bitwise: 0 */
2+
3+
const { parse } = require('graphql/language/parser');
4+
5+
function nonSecureHash(s) {
6+
let hash = 0;
7+
for (let i = 0; i < s.length; i++) {
8+
let char = s.charCodeAt(i);
9+
const mixed = (hash << 5) - hash;
10+
hash = mixed + char;
11+
hash = hash & hash; // Convert to 32bit integer
12+
}
13+
return hash;
14+
}
15+
16+
class NotFragment {
17+
constructor(value) {
18+
this.name = String(value);
19+
}
20+
21+
collectFragments(acc) {
22+
return acc;
23+
}
24+
}
25+
26+
class Fragment {
27+
constructor(segments, childFragments) {
28+
this.segments = segments;
29+
this.childFragments = childFragments.map(f => {
30+
if (!(f instanceof Fragment)) {
31+
return new NotFragment(f);
32+
}
33+
return f;
34+
});
35+
36+
const hash = nonSecureHash([...segments, ...this.childNames()].join(''));
37+
this.name = `fragment_${Math.abs(hash).toString(36)}`;
38+
}
39+
40+
format() {
41+
return `fragment ${this.name} ${interleaveStrings(
42+
this.segments,
43+
this.childNames()
44+
).replace('...', '')}`;
45+
}
46+
47+
childNames() {
48+
return this.childFragments.map(f => f.name);
49+
}
50+
51+
collectFragments(acc) {
52+
const children = this.childFragments.reduce(
53+
(acc, f) => f.collectFragments(acc),
54+
acc
55+
);
56+
return [this, ...children];
57+
}
58+
}
59+
60+
function createFragment(segments, ...childFragments) {
61+
return new Fragment(segments, childFragments);
62+
}
63+
64+
function interleaveStrings(array1, array2) {
65+
return array1.map((string, i) => string + (array2[i] || '')).join('');
66+
}
67+
68+
function uniqueFragments(collectedFragments) {
69+
const uniqueFragmentMap = {};
70+
for (const fragment of collectedFragments) {
71+
uniqueFragmentMap[fragment.name] = fragment;
72+
}
73+
return Object.keys(uniqueFragmentMap)
74+
.sort()
75+
.map(name => uniqueFragmentMap[name]);
76+
}
77+
78+
function createQuery(segments, ...childFragments) {
79+
const root = new Fragment([], childFragments);
80+
const collectedFragments = root.collectFragments([]);
81+
collectedFragments.shift(); // Skip root
82+
83+
const fragmentsString = uniqueFragments(collectedFragments)
84+
.map(fragment => fragment.format())
85+
.join('\n');
86+
const clonedSegments = segments.slice();
87+
clonedSegments[clonedSegments.length - 1] += fragmentsString;
88+
return parse(interleaveStrings(clonedSegments, root.childNames()));
89+
}
90+
91+
module.exports = {
92+
createQuery,
93+
createFragment
94+
};

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "graphql-defragmentizer",
3+
"version": "1.0.0",
4+
"description": "Library for building GraphQL queries from fragments. Goes nicely with React.",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "jest test.js"
8+
},
9+
"keywords": [
10+
"graphql",
11+
"react",
12+
"apollo"
13+
],
14+
"author": "Sveriges Television AB (https://www.svt.se/opensource/)",
15+
"license": "MIT",
16+
"dependencies": {
17+
"graphql": "^14.4.2"
18+
},
19+
"devDependencies": {
20+
"jest": "^24.9.0"
21+
}
22+
}

0 commit comments

Comments
 (0)