@ardatan/relay-compiler
Version:
Fork of `relay-compiler`
255 lines (209 loc) • 6.46 kB
JavaScript
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
// flowlint ambiguous-object-type:error
;
var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');
var _objectSpread2 = _interopRequireDefault(require('@babel/runtime/helpers/objectSpread2'));
var _toConsumableArray2 = _interopRequireDefault(
require('@babel/runtime/helpers/toConsumableArray'),
);
var IRTransformer = require('../core/IRTransformer');
var _require = require('../core/CompilerError'),
createCompilerError = _require.createCompilerError,
createUserError = _require.createUserError;
var cachesByNode = new Map();
function clientExtensionTransform(context) {
cachesByNode = new Map();
return IRTransformer.transform(context, {
Fragment: traverseDefinition,
Root: traverseDefinition,
SplitOperation: traverseDefinition,
});
}
function traverseDefinition(node) {
// $FlowFixMe[incompatible-use]
var compilerContext = this.getContext();
var schema = compilerContext.getSchema();
var rootType;
switch (node.kind) {
case 'Root':
switch (node.operation) {
case 'query':
rootType = schema.getQueryType();
break;
case 'mutation':
rootType = schema.getMutationType();
break;
case 'subscription':
rootType = schema.getSubscriptionType();
break;
default:
node.operation;
}
break;
case 'SplitOperation':
if (!schema.isServerType(node.type)) {
throw createUserError(
'ClientExtensionTransform: SplitOperation (@module) can be created ' +
'only for fragments that defined on a server type',
[node.loc],
);
}
rootType = node.type;
break;
case 'Fragment':
rootType = node.type;
break;
default:
node;
}
if (rootType == null) {
throw createUserError(
'ClientExtensionTransform: Expected the type of `'.concat(
node.name,
'` to have been defined in the schema. Make sure both server and ',
) + 'client schema are up to date.',
[node.loc],
);
}
return traverseSelections(node, compilerContext, rootType);
}
function traverseSelections(node, compilerContext, parentType) {
// $FlowFixMe[escaped-generic]
var nodeCache = cachesByNode.get(node);
if (nodeCache == null) {
nodeCache = new Map(); // $FlowFixMe[escaped-generic]
cachesByNode.set(node, nodeCache);
}
var result = nodeCache.get(parentType);
if (result != null) {
/* $FlowFixMe[incompatible-return] - TODO: type IRTransformer to allow
* changing result type */
return result;
}
var schema = compilerContext.getSchema();
var clientSelections = [];
var serverSelections = cowMap(node.selections, function (selection) {
switch (selection.kind) {
case 'ClientExtension':
throw createCompilerError(
'Unexpected ClientExtension node before ClientExtensionTransform',
[selection.loc],
);
case 'Condition':
case 'Defer':
case 'InlineDataFragmentSpread':
case 'ModuleImport':
case 'Stream':
return traverseSelections(selection, compilerContext, parentType);
case 'ScalarField':
if (
schema.isClientDefinedField(
schema.assertCompositeType(schema.getRawType(parentType)),
selection,
)
) {
clientSelections.push(selection);
return null;
} else {
return selection;
}
case 'LinkedField': {
if (
schema.isClientDefinedField(
schema.assertCompositeType(schema.getRawType(parentType)),
selection,
)
) {
clientSelections.push(selection);
return null;
}
return traverseSelections(selection, compilerContext, selection.type);
}
case 'InlineFragment': {
var isClientType = !schema.isServerType(selection.typeCondition);
if (isClientType) {
clientSelections.push(selection);
return null;
}
return traverseSelections(selection, compilerContext, selection.typeCondition);
}
case 'FragmentSpread': {
return selection;
}
default:
selection;
throw createCompilerError(
'ClientExtensionTransform: Unexpected selection of kind `'.concat(selection.kind, '`.'),
[selection.loc],
);
}
});
if (clientSelections.length === 0) {
if (serverSelections === node.selections) {
result = node;
} else {
result = (0, _objectSpread2['default'])(
(0, _objectSpread2['default'])({}, node),
{},
{
selections: serverSelections,
},
);
}
} else {
result = (0, _objectSpread2['default'])(
(0, _objectSpread2['default'])({}, node),
{},
{
selections: [].concat((0, _toConsumableArray2['default'])(serverSelections), [
// Group client fields under a single ClientExtension node
{
kind: 'ClientExtension',
loc: node.loc,
metadata: null,
selections: clientSelections,
},
]),
},
);
} // $FlowFixMe[escaped-generic]
nodeCache.set(parentType, result);
/* $FlowFixMe[incompatible-return] - TODO: type IRTransformer to allow
* changing result type */
return result;
}
/**
* Maps an array with copy-on-write semantics.
* `null` return values from the map function are removals.
*/
function cowMap(selections, f) {
for (var i = 0; i < selections.length; i++) {
var prevSelection = selections[i];
var nextSelection = f(prevSelection);
if (prevSelection !== nextSelection) {
var result = selections.slice(0, i);
if (nextSelection != null) {
result.push(nextSelection);
}
for (var j = i + 1; j < selections.length; j++) {
var innerNextSelection = f(selections[j]);
if (innerNextSelection != null) {
result.push(innerNextSelection);
}
}
return result;
}
} // nothing changed, return original
return selections;
}
module.exports = {
transform: clientExtensionTransform,
};