UNPKG

@graphprotocol/client-block-tracking

Version:

`graph-client` implements automatic block tracking using `number_gte` filter of `graph-node`. This automates the process [of fetching and tracking the block number of entites](https://thegraph.com/docs/en/developer/distributed-systems/#polling-for-updated

148 lines (147 loc) 6.44 kB
import { memoize1 } from '@graphql-tools/utils'; import { Kind, OperationTypeNode, visit } from 'graphql'; export const DEFAULT_CONFIG = { ignoreOperationNames: [], ignoreFieldNames: [], metaTypeName: '_Meta_', blockFieldName: 'block', blockNumberFieldName: 'number', metaRootFieldName: '_meta', blockArgumentName: 'block', minBlockArgumentName: 'number_gte', }; export const createMetaSelectionNode = memoize1(function createMetaSelectionNode(config) { return { kind: Kind.FIELD, name: { kind: Kind.NAME, value: config.metaRootFieldName, }, selectionSet: { kind: Kind.SELECTION_SET, selections: [ { kind: Kind.FIELD, name: { kind: Kind.NAME, value: config.blockFieldName, }, selectionSet: { kind: Kind.SELECTION_SET, selections: [ { kind: Kind.FIELD, name: { kind: Kind.NAME, value: config.blockNumberFieldName, }, }, ], }, }, ], }, }; }); const getQueryFieldNames = memoize1(function getQueryFields(schema) { const queryType = schema.getQueryType(); if (queryType == null) { throw new Error(`Make sure you have a query type in this source before applying Block Tracking`); } return Object.keys(queryType.getFields()); }); const metaFieldAddedByContext = new WeakMap(); function getRequestIdentifier(executionRequest) { return executionRequest.context ?? executionRequest.rootValue ?? executionRequest.info?.operation; } export function transformExecutionRequest(executionRequest, config, transformedSchema, batch = false, minBlock) { if (executionRequest.operationName != null && config.ignoreOperationNames.includes(executionRequest.operationName)) { return executionRequest; } const document = visit(executionRequest.document, { Field: { leave: (fieldSelectionNode) => { if (minBlock != null && !fieldSelectionNode.name.value.startsWith('_') && !config.ignoreFieldNames.includes(fieldSelectionNode.name.value) && getQueryFieldNames(transformedSchema).includes(fieldSelectionNode.name.value)) { const argNodes = fieldSelectionNode.arguments?.filter((argument) => argument.name.value !== config.blockArgumentName) || []; const blockArgument = argNodes.find((argument) => argument.name.value === config.blockArgumentName) || { kind: Kind.ARGUMENT, name: { kind: Kind.NAME, value: config.blockArgumentName, }, value: { kind: Kind.OBJECT, fields: [], }, }; const blockArgumentFields = blockArgument.value.fields; if (!blockArgumentFields.some((field) => field.name.value === config.minBlockArgumentName)) { return { ...fieldSelectionNode, arguments: [ ...argNodes, { ...blockArgument, value: { ...blockArgument.value, fields: [ ...blockArgumentFields, { kind: Kind.OBJECT_FIELD, name: { kind: Kind.NAME, value: config.minBlockArgumentName, }, value: { kind: Kind.INT, value: minBlock.toString(), }, }, ], }, }, ], }; } } return fieldSelectionNode; }, }, OperationDefinition: { leave: (operationNode) => { const requestIdentifier = getRequestIdentifier(executionRequest); let shouldAddMetaField = true; if (batch) { if (requestIdentifier != null) { const isAddedBefore = metaFieldAddedByContext.get(requestIdentifier); if (isAddedBefore != null) { shouldAddMetaField = !isAddedBefore; } } } if (operationNode.operation === OperationTypeNode.QUERY && shouldAddMetaField) { const metaSelectionNode = createMetaSelectionNode(config); metaFieldAddedByContext.set(requestIdentifier, true); return { ...operationNode, selectionSet: { ...operationNode.selectionSet, selections: [...operationNode.selectionSet.selections, metaSelectionNode], }, }; } return operationNode; }, }, }); return { ...executionRequest, document, }; } export function getNewBlockNumberFromExecutionResult(originalResult, config) { return originalResult.data?.[config.metaRootFieldName]?.[config.blockFieldName]?.[config.blockNumberFieldName]; }