@grapes-agency/apollo-link-local-schema
Advanced tools
Comparing version 1.0.0-alpha.11 to 1.0.0-alpha.12
@@ -5,2 +5,9 @@ # Changelog | ||
## [1.0.0-alpha.12](https://github.com/grapes-agency/apollo-link-local-schema/compare/v1.0.0-alpha.11...v1.0.0-alpha.12) (2020-10-20) | ||
### Features | ||
* preparation for local federation ([3ad7e33](https://github.com/grapes-agency/apollo-link-local-schema/commit/3ad7e33f1fb43a8206a03fe5b16ca6019190db4f)) | ||
## [1.0.0-alpha.11](https://github.com/grapes-agency/apollo-link-local-schema/compare/v1.0.0-alpha.10...v1.0.0-alpha.11) (2020-10-12) | ||
@@ -7,0 +14,0 @@ |
415
index.cjs.js
@@ -7,5 +7,71 @@ 'use strict'; | ||
var LocalState = require('@apollo/client/core/LocalState'); | ||
var utilities = require('@apollo/client/utilities'); | ||
var tslib = require('tslib'); | ||
var graphql = require('graphql'); | ||
var utilities = require('@apollo/client/utilities'); | ||
const fixTypeDefs = (serviceName, typeDefs) => { | ||
for (const definition of typeDefs.definitions) { | ||
if (definition.kind === 'ObjectTypeDefinition') { | ||
switch (definition.name.value) { | ||
case 'Query': { | ||
throw new Error(`Federated links cannot define type Query in service "${serviceName}", only extend it`); | ||
} | ||
case 'Mutation': { | ||
throw new Error(`Federated links cannot define type Mutation in service "${serviceName}", only extend it`); | ||
} | ||
case 'Subscription': { | ||
throw new Error(`Federated links cannot define type Subscription in service "${serviceName}", only extend it`); | ||
} | ||
} | ||
} | ||
if (definition.kind === 'ObjectTypeExtension') { | ||
switch (definition.name.value) { | ||
case 'Query': | ||
case 'Mutation': | ||
case 'Subscription': { | ||
Object.assign(definition, { | ||
kind: 'ObjectTypeDefinition', | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
class LocalFederationSupport { | ||
constructor(name) { | ||
this.name = name; | ||
// | ||
} | ||
initTypeDefs(typeDefs) { | ||
fixTypeDefs(this.name, typeDefs); | ||
this.typeDefs = typeDefs; | ||
} | ||
initResovlers(resolvers) { | ||
if (!resolvers.Query) { | ||
resolvers.Query = {}; | ||
} | ||
resolvers.Query.__resolveType = (_root, { rootData, resolveData }, context, info) => { | ||
var _a; | ||
if (resolveData) { | ||
const { __typename } = resolveData, rest = tslib.__rest(resolveData, ["__typename"]); | ||
if (!__typename) { | ||
console.warn(`Missing __typename for resolving`); | ||
return null; | ||
} | ||
const resolver = (_a = resolvers[__typename]) === null || _a === void 0 ? void 0 : _a.__resolveReference; | ||
if (!resolver) { | ||
console.warn(`Missing __resolveReference implementation for type "${__typename}"`); | ||
return null; | ||
} | ||
return resolver(rest, {}, context, info); | ||
} | ||
return rootData; | ||
}; | ||
} | ||
getType(name) { | ||
return this.typeDefs.definitions.find(definition => { var _a; return 'name' in definition && ((_a = definition.name) === null || _a === void 0 ? void 0 : _a.value) === name; }); | ||
} | ||
} | ||
const isObjectTypeDefinition = (node) => node.kind === 'ObjectTypeDefinition'; | ||
@@ -21,2 +87,3 @@ const isInterfaceTypeDefinition = (node) => node.kind === 'InterfaceTypeDefinition'; | ||
const isFragmentDefinition = (node) => node.kind === 'FragmentDefinition'; | ||
const isSchemaDefinition = (node) => node.kind === 'SchemaDefinition'; | ||
const isTypeDefinition = (node) => [ | ||
@@ -70,11 +137,11 @@ 'ScalarTypeDefinition', | ||
if (typeDefinitions.length > 1) { | ||
throw new Error(`Multiple type definitions for type ${name}`); | ||
throw new Error(`Multiple type definitions for type "${name}"`); | ||
} | ||
if (typeDefinitions.length === 0) { | ||
const [typeExtension] = typeExtensions; | ||
typeDefinitions.push(Object.assign(Object.assign({}, typeExtension), { kind: 'ObjectTypeDefinition' })); | ||
const [typeExtension, ...restTypeExtensions] = typeExtensions; | ||
documentDefinitions.push(Object.assign(Object.assign({}, typeExtension), { fields: restTypeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...typeExtension.fields]) })); | ||
return; | ||
} | ||
const [definition] = typeDefinitions; | ||
const fields = typeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...definition.fields]); | ||
documentDefinitions.push(Object.assign(Object.assign({}, definition), { fields })); | ||
documentDefinitions.push(Object.assign(Object.assign({}, definition), { fields: typeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...definition.fields]) })); | ||
}); | ||
@@ -87,111 +154,2 @@ return { | ||
const splitDocumentByDirective = (document, splitDirective) => { | ||
const hasLocalDirective = (field) => { var _a; return Boolean((_a = field.directives) === null || _a === void 0 ? void 0 : _a.find(directive => directive.name.value === splitDirective)); }; | ||
const localParentFields = new Set(); | ||
const usedFragments = new Set(); | ||
const localDocument = graphql.visit(document, { | ||
OperationDefinition: { | ||
leave(operationDefinition) { | ||
if (operationDefinition.selectionSet.selections.length === 0) { | ||
return null; | ||
} | ||
}, | ||
}, | ||
FragmentSpread(fragmentSpread) { | ||
usedFragments.add(fragmentSpread.name.value); | ||
}, | ||
FragmentDefinition: { | ||
enter(fragmentDefinition) { | ||
if (usedFragments.has(fragmentDefinition.name.value)) { | ||
localParentFields.add(fragmentDefinition); | ||
} | ||
}, | ||
leave(fragmentDefinition) { | ||
localParentFields.delete(fragmentDefinition); | ||
}, | ||
}, | ||
Field: { | ||
enter(field) { | ||
if (hasLocalDirective(field)) { | ||
localParentFields.add(field); | ||
return; | ||
} | ||
if (localParentFields.size > 0 || utilities.hasDirectives([splitDirective], field)) { | ||
return; | ||
} | ||
return null; | ||
}, | ||
leave(field) { | ||
localParentFields.delete(field); | ||
}, | ||
}, | ||
}); | ||
usedFragments.clear(); | ||
const nonLocalDocument = graphql.visit(document, { | ||
OperationDefinition: { | ||
leave(operationDefinition) { | ||
if (operationDefinition.selectionSet.selections.length === 0) { | ||
return null; | ||
} | ||
}, | ||
}, | ||
Field(field) { | ||
if (hasLocalDirective(field)) { | ||
return null; | ||
} | ||
}, | ||
FragmentSpread(fragmentSpread) { | ||
usedFragments.add(fragmentSpread.name.value); | ||
}, | ||
FragmentDefinition(fragmentDefinition) { | ||
if (!usedFragments.has(fragmentDefinition.typeCondition.name.value)) { | ||
return null; | ||
} | ||
}, | ||
}); | ||
return [ | ||
localDocument.definitions.length > 0 ? localDocument : null, | ||
nonLocalDocument.definitions.length > 0 ? nonLocalDocument : null, | ||
]; | ||
}; | ||
const splitDocumentByOperation = (document, operationType) => { | ||
const usedFragments = new Set(); | ||
const documentWithOperation = graphql.visit(document, { | ||
OperationDefinition(operationDefinition) { | ||
if (operationDefinition.operation !== operationType) { | ||
return null; | ||
} | ||
}, | ||
FragmentSpread(fragmentSpread) { | ||
usedFragments.add(fragmentSpread.name.value); | ||
}, | ||
FragmentDefinition(fragmentDefinition) { | ||
if (!usedFragments.has(fragmentDefinition.name.value)) { | ||
return null; | ||
} | ||
}, | ||
}); | ||
usedFragments.clear(); | ||
const documentWithoutOperation = graphql.visit(document, { | ||
OperationDefinition(operationDefinition) { | ||
if (operationDefinition.operation === operationType) { | ||
return null; | ||
} | ||
}, | ||
FragmentSpread(fragmentSpread) { | ||
usedFragments.add(fragmentSpread.name.value); | ||
}, | ||
FragmentDefinition(fragmentDefinition) { | ||
if (!usedFragments.has(fragmentDefinition.name.value)) { | ||
return null; | ||
} | ||
}, | ||
}); | ||
return [ | ||
documentWithOperation.definitions.length > 0 ? documentWithOperation : null, | ||
documentWithoutOperation.definitions.length > 0 ? documentWithoutOperation : null, | ||
]; | ||
}; | ||
const extendSelection = (node, fragmentMap) => { | ||
@@ -313,6 +271,5 @@ var _a; | ||
/* eslint-disable no-continue */ | ||
const mapResolvers = (resolvers, typeDefs) => { | ||
var _a; | ||
const objectTypeDefinitions = typeDefs.definitions.filter(isObjectTypeDefinition); | ||
const objectTypeDefinitions = typeDefs.definitions.filter(definition => isObjectTypeDefinition(definition) || isObjectTypeExtension(definition)); | ||
const typeMap = new Map(objectTypeDefinitions.map(definition => [definition.name.value, definition])); | ||
@@ -327,3 +284,16 @@ const mappedResolvers = Object.fromEntries(Object.entries(resolvers).map(([key, fields]) => { | ||
Object.fromEntries(Object.entries(fields).map(([fieldName, fieldResolver]) => { | ||
const fieldDefinition = typeDefinition.fields.find(field => field.name.value === fieldName); | ||
let fieldDefinition; | ||
if (fieldName === '__resolveReference') { | ||
fieldDefinition = { | ||
kind: 'FieldDefinition', | ||
name: { kind: 'Name', value: '__resolveReference' }, | ||
type: { | ||
kind: 'NamedType', | ||
name: { kind: 'Name', value: key }, | ||
}, | ||
}; | ||
} | ||
else { | ||
fieldDefinition = typeDefinition.fields.find(field => field.name.value === fieldName); | ||
} | ||
if (!fieldDefinition) { | ||
@@ -345,3 +315,3 @@ throw new Error(`Resolver field ${fieldName} of type ${key} does not exist in your local schema`); | ||
if (!(requiredArg.name.value in (args || {}))) { | ||
throw new Error(`Field "${info.field.name.value}" argument "${requiredArg.name.value}" of type "${graphql.print(requiredArg.type)}" is required, but it was not provided.`); | ||
throw new Error(`Field "${info.field.name.value}" argument "${requiredArg.name.value}" of type "${getTypeName(requiredArg.type)}" is required, but it was not provided.`); | ||
} | ||
@@ -400,6 +370,6 @@ } | ||
} | ||
return [mappedResolvers, typeMap]; | ||
return mappedResolvers; | ||
}; | ||
const cleanResult = ({ data, document }) => { | ||
const cleanResult = ({ data, document, typeHint }) => { | ||
if (!document) { | ||
@@ -410,3 +380,4 @@ return data; | ||
const fragments = document.definitions.filter(isFragmentDefinition); | ||
const processSelectionSet = (selectionSet, currentData) => { | ||
const getType = (name) => typeHint === null || typeHint === void 0 ? void 0 : typeHint.objectTypes.find(objectType => objectType.name.value === name); | ||
const processSelectionSet = (selectionSet, currentData, objectType) => { | ||
if (currentData === null || currentData === undefined) { | ||
@@ -417,5 +388,5 @@ return null; | ||
selectionSet.selections.forEach(selection => { | ||
var _a; | ||
var _a, _b, _c, _d; | ||
if (selection.kind === 'InlineFragment') { | ||
Object.assign(processedData, processSelectionSet(selection.selectionSet, currentData)); | ||
Object.assign(processedData, processSelectionSet(selection.selectionSet, currentData, getType(((_a = selection.typeCondition) === null || _a === void 0 ? void 0 : _a.name.value) || ''))); | ||
} | ||
@@ -425,18 +396,23 @@ if (selection.kind === 'FragmentSpread') { | ||
if (fragment) { | ||
Object.assign(processedData, processSelectionSet(fragment.selectionSet, currentData)); | ||
Object.assign(processedData, processSelectionSet(fragment.selectionSet, currentData, getType(fragment.typeCondition.name.value))); | ||
} | ||
} | ||
if (selection.kind === 'Field') { | ||
const selectionName = ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value; | ||
const selectionData = currentData[selectionName]; | ||
const selectionName = ((_b = selection.alias) === null || _b === void 0 ? void 0 : _b.value) || selection.name.value; | ||
const selectionData = (_c = currentData[selectionName]) !== null && _c !== void 0 ? _c : null; | ||
const selectionField = (_d = objectType === null || objectType === void 0 ? void 0 : objectType.fields) === null || _d === void 0 ? void 0 : _d.find(field => field.name.value === selection.name.value); | ||
if (selectionData === null && selectionField && selectionField.type.kind === 'NonNullType') { | ||
throw new Error(`Cannot return null for non-nullable field ${objectType.name.value}.${selection.name.value}`); | ||
} | ||
if (selection.selectionSet) { | ||
const selectionType = selectionField ? getType(getTypeName(selectionField.type)) : undefined; | ||
if (Array.isArray(selectionData)) { | ||
processedData[selectionName] = selectionData.map((d) => processSelectionSet(selection.selectionSet, d)); | ||
processedData[selectionName] = selectionData.map((d) => processSelectionSet(selection.selectionSet, d, selectionType)); | ||
} | ||
else { | ||
processedData[selectionName] = processSelectionSet(selection.selectionSet, selectionData); | ||
processedData[selectionName] = processSelectionSet(selection.selectionSet, selectionData, selectionType); | ||
} | ||
} | ||
else { | ||
processedData[selectionName] = selectionData || null; | ||
processedData[selectionName] = selectionData; | ||
} | ||
@@ -447,9 +423,10 @@ } | ||
}; | ||
return operations.reduce((cleanedData, definition) => (Object.assign(Object.assign({}, cleanedData), processSelectionSet(definition.selectionSet, data))), {}); | ||
return operations.reduce((cleanedData, definition) => (Object.assign(Object.assign({}, cleanedData), processSelectionSet(definition.selectionSet, data, typeHint ? getType(typeHint.operationNames[definition.operation]) : undefined))), {}); | ||
}; | ||
const validate = ({ document, typeMap }) => { | ||
const validate = ({ document, typeDefs, operationNames }) => { | ||
if (!document) { | ||
return; | ||
} | ||
const objectTypesMap = new Map(typeDefs.definitions.filter(isObjectTypeDefinition).map(definition => [definition.name.value, definition])); | ||
const operations = document.definitions.filter(isOperationDefinition); | ||
@@ -459,3 +436,3 @@ const fragments = document.definitions.filter(isFragmentDefinition); | ||
const typeName = fragment.typeCondition.name.value; | ||
if (!typeMap.has(typeName) && !typeName.startsWith('__')) { | ||
if (!objectTypesMap.has(typeName) && !typeName.startsWith('__')) { | ||
throw new Error(`Unknown type "${typeName}".`); | ||
@@ -469,3 +446,3 @@ } | ||
const typeName = selection.typeCondition.name.value; | ||
if (!typeMap.has(typeName) && !typeName.startsWith('__')) { | ||
if (!objectTypesMap.has(typeName) && !typeName.startsWith('__')) { | ||
throw new Error(`Unknown type "${typeName}".`); | ||
@@ -487,3 +464,3 @@ } | ||
} | ||
const subType = typeMap.get(getTypeName(field.type)); | ||
const subType = objectTypesMap.get(getTypeName(field.type)); | ||
if (subType && selection.selectionSet) { | ||
@@ -496,4 +473,4 @@ processSelectionSet(selection.selectionSet, subType); | ||
operations.forEach(operation => { | ||
const operationName = operation.operation[0].toUpperCase() + operation.operation.substring(1); | ||
const type = typeMap.get(operationName); | ||
const operationName = operationNames[operation.operation]; | ||
const type = objectTypesMap.get(operationName); | ||
if (!type) { | ||
@@ -506,2 +483,65 @@ throw new Error(`Cannot query type ${operationName}`); | ||
/* eslint-disable default-case */ | ||
const alwaysAllowFields = ['__schema', '__type', '__resolveType']; | ||
const splitDocument = (document, objectType) => { | ||
if (!objectType) { | ||
return [null, document]; | ||
} | ||
const hasField = (field) => { var _a; return alwaysAllowFields.includes(field.name.value) || Boolean((_a = objectType.fields) === null || _a === void 0 ? void 0 : _a.find(f => f.name.value === field.name.value)); }; | ||
const usedFragments = new Set(); | ||
const baseVisitor = { | ||
OperationDefinition: { | ||
leave(operationDefinition) { | ||
if (operationDefinition.selectionSet.selections.length === 0) { | ||
return null; | ||
} | ||
}, | ||
}, | ||
FragmentSpread(fragmentSpread) { | ||
usedFragments.add(fragmentSpread.name.value); | ||
}, | ||
FragmentDefinition(fragmentDefinition) { | ||
if (!usedFragments.has(fragmentDefinition.name.value)) { | ||
return null; | ||
} | ||
}, | ||
}; | ||
let fieldDepth = -1; | ||
const internalDocument = graphql.visit(document, Object.assign(Object.assign({}, baseVisitor), { Field: { | ||
enter(field) { | ||
fieldDepth += 1; | ||
if (fieldDepth > 0) { | ||
return field; | ||
} | ||
if (hasField(field)) { | ||
return field; | ||
} | ||
return null; | ||
}, | ||
leave() { | ||
fieldDepth -= 1; | ||
}, | ||
} })); | ||
usedFragments.clear(); | ||
const externalDocument = graphql.visit(document, Object.assign(Object.assign({}, baseVisitor), { Field: { | ||
enter(field) { | ||
fieldDepth += 1; | ||
if (fieldDepth > 0) { | ||
return field; | ||
} | ||
if (!hasField(field)) { | ||
return field; | ||
} | ||
return null; | ||
}, | ||
leave() { | ||
fieldDepth -= 1; | ||
}, | ||
} })); | ||
return [ | ||
internalDocument && internalDocument.definitions.length > 0 ? internalDocument : null, | ||
externalDocument && externalDocument.definitions.length > 0 ? externalDocument : null, | ||
]; | ||
}; | ||
// https://github.com/graphql/graphql-spec/blob/master/spec/Section%204%20--%20Introspection.md | ||
@@ -533,3 +573,3 @@ const specifiedDefinitions = graphql.specifiedScalarTypes.map(scalarType => (Object.assign({ kind: 'ScalarTypeDefinition', name: { | ||
}; | ||
const addSchemaResolver = (resolvers, typeDefs) => { | ||
const addIntrospectionResolvers = (resolvers, typeDefs) => { | ||
const allTypes = [...specifiedDefinitions, ...typeDefs.definitions].filter(isTypeDefinition); | ||
@@ -540,3 +580,3 @@ const findType = (name) => allTypes.find(definition => { var _a; return 'name' in definition && ((_a = definition.name) === null || _a === void 0 ? void 0 : _a.value) === name; }); | ||
} | ||
const schemaDefinition = typeDefs.definitions.find(definition => definition.kind === 'SchemaDefinition'); | ||
const schemaDefinition = typeDefs.definitions.find(isSchemaDefinition); | ||
const findMainType = (name, fallbackName) => { | ||
@@ -606,3 +646,3 @@ const queryOperation = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(operationType => operationType.operation === name); | ||
description: ({ node }) => { var _a; return ('description' in node ? ((_a = node.description) === null || _a === void 0 ? void 0 : _a.value) || null : null); }, | ||
fields: ({ node }, { includeDeprecated = false }) => { | ||
fields: ({ node }, args) => { | ||
var _a; | ||
@@ -612,3 +652,3 @@ if (!(isObjectTypeDefinition(node) || isInterfaceTypeDefinition(node))) { | ||
} | ||
return (((_a = node.fields) === null || _a === void 0 ? void 0 : _a.filter(field => includeDeprecated || isDeprecated(field)).map(field => ({ __typename: '__Field', node: field }))) || []); | ||
return (((_a = node.fields) === null || _a === void 0 ? void 0 : _a.filter(field => (args && args.includeDeprecated) || !isDeprecated(field)).map(field => ({ __typename: '__Field', node: field }))) || []); | ||
}, | ||
@@ -630,3 +670,3 @@ interfaces: ({ node }) => { | ||
}, | ||
enumValues: ({ node }, { includeDeprecated = false }) => { | ||
enumValues: ({ node }, args) => { | ||
var _a; | ||
@@ -636,3 +676,3 @@ if (!isEnumTypeDefinition(node)) { | ||
} | ||
return (((_a = node.values) === null || _a === void 0 ? void 0 : _a.filter(value => includeDeprecated || isDeprecated(value)).map(value => ({ __typename: '__EnumValue', node: value }))) || []); | ||
return (((_a = node.values) === null || _a === void 0 ? void 0 : _a.filter(value => (args && args.includeDeprecated) || !isDeprecated(value)).map(value => ({ __typename: '__EnumValue', node: value }))) || []); | ||
}, | ||
@@ -690,17 +730,38 @@ inputFields: ({ node }) => { | ||
class LocalSchemaLink extends client.ApolloLink { | ||
constructor({ typeDefs, resolvers, context, assumeLocal, validateQuery = true, introspection = true, discriminationDirective = 'local', }) { | ||
constructor(options) { | ||
super(); | ||
this.options = options; | ||
this.localState = null; | ||
this.processedDocuments = new Map(); | ||
this.initalized = false; | ||
} | ||
init() { | ||
var _a, _b, _c; | ||
if (this.initalized) { | ||
return; | ||
} | ||
const { typeDefs, resolvers, context, validateQuery = true, introspection = true } = this.options; | ||
this.context = context; | ||
this.assumeLocal = Boolean(assumeLocal); | ||
const mergedTypeDefs = mergeDocuments(Array.isArray(typeDefs) ? typeDefs : [typeDefs]); | ||
const [mappedResolvers, typeMap] = mapResolvers(resolvers, mergedTypeDefs); | ||
const schemaDefinition = mergedTypeDefs.definitions.find(isSchemaDefinition); | ||
this.operationNames = { | ||
query: ((_a = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'query')) === null || _a === void 0 ? void 0 : _a.type.name.value) || 'Query', | ||
mutation: ((_b = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'mutation')) === null || _b === void 0 ? void 0 : _b.type.name.value) || 'Mutation', | ||
subscription: ((_c = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'subscription')) === null || _c === void 0 ? void 0 : _c.type.name.value) || 'Subscripion', | ||
}; | ||
if (this.federated) { | ||
this.federated.initTypeDefs(mergedTypeDefs); | ||
} | ||
const mappedResolvers = mapResolvers(resolvers, mergedTypeDefs); | ||
if (this.federated) { | ||
this.federated.initResovlers(mappedResolvers); | ||
} | ||
if (introspection) { | ||
addSchemaResolver(mappedResolvers, mergedTypeDefs); | ||
addIntrospectionResolvers(mappedResolvers, mergedTypeDefs); | ||
} | ||
this.typeDefs = mergedTypeDefs; | ||
this.resolvers = mappedResolvers; | ||
this.typeMap = typeMap; | ||
this.validateQuery = validateQuery; | ||
this.discriminationDirective = discriminationDirective; | ||
this.objectTypes = mergedTypeDefs.definitions.filter(definition => isObjectTypeDefinition(definition) || isObjectTypeExtension(definition)); | ||
this.initalized = true; | ||
} | ||
@@ -722,7 +783,19 @@ getLocalState(operation) { | ||
} | ||
splitDocumentByDirective(document) { | ||
__addFederationSupport(info) { | ||
this.federated = new LocalFederationSupport(info.serviceName); | ||
this.init(); | ||
this.validateQuery = false; | ||
return this.federated; | ||
} | ||
splitDocuments(document) { | ||
if (this.processedDocuments.has(document)) { | ||
return this.processedDocuments.get(document); | ||
} | ||
const documentsPair = splitDocumentByDirective(document, this.discriminationDirective); | ||
const { operation } = utilities.getMainDefinition(document); | ||
const operationDefinition = operation === 'query' | ||
? this.objectTypes.find(objectType => objectType.name.value === 'Query') | ||
: operation === 'mutation' | ||
? this.objectTypes.find(objectType => objectType.name.value === 'Mutation') | ||
: this.objectTypes.find(objectType => objectType.name.value === 'Subscription'); | ||
const documentsPair = splitDocument(document, operationDefinition); | ||
this.processedDocuments.set(document, documentsPair); | ||
@@ -732,22 +805,27 @@ return documentsPair; | ||
request(operation, forward) { | ||
this.init(); | ||
const { query, variables } = operation; | ||
const [localQuery, nonLocalQuery] = this.assumeLocal ? [query, null] : this.splitDocumentByDirective(query); | ||
const mainDefintion = utilities.getMainDefinition(query); | ||
const [internalQuery, externalQuery] = this.federated ? [query, null] : this.splitDocuments(query); | ||
const isSubscription = mainDefintion.kind === 'OperationDefinition' && mainDefintion.operation === 'subscription'; | ||
if (isSubscription && internalQuery && externalQuery) { | ||
throw new Error('Subscriptions cannot be a mix of local and external subscriptions'); | ||
} | ||
let nonLocalObservable = client.Observable.of({ data: {} }); | ||
if (forward) { | ||
if (!localQuery) { | ||
if (!internalQuery) { | ||
return forward(operation); | ||
} | ||
if (nonLocalQuery) { | ||
if (externalQuery) { | ||
// eslint-disable-next-line no-param-reassign | ||
operation.query = nonLocalQuery; | ||
operation.query = externalQuery; | ||
nonLocalObservable = forward(operation); | ||
} | ||
} | ||
const [subscriptionQuery, normalQuery] = splitDocumentByOperation(localQuery, 'subscription'); | ||
const localState = this.getLocalState(operation); | ||
if (normalQuery) { | ||
if (!isSubscription) { | ||
return nonLocalObservable.flatMap(remoteResult => new client.Observable(observer => { | ||
if (this.validateQuery) { | ||
try { | ||
validate({ document: localQuery, typeMap: this.typeMap }); | ||
validate({ document: internalQuery, typeDefs: this.typeDefs, operationNames: this.operationNames }); | ||
} | ||
@@ -762,3 +840,3 @@ catch (error) { | ||
.runResolvers({ | ||
document: normalQuery, | ||
document: internalQuery, | ||
remoteResult, | ||
@@ -769,3 +847,10 @@ context: this.getContext(), | ||
.then(({ data, errors }) => { | ||
observer.next({ data: cleanResult({ data, document: localQuery }), errors }); | ||
observer.next({ | ||
data: cleanResult({ | ||
data, | ||
document: internalQuery, | ||
typeHint: { objectTypes: this.objectTypes, operationNames: this.operationNames }, | ||
}), | ||
errors, | ||
}); | ||
observer.complete(); | ||
@@ -782,3 +867,3 @@ }) | ||
localState | ||
.runResolvers({ document: subscriptionQuery, remoteResult: { data: {} }, context: this.getContext(), variables }) | ||
.runResolvers({ document: internalQuery, remoteResult: { data: {} }, context: this.getContext(), variables }) | ||
.then(({ data, errors }) => { | ||
@@ -800,3 +885,3 @@ if (!data) { | ||
.runResolvers({ | ||
document: subscriptionQuery, | ||
document: internalQuery, | ||
remoteResult: { data: result }, | ||
@@ -809,3 +894,7 @@ context: Object.assign(Object.assign({}, this.getContext()), { __subscription: true }), | ||
.map(({ data }) => ({ | ||
data: cleanResult({ data, document: subscriptionQuery }), | ||
data: cleanResult({ | ||
data, | ||
document: internalQuery, | ||
typeHint: { objectTypes: this.objectTypes, operationNames: this.operationNames }, | ||
}), | ||
}))); | ||
@@ -812,0 +901,0 @@ }); |
import type { FragmentMap } from '@apollo/client/utilities'; | ||
import type { FieldNode, ObjectTypeDefinitionNode, DocumentNode } from 'graphql'; | ||
import type { FieldNode, DocumentNode } from 'graphql'; | ||
export declare type DocumentsPair = readonly [DocumentNode | null, DocumentNode | null]; | ||
export interface SubscriptionResolver<Context = any, T = any> { | ||
resolve?: (...args: Parameters<Resolver<Context>>) => T | Promise<T>; | ||
subscribe: (...args: Parameters<Resolver<Context>>) => AsyncIterator<T> | Promise<AsyncIterator<T>>; | ||
} | ||
export declare type Resolver<Context = any> = (rootValue: any, args: any, context: Context, info: { | ||
@@ -8,3 +12,7 @@ field: FieldNode; | ||
}) => any; | ||
export declare type Resolvers<Context = any> = Record<string, Record<string, Resolver<Context>>>; | ||
export declare type TypeMap = Map<string, ObjectTypeDefinitionNode>; | ||
export interface OperationNames { | ||
query: string; | ||
mutation: string; | ||
subscription: string; | ||
} | ||
export declare type Resolvers<Context = any> = Record<string, Record<string, Resolver<Context> | SubscriptionResolver<Context>>>; |
@@ -5,5 +5,4 @@ /// <reference types="zen-observable" /> | ||
import { Resolvers } from './interfaces'; | ||
import { LocalFederationSupport } from './localFederation'; | ||
interface LocalSchemaLinkOptions<Context = any> { | ||
assumeLocal?: boolean; | ||
discriminationDirective?: string; | ||
typeDefs: DocumentNode | Array<DocumentNode>; | ||
@@ -15,3 +14,7 @@ resolvers: Resolvers<Context>; | ||
} | ||
interface FederatedInfo { | ||
serviceName: string; | ||
} | ||
export declare class LocalSchemaLink<Context = any> extends ApolloLink { | ||
private options; | ||
private localState; | ||
@@ -21,10 +24,14 @@ private resolvers; | ||
private context; | ||
private assumeLocal; | ||
private typeMap; | ||
private validateQuery; | ||
private discriminationDirective; | ||
constructor({ typeDefs, resolvers, context, assumeLocal, validateQuery, introspection, discriminationDirective, }: LocalSchemaLinkOptions<Context>); | ||
private objectTypes; | ||
private initalized; | ||
private federated?; | ||
private typeDefs; | ||
private operationNames; | ||
constructor(options: LocalSchemaLinkOptions<Context>); | ||
private init; | ||
private getLocalState; | ||
private getContext; | ||
private splitDocumentByDirective; | ||
__addFederationSupport(info: FederatedInfo): LocalFederationSupport; | ||
private splitDocuments; | ||
request(operation: Operation, forward?: NextLink): Observable<FetchResult> | null; | ||
@@ -31,0 +38,0 @@ } |
import { ApolloLink, Observable } from '@apollo/client'; | ||
import { LocalState } from '@apollo/client/core/LocalState'; | ||
import { getMainDefinition } from '@apollo/client/utilities'; | ||
import { LocalFederationSupport } from './localFederation/LocalFederationSupport.js'; | ||
import './localFederation/index.js'; | ||
import { isSchemaDefinition, isObjectTypeDefinition, isObjectTypeExtension } from './utils/node.js'; | ||
import { mergeDocuments } from './utils/mergeDocuments.js'; | ||
import { splitDocumentByDirective } from './utils/splitDocumentByDirective.js'; | ||
import { splitDocumentByOperation } from './utils/splitDocumentByOperation.js'; | ||
import { mapResolvers } from './utils/mapResolvers.js'; | ||
import { cleanResult } from './utils/cleanResult.js'; | ||
import { validate } from './utils/validate.js'; | ||
import { splitDocument } from './utils/splitDocument.js'; | ||
import { addIntrospectionResolvers } from './utils/addIntrospectionResolvers.js'; | ||
import './utils/index.js'; | ||
import { addSchemaResolver } from './resolvers/buildSchemaResolver.js'; | ||
import './resolvers/index.js'; | ||
/* eslint-disable no-shadow */ | ||
class LocalSchemaLink extends ApolloLink { | ||
constructor({ typeDefs, resolvers, context, assumeLocal, validateQuery = true, introspection = true, discriminationDirective = 'local', }) { | ||
constructor(options) { | ||
super(); | ||
this.options = options; | ||
this.localState = null; | ||
this.processedDocuments = new Map(); | ||
this.initalized = false; | ||
} | ||
init() { | ||
var _a, _b, _c; | ||
if (this.initalized) { | ||
return; | ||
} | ||
const { typeDefs, resolvers, context, validateQuery = true, introspection = true } = this.options; | ||
this.context = context; | ||
this.assumeLocal = Boolean(assumeLocal); | ||
const mergedTypeDefs = mergeDocuments(Array.isArray(typeDefs) ? typeDefs : [typeDefs]); | ||
const [mappedResolvers, typeMap] = mapResolvers(resolvers, mergedTypeDefs); | ||
const schemaDefinition = mergedTypeDefs.definitions.find(isSchemaDefinition); | ||
this.operationNames = { | ||
query: ((_a = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'query')) === null || _a === void 0 ? void 0 : _a.type.name.value) || 'Query', | ||
mutation: ((_b = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'mutation')) === null || _b === void 0 ? void 0 : _b.type.name.value) || 'Mutation', | ||
subscription: ((_c = schemaDefinition === null || schemaDefinition === void 0 ? void 0 : schemaDefinition.operationTypes.find(type => type.operation === 'subscription')) === null || _c === void 0 ? void 0 : _c.type.name.value) || 'Subscripion', | ||
}; | ||
if (this.federated) { | ||
this.federated.initTypeDefs(mergedTypeDefs); | ||
} | ||
const mappedResolvers = mapResolvers(resolvers, mergedTypeDefs); | ||
if (this.federated) { | ||
this.federated.initResovlers(mappedResolvers); | ||
} | ||
if (introspection) { | ||
addSchemaResolver(mappedResolvers, mergedTypeDefs); | ||
addIntrospectionResolvers(mappedResolvers, mergedTypeDefs); | ||
} | ||
this.typeDefs = mergedTypeDefs; | ||
this.resolvers = mappedResolvers; | ||
this.typeMap = typeMap; | ||
this.validateQuery = validateQuery; | ||
this.discriminationDirective = discriminationDirective; | ||
this.objectTypes = mergedTypeDefs.definitions.filter(definition => isObjectTypeDefinition(definition) || isObjectTypeExtension(definition)); | ||
this.initalized = true; | ||
} | ||
@@ -46,7 +69,19 @@ getLocalState(operation) { | ||
} | ||
splitDocumentByDirective(document) { | ||
__addFederationSupport(info) { | ||
this.federated = new LocalFederationSupport(info.serviceName); | ||
this.init(); | ||
this.validateQuery = false; | ||
return this.federated; | ||
} | ||
splitDocuments(document) { | ||
if (this.processedDocuments.has(document)) { | ||
return this.processedDocuments.get(document); | ||
} | ||
const documentsPair = splitDocumentByDirective(document, this.discriminationDirective); | ||
const { operation } = getMainDefinition(document); | ||
const operationDefinition = operation === 'query' | ||
? this.objectTypes.find(objectType => objectType.name.value === 'Query') | ||
: operation === 'mutation' | ||
? this.objectTypes.find(objectType => objectType.name.value === 'Mutation') | ||
: this.objectTypes.find(objectType => objectType.name.value === 'Subscription'); | ||
const documentsPair = splitDocument(document, operationDefinition); | ||
this.processedDocuments.set(document, documentsPair); | ||
@@ -56,22 +91,27 @@ return documentsPair; | ||
request(operation, forward) { | ||
this.init(); | ||
const { query, variables } = operation; | ||
const [localQuery, nonLocalQuery] = this.assumeLocal ? [query, null] : this.splitDocumentByDirective(query); | ||
const mainDefintion = getMainDefinition(query); | ||
const [internalQuery, externalQuery] = this.federated ? [query, null] : this.splitDocuments(query); | ||
const isSubscription = mainDefintion.kind === 'OperationDefinition' && mainDefintion.operation === 'subscription'; | ||
if (isSubscription && internalQuery && externalQuery) { | ||
throw new Error('Subscriptions cannot be a mix of local and external subscriptions'); | ||
} | ||
let nonLocalObservable = Observable.of({ data: {} }); | ||
if (forward) { | ||
if (!localQuery) { | ||
if (!internalQuery) { | ||
return forward(operation); | ||
} | ||
if (nonLocalQuery) { | ||
if (externalQuery) { | ||
// eslint-disable-next-line no-param-reassign | ||
operation.query = nonLocalQuery; | ||
operation.query = externalQuery; | ||
nonLocalObservable = forward(operation); | ||
} | ||
} | ||
const [subscriptionQuery, normalQuery] = splitDocumentByOperation(localQuery, 'subscription'); | ||
const localState = this.getLocalState(operation); | ||
if (normalQuery) { | ||
if (!isSubscription) { | ||
return nonLocalObservable.flatMap(remoteResult => new Observable(observer => { | ||
if (this.validateQuery) { | ||
try { | ||
validate({ document: localQuery, typeMap: this.typeMap }); | ||
validate({ document: internalQuery, typeDefs: this.typeDefs, operationNames: this.operationNames }); | ||
} | ||
@@ -86,3 +126,3 @@ catch (error) { | ||
.runResolvers({ | ||
document: normalQuery, | ||
document: internalQuery, | ||
remoteResult, | ||
@@ -93,3 +133,10 @@ context: this.getContext(), | ||
.then(({ data, errors }) => { | ||
observer.next({ data: cleanResult({ data, document: localQuery }), errors }); | ||
observer.next({ | ||
data: cleanResult({ | ||
data, | ||
document: internalQuery, | ||
typeHint: { objectTypes: this.objectTypes, operationNames: this.operationNames }, | ||
}), | ||
errors, | ||
}); | ||
observer.complete(); | ||
@@ -106,3 +153,3 @@ }) | ||
localState | ||
.runResolvers({ document: subscriptionQuery, remoteResult: { data: {} }, context: this.getContext(), variables }) | ||
.runResolvers({ document: internalQuery, remoteResult: { data: {} }, context: this.getContext(), variables }) | ||
.then(({ data, errors }) => { | ||
@@ -124,3 +171,3 @@ if (!data) { | ||
.runResolvers({ | ||
document: subscriptionQuery, | ||
document: internalQuery, | ||
remoteResult: { data: result }, | ||
@@ -133,3 +180,7 @@ context: Object.assign(Object.assign({}, this.getContext()), { __subscription: true }), | ||
.map(({ data }) => ({ | ||
data: cleanResult({ data, document: subscriptionQuery }), | ||
data: cleanResult({ | ||
data, | ||
document: internalQuery, | ||
typeHint: { objectTypes: this.objectTypes, operationNames: this.operationNames }, | ||
}), | ||
}))); | ||
@@ -136,0 +187,0 @@ }); |
{ | ||
"name": "@grapes-agency/apollo-link-local-schema", | ||
"version": "1.0.0-alpha.11", | ||
"version": "1.0.0-alpha.12", | ||
"description": "Lightweight local schema resolver for @apollo/client", | ||
@@ -5,0 +5,0 @@ "main": "index.cjs.js", |
import type { FragmentMap } from '@apollo/client/utilities'; | ||
import type { ObjectTypeDefinitionNode, FieldNode } from 'graphql'; | ||
import type { ObjectTypeDefinitionNode, FieldNode, ObjectTypeExtensionNode } from 'graphql'; | ||
interface ProcessResultOptions { | ||
field: FieldNode; | ||
fragmentMap: FragmentMap; | ||
objectType: ObjectTypeDefinitionNode; | ||
typeMap: Map<string, ObjectTypeDefinitionNode>; | ||
objectType: ObjectTypeDefinitionNode | ObjectTypeExtensionNode; | ||
typeMap: Map<string, ObjectTypeDefinitionNode | ObjectTypeExtensionNode>; | ||
data: any; | ||
@@ -9,0 +9,0 @@ } |
@@ -1,7 +0,12 @@ | ||
import type { DocumentNode } from 'graphql'; | ||
import type { DocumentNode, ObjectTypeDefinitionNode, ObjectTypeExtensionNode } from 'graphql'; | ||
import { OperationNames } from '../interfaces'; | ||
interface CleanResultOptions { | ||
data: any; | ||
document: DocumentNode | null; | ||
typeHint?: { | ||
objectTypes: Array<ObjectTypeDefinitionNode | ObjectTypeExtensionNode>; | ||
operationNames: OperationNames; | ||
}; | ||
} | ||
export declare const cleanResult: ({ data, document }: CleanResultOptions) => any; | ||
export declare const cleanResult: ({ data, document, typeHint }: CleanResultOptions) => any; | ||
export {}; |
@@ -1,4 +0,4 @@ | ||
import { isOperationDefinition, isFragmentDefinition } from './node.js'; | ||
import { isOperationDefinition, isFragmentDefinition, getTypeName } from './node.js'; | ||
const cleanResult = ({ data, document }) => { | ||
const cleanResult = ({ data, document, typeHint }) => { | ||
if (!document) { | ||
@@ -9,3 +9,4 @@ return data; | ||
const fragments = document.definitions.filter(isFragmentDefinition); | ||
const processSelectionSet = (selectionSet, currentData) => { | ||
const getType = (name) => typeHint === null || typeHint === void 0 ? void 0 : typeHint.objectTypes.find(objectType => objectType.name.value === name); | ||
const processSelectionSet = (selectionSet, currentData, objectType) => { | ||
if (currentData === null || currentData === undefined) { | ||
@@ -16,5 +17,5 @@ return null; | ||
selectionSet.selections.forEach(selection => { | ||
var _a; | ||
var _a, _b, _c, _d; | ||
if (selection.kind === 'InlineFragment') { | ||
Object.assign(processedData, processSelectionSet(selection.selectionSet, currentData)); | ||
Object.assign(processedData, processSelectionSet(selection.selectionSet, currentData, getType(((_a = selection.typeCondition) === null || _a === void 0 ? void 0 : _a.name.value) || ''))); | ||
} | ||
@@ -24,18 +25,23 @@ if (selection.kind === 'FragmentSpread') { | ||
if (fragment) { | ||
Object.assign(processedData, processSelectionSet(fragment.selectionSet, currentData)); | ||
Object.assign(processedData, processSelectionSet(fragment.selectionSet, currentData, getType(fragment.typeCondition.name.value))); | ||
} | ||
} | ||
if (selection.kind === 'Field') { | ||
const selectionName = ((_a = selection.alias) === null || _a === void 0 ? void 0 : _a.value) || selection.name.value; | ||
const selectionData = currentData[selectionName]; | ||
const selectionName = ((_b = selection.alias) === null || _b === void 0 ? void 0 : _b.value) || selection.name.value; | ||
const selectionData = (_c = currentData[selectionName]) !== null && _c !== void 0 ? _c : null; | ||
const selectionField = (_d = objectType === null || objectType === void 0 ? void 0 : objectType.fields) === null || _d === void 0 ? void 0 : _d.find(field => field.name.value === selection.name.value); | ||
if (selectionData === null && selectionField && selectionField.type.kind === 'NonNullType') { | ||
throw new Error(`Cannot return null for non-nullable field ${objectType.name.value}.${selection.name.value}`); | ||
} | ||
if (selection.selectionSet) { | ||
const selectionType = selectionField ? getType(getTypeName(selectionField.type)) : undefined; | ||
if (Array.isArray(selectionData)) { | ||
processedData[selectionName] = selectionData.map((d) => processSelectionSet(selection.selectionSet, d)); | ||
processedData[selectionName] = selectionData.map((d) => processSelectionSet(selection.selectionSet, d, selectionType)); | ||
} | ||
else { | ||
processedData[selectionName] = processSelectionSet(selection.selectionSet, selectionData); | ||
processedData[selectionName] = processSelectionSet(selection.selectionSet, selectionData, selectionType); | ||
} | ||
} | ||
else { | ||
processedData[selectionName] = selectionData || null; | ||
processedData[selectionName] = selectionData; | ||
} | ||
@@ -46,3 +52,3 @@ } | ||
}; | ||
return operations.reduce((cleanedData, definition) => (Object.assign(Object.assign({}, cleanedData), processSelectionSet(definition.selectionSet, data))), {}); | ||
return operations.reduce((cleanedData, definition) => (Object.assign(Object.assign({}, cleanedData), processSelectionSet(definition.selectionSet, data, typeHint ? getType(typeHint.operationNames[definition.operation]) : undefined))), {}); | ||
}; | ||
@@ -49,0 +55,0 @@ |
export * from './mergeDocuments'; | ||
export * from './splitDocumentByDirective'; | ||
export * from './splitDocumentByOperation'; | ||
export * from './mapResolvers'; | ||
@@ -8,1 +6,3 @@ export * from './cleanResult'; | ||
export * from './node'; | ||
export * from './splitDocument'; | ||
export * from './addIntrospectionResolvers'; |
@@ -1,8 +0,8 @@ | ||
export { getTypeName, isDeepListType, isDirectiveDefinition, isEnumTypeDefinition, isFragmentDefinition, isInputObjectTypeDefinition, isInterfaceTypeDefinition, isListType, isNamedType, isNonNullType, isObjectTypeDefinition, isObjectTypeExtension, isOperationDefinition, isScalarTypeDefinition, isTypeDefinition, isUnionTypeDefinition } from './node.js'; | ||
export { getTypeName, isDeepListType, isDirectiveDefinition, isEnumTypeDefinition, isFragmentDefinition, isInputObjectTypeDefinition, isInterfaceTypeDefinition, isListType, isNamedType, isNonNullType, isObjectTypeDefinition, isObjectTypeExtension, isOperationDefinition, isScalarTypeDefinition, isSchemaDefinition, isTypeDefinition, isUnionTypeDefinition } from './node.js'; | ||
export { mergeDocuments } from './mergeDocuments.js'; | ||
export { splitDocumentByDirective } from './splitDocumentByDirective.js'; | ||
export { splitDocumentByOperation } from './splitDocumentByOperation.js'; | ||
export { mapResolvers } from './mapResolvers.js'; | ||
export { cleanResult } from './cleanResult.js'; | ||
export { validate } from './validate.js'; | ||
export { splitDocument } from './splitDocument.js'; | ||
export { addIntrospectionResolvers } from './addIntrospectionResolvers.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -1,3 +0,3 @@ | ||
import { DocumentNode } from 'graphql'; | ||
import { Resolvers, Resolver, TypeMap } from '../interfaces'; | ||
export declare const mapResolvers: <C>(resolvers: Record<string, Record<string, Resolver<C>>>, typeDefs: DocumentNode) => [Record<string, Record<string, Resolver<C>>>, TypeMap]; | ||
import type { DocumentNode } from 'graphql'; | ||
import type { Resolvers, Resolver } from '../interfaces'; | ||
export declare const mapResolvers: <C>(resolvers: Record<string, Record<string, Resolver<C> | import("../interfaces").SubscriptionResolver<C, any>>>, typeDefs: DocumentNode) => Record<string, Record<string, Resolver<C> | import("../interfaces").SubscriptionResolver<C, any>>>; |
@@ -1,10 +0,8 @@ | ||
import { print } from 'graphql'; | ||
import { isObjectTypeDefinition, getTypeName, isScalarTypeDefinition } from './node.js'; | ||
import { isObjectTypeDefinition, isObjectTypeExtension, getTypeName, isScalarTypeDefinition } from './node.js'; | ||
import { addTypesnames } from './addTypenames.js'; | ||
import { isSubscriptionResolver, mapSubscription } from './mapSubscription.js'; | ||
/* eslint-disable no-continue */ | ||
const mapResolvers = (resolvers, typeDefs) => { | ||
var _a; | ||
const objectTypeDefinitions = typeDefs.definitions.filter(isObjectTypeDefinition); | ||
const objectTypeDefinitions = typeDefs.definitions.filter(definition => isObjectTypeDefinition(definition) || isObjectTypeExtension(definition)); | ||
const typeMap = new Map(objectTypeDefinitions.map(definition => [definition.name.value, definition])); | ||
@@ -19,3 +17,16 @@ const mappedResolvers = Object.fromEntries(Object.entries(resolvers).map(([key, fields]) => { | ||
Object.fromEntries(Object.entries(fields).map(([fieldName, fieldResolver]) => { | ||
const fieldDefinition = typeDefinition.fields.find(field => field.name.value === fieldName); | ||
let fieldDefinition; | ||
if (fieldName === '__resolveReference') { | ||
fieldDefinition = { | ||
kind: 'FieldDefinition', | ||
name: { kind: 'Name', value: '__resolveReference' }, | ||
type: { | ||
kind: 'NamedType', | ||
name: { kind: 'Name', value: key }, | ||
}, | ||
}; | ||
} | ||
else { | ||
fieldDefinition = typeDefinition.fields.find(field => field.name.value === fieldName); | ||
} | ||
if (!fieldDefinition) { | ||
@@ -37,3 +48,3 @@ throw new Error(`Resolver field ${fieldName} of type ${key} does not exist in your local schema`); | ||
if (!(requiredArg.name.value in (args || {}))) { | ||
throw new Error(`Field "${info.field.name.value}" argument "${requiredArg.name.value}" of type "${print(requiredArg.type)}" is required, but it was not provided.`); | ||
throw new Error(`Field "${info.field.name.value}" argument "${requiredArg.name.value}" of type "${getTypeName(requiredArg.type)}" is required, but it was not provided.`); | ||
} | ||
@@ -92,3 +103,3 @@ } | ||
} | ||
return [mappedResolvers, typeMap]; | ||
return mappedResolvers; | ||
}; | ||
@@ -95,0 +106,0 @@ |
@@ -1,13 +0,9 @@ | ||
import { ObjectTypeDefinitionNode } from 'graphql'; | ||
import { Resolver } from '../interfaces'; | ||
interface SubscriptionResolver<T> { | ||
resolve?: (...args: Parameters<Resolver<any>>) => T | Promise<T>; | ||
subscribe: (...args: Parameters<Resolver<any>>) => AsyncIterator<T> | Promise<AsyncIterator<T>>; | ||
} | ||
export declare const isSubscriptionResolver: (resolver: any) => resolver is SubscriptionResolver<any>; | ||
import { ObjectTypeDefinitionNode, ObjectTypeExtensionNode } from 'graphql'; | ||
import { Resolver, SubscriptionResolver } from '../interfaces'; | ||
export declare const isSubscriptionResolver: (resolver: any) => resolver is SubscriptionResolver<any, any>; | ||
interface MapSubscriptionOptions<T> { | ||
fieldName: string; | ||
resolver: SubscriptionResolver<T>; | ||
objectType?: ObjectTypeDefinitionNode; | ||
typeMap: Map<string, ObjectTypeDefinitionNode>; | ||
resolver: SubscriptionResolver<any, T>; | ||
objectType?: ObjectTypeDefinitionNode | ObjectTypeExtensionNode; | ||
typeMap: Map<string, ObjectTypeDefinitionNode | ObjectTypeExtensionNode>; | ||
} | ||
@@ -14,0 +10,0 @@ export declare const mapSubscription: <C, T>({ fieldName, resolver, objectType, typeMap, }: MapSubscriptionOptions<T>) => Resolver<C & { |
@@ -29,11 +29,11 @@ import { visit } from 'graphql'; | ||
if (typeDefinitions.length > 1) { | ||
throw new Error(`Multiple type definitions for type ${name}`); | ||
throw new Error(`Multiple type definitions for type "${name}"`); | ||
} | ||
if (typeDefinitions.length === 0) { | ||
const [typeExtension] = typeExtensions; | ||
typeDefinitions.push(Object.assign(Object.assign({}, typeExtension), { kind: 'ObjectTypeDefinition' })); | ||
const [typeExtension, ...restTypeExtensions] = typeExtensions; | ||
documentDefinitions.push(Object.assign(Object.assign({}, typeExtension), { fields: restTypeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...typeExtension.fields]) })); | ||
return; | ||
} | ||
const [definition] = typeDefinitions; | ||
const fields = typeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...definition.fields]); | ||
documentDefinitions.push(Object.assign(Object.assign({}, definition), { fields })); | ||
documentDefinitions.push(Object.assign(Object.assign({}, definition), { fields: typeExtensions.reduce((fieldDefinitions, extension) => [...fieldDefinitions, ...extension.fields], [...definition.fields]) })); | ||
}); | ||
@@ -40,0 +40,0 @@ return { |
@@ -1,2 +0,2 @@ | ||
import { TypeNode, NamedTypeNode, ObjectTypeExtensionNode, ObjectTypeDefinitionNode, ScalarTypeDefinitionNode, OperationDefinitionNode, FragmentDefinitionNode, EnumTypeDefinitionNode, DirectiveDefinitionNode, InputObjectTypeDefinitionNode, ASTNode, InterfaceTypeDefinitionNode, ListTypeNode, NonNullTypeNode, TypeDefinitionNode, UnionTypeDefinitionNode } from 'graphql'; | ||
import { TypeNode, NamedTypeNode, ObjectTypeExtensionNode, ObjectTypeDefinitionNode, ScalarTypeDefinitionNode, OperationDefinitionNode, FragmentDefinitionNode, EnumTypeDefinitionNode, DirectiveDefinitionNode, InputObjectTypeDefinitionNode, ASTNode, InterfaceTypeDefinitionNode, ListTypeNode, NonNullTypeNode, TypeDefinitionNode, UnionTypeDefinitionNode, SchemaDefinitionNode } from 'graphql'; | ||
export declare const isObjectTypeDefinition: (node: ASTNode) => node is ObjectTypeDefinitionNode; | ||
@@ -12,2 +12,3 @@ export declare const isInterfaceTypeDefinition: (node: ASTNode) => node is InterfaceTypeDefinitionNode; | ||
export declare const isFragmentDefinition: (node: ASTNode) => node is FragmentDefinitionNode; | ||
export declare const isSchemaDefinition: (node: ASTNode) => node is SchemaDefinitionNode; | ||
export declare const isTypeDefinition: (node: ASTNode) => node is TypeDefinitionNode; | ||
@@ -14,0 +15,0 @@ export declare const isNamedType: (node: TypeNode) => node is NamedTypeNode; |
@@ -11,2 +11,3 @@ const isObjectTypeDefinition = (node) => node.kind === 'ObjectTypeDefinition'; | ||
const isFragmentDefinition = (node) => node.kind === 'FragmentDefinition'; | ||
const isSchemaDefinition = (node) => node.kind === 'SchemaDefinition'; | ||
const isTypeDefinition = (node) => [ | ||
@@ -36,3 +37,3 @@ 'ScalarTypeDefinition', | ||
export { getTypeName, isDeepListType, isDirectiveDefinition, isEnumTypeDefinition, isFragmentDefinition, isInputObjectTypeDefinition, isInterfaceTypeDefinition, isListType, isNamedType, isNonNullType, isObjectTypeDefinition, isObjectTypeExtension, isOperationDefinition, isScalarTypeDefinition, isTypeDefinition, isUnionTypeDefinition }; | ||
export { getTypeName, isDeepListType, isDirectiveDefinition, isEnumTypeDefinition, isFragmentDefinition, isInputObjectTypeDefinition, isInterfaceTypeDefinition, isListType, isNamedType, isNonNullType, isObjectTypeDefinition, isObjectTypeExtension, isOperationDefinition, isScalarTypeDefinition, isSchemaDefinition, isTypeDefinition, isUnionTypeDefinition }; | ||
//# sourceMappingURL=node.js.map |
import type { DocumentNode } from 'graphql'; | ||
import { TypeMap } from '../interfaces'; | ||
import { OperationNames } from '../interfaces'; | ||
interface ValidateOptions { | ||
document: DocumentNode | null; | ||
typeMap: TypeMap; | ||
typeDefs: DocumentNode; | ||
operationNames: OperationNames; | ||
} | ||
export declare const validate: ({ document, typeMap }: ValidateOptions) => void; | ||
export declare const validate: ({ document, typeDefs, operationNames }: ValidateOptions) => void; | ||
export {}; |
@@ -1,7 +0,8 @@ | ||
import { isOperationDefinition, isFragmentDefinition, getTypeName } from './node.js'; | ||
import { isObjectTypeDefinition, isOperationDefinition, isFragmentDefinition, getTypeName } from './node.js'; | ||
const validate = ({ document, typeMap }) => { | ||
const validate = ({ document, typeDefs, operationNames }) => { | ||
if (!document) { | ||
return; | ||
} | ||
const objectTypesMap = new Map(typeDefs.definitions.filter(isObjectTypeDefinition).map(definition => [definition.name.value, definition])); | ||
const operations = document.definitions.filter(isOperationDefinition); | ||
@@ -11,3 +12,3 @@ const fragments = document.definitions.filter(isFragmentDefinition); | ||
const typeName = fragment.typeCondition.name.value; | ||
if (!typeMap.has(typeName) && !typeName.startsWith('__')) { | ||
if (!objectTypesMap.has(typeName) && !typeName.startsWith('__')) { | ||
throw new Error(`Unknown type "${typeName}".`); | ||
@@ -21,3 +22,3 @@ } | ||
const typeName = selection.typeCondition.name.value; | ||
if (!typeMap.has(typeName) && !typeName.startsWith('__')) { | ||
if (!objectTypesMap.has(typeName) && !typeName.startsWith('__')) { | ||
throw new Error(`Unknown type "${typeName}".`); | ||
@@ -39,3 +40,3 @@ } | ||
} | ||
const subType = typeMap.get(getTypeName(field.type)); | ||
const subType = objectTypesMap.get(getTypeName(field.type)); | ||
if (subType && selection.selectionSet) { | ||
@@ -48,4 +49,4 @@ processSelectionSet(selection.selectionSet, subType); | ||
operations.forEach(operation => { | ||
const operationName = operation.operation[0].toUpperCase() + operation.operation.substring(1); | ||
const type = typeMap.get(operationName); | ||
const operationName = operationNames[operation.operation]; | ||
const type = objectTypesMap.get(operationName); | ||
if (!type) { | ||
@@ -52,0 +53,0 @@ throw new Error(`Cannot query type ${operationName}`); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
178808
53
1977