Skip to content

Commit fdd4288

Browse files
author
v1rtl
committed
Initial commit
1 parent cff8450 commit fdd4288

File tree

2 files changed

+171
-1
lines changed

2 files changed

+171
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# graphql-tag
2-
🦕 Deno port of `graphql-tag` library
2+
3+
🦕 Deno port of `graphql-tag` library.

mod.ts

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { parse } from 'https://deno.land/x/graphql_deno@v15.0.0/mod.ts'
2+
3+
// Strip insignificant whitespace
4+
// Note that this could do a lot more, such as reorder fields etc.
5+
const normalize = (x: string) => x.replace(/[\s,]+/g, ' ').trim()
6+
7+
// A map docString -> graphql document
8+
let docCache: any = {}
9+
10+
// A map fragmentName -> [normalized source]
11+
let fragmentSourceMap: any = {}
12+
13+
function cacheKeyFromLoc(loc: any) {
14+
return normalize(loc.source.body.substring(loc.start, loc.end))
15+
}
16+
17+
// For testing.
18+
export function resetCaches() {
19+
docCache = {}
20+
fragmentSourceMap = {}
21+
}
22+
23+
// Take a unstripped parsed document (query/mutation or even fragment), and
24+
// check all fragment definitions, checking for name->source uniqueness.
25+
// We also want to make sure only unique fragments exist in the document.
26+
let printFragmentWarnings = true
27+
function processFragments(ast: any) {
28+
const astFragmentMap: any = {}
29+
const definitions: any[] = []
30+
31+
for (let i = 0; i < ast.definitions.length; i++) {
32+
const fragmentDefinition = ast.definitions[i]
33+
34+
if (fragmentDefinition.kind === 'FragmentDefinition') {
35+
const fragmentName = fragmentDefinition.name.value
36+
const sourceKey = cacheKeyFromLoc(fragmentDefinition.loc)
37+
38+
// We know something about this fragment
39+
if (fragmentSourceMap.hasOwnProperty(fragmentName) && !fragmentSourceMap[fragmentName][sourceKey]) {
40+
// this is a problem because the app developer is trying to register another fragment with
41+
// the same name as one previously registered. So, we tell them about it.
42+
if (printFragmentWarnings) {
43+
console.warn(
44+
'Warning: fragment with name ' +
45+
fragmentName +
46+
' already exists.\n' +
47+
'graphql-tag enforces all fragment names across your application to be unique; read more about\n' +
48+
'this in the docs: http://dev.apollodata.com/core/fragments.html#unique-names'
49+
)
50+
}
51+
52+
fragmentSourceMap[fragmentName][sourceKey] = true
53+
} else if (!fragmentSourceMap.hasOwnProperty(fragmentName)) {
54+
fragmentSourceMap[fragmentName] = {}
55+
fragmentSourceMap[fragmentName][sourceKey] = true
56+
}
57+
58+
if (!astFragmentMap[sourceKey]) {
59+
astFragmentMap[sourceKey] = true
60+
definitions.push(fragmentDefinition)
61+
}
62+
} else {
63+
definitions.push(fragmentDefinition)
64+
}
65+
}
66+
67+
ast.definitions = definitions
68+
return ast
69+
}
70+
71+
export function disableFragmentWarnings() {
72+
printFragmentWarnings = false
73+
}
74+
75+
function stripLoc(doc: any, removeLocAtThisLevel: any) {
76+
let docType = Object.prototype.toString.call(doc)
77+
78+
if (docType === '[object Array]') {
79+
return doc.map(function (d: any) {
80+
return stripLoc(d, removeLocAtThisLevel)
81+
})
82+
}
83+
84+
if (docType !== '[object Object]') {
85+
throw new Error('Unexpected input.')
86+
}
87+
88+
// We don't want to remove the root loc field so we can use it
89+
// for fragment substitution (see below)
90+
if (removeLocAtThisLevel && doc.loc) {
91+
delete doc.loc
92+
}
93+
94+
// https://github.com/apollographql/graphql-tag/issues/40
95+
if (doc.loc) {
96+
delete doc.loc.startToken
97+
delete doc.loc.endToken
98+
}
99+
100+
const keys = Object.keys(doc)
101+
let key
102+
let value
103+
let valueType
104+
105+
for (key in keys) {
106+
if (keys.hasOwnProperty(key)) {
107+
value = doc[keys[key]]
108+
valueType = Object.prototype.toString.call(value)
109+
110+
if (valueType === '[object Object]' || valueType === '[object Array]') {
111+
doc[keys[key]] = stripLoc(value, true)
112+
}
113+
}
114+
}
115+
116+
return doc
117+
}
118+
119+
let experimentalFragmentVariables = false
120+
121+
function parseDocument(doc: string) {
122+
const cacheKey = normalize(doc)
123+
124+
if (docCache[cacheKey]) {
125+
return docCache[cacheKey]
126+
}
127+
128+
let parsed = parse(doc, {
129+
experimentalFragmentVariables
130+
})
131+
if (!parsed || parsed.kind !== 'Document') {
132+
throw new Error('Not a valid GraphQL document.')
133+
}
134+
135+
// check that all "new" fragments inside the documents are consistent with
136+
// existing fragments of the same name
137+
parsed = processFragments(parsed)
138+
parsed = stripLoc(parsed, false)
139+
docCache[cacheKey] = parsed
140+
141+
return parsed
142+
}
143+
144+
export function enableExperimentalFragmentletiables() {
145+
experimentalFragmentVariables = true
146+
}
147+
148+
export function disableExperimentalFragmentVariables() {
149+
experimentalFragmentVariables = false
150+
}
151+
152+
// XXX This should eventually disallow arbitrary string interpolation, like Relay does
153+
export function gql(...args: any[]) {
154+
// We always get literals[0] and then matching post literals for each arg given
155+
const literals = args[0]
156+
let result = typeof literals === 'string' ? literals : literals[0]
157+
158+
for (let i = 1; i < args.length; i++) {
159+
if (args[i] && args[i].kind && args[i].kind === 'Document') {
160+
result += args[i].loc.source.body
161+
} else {
162+
result += args[i]
163+
}
164+
165+
result += literals[i]
166+
}
167+
168+
return parseDocument(result)
169+
}

0 commit comments

Comments
 (0)