@envelop/core
Advanced tools
Comparing version 0.2.2 to 0.3.0-alpha-5108c55.0
import { Envelop, Plugin } from '@envelop/types'; | ||
export declare function envelop({ plugins }: { | ||
export declare function envelop(options: { | ||
plugins: Plugin[]; | ||
enableInternalTracing?: boolean; | ||
}): Envelop; |
395
index.cjs.js
@@ -10,5 +10,61 @@ 'use strict'; | ||
const resolversHooksSymbol = Symbol('RESOLVERS_HOOKS'); | ||
function envelop({ plugins }) { | ||
function prepareTracedSchema(schema) { | ||
if (!schema || schema[trackedSchemaSymbol]) { | ||
return; | ||
} | ||
schema[trackedSchemaSymbol] = true; | ||
const entries = Object.values(schema.getTypeMap()); | ||
for (const type of entries) { | ||
if (!graphql.isIntrospectionType(type) && graphql.isObjectType(type)) { | ||
const fields = Object.values(type.getFields()); | ||
for (const field of fields) { | ||
const originalFn = field.resolve || graphql.defaultFieldResolver; | ||
field.resolve = async (root, args, context, info) => { | ||
if (context && context[resolversHooksSymbol]) { | ||
const hooks = context[resolversHooksSymbol]; | ||
const afterCalls = []; | ||
for (const hook of hooks) { | ||
const afterFn = await hook({ root, args, context, info }); | ||
afterFn && afterCalls.push(afterFn); | ||
} | ||
try { | ||
let result = await originalFn(root, args, context, info); | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result, | ||
setResult: newResult => { | ||
result = newResult; | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
catch (e) { | ||
let resultErr = e; | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result: resultErr, | ||
setResult: newResult => { | ||
resultErr = newResult; | ||
}, | ||
}); | ||
} | ||
throw resultErr; | ||
} | ||
} | ||
else { | ||
return originalFn(root, args, context, info); | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
function createEnvelopOrchestrator(plugins) { | ||
let schema = null; | ||
let initDone = false; | ||
// Define the initial method for replacing the GraphQL schema, this is needed in order | ||
// to allow setting the schema from the onPluginInit callback. We also need to make sure | ||
// here not to call the same plugin that initiated the schema switch. | ||
const replaceSchema = (newSchema, ignorePluginIndex = -1) => { | ||
@@ -30,2 +86,3 @@ schema = newSchema; | ||
}; | ||
// Iterate all plugins and trigger onPluginInit | ||
for (const [i, plugin] of plugins.entries()) { | ||
@@ -41,21 +98,29 @@ plugin.onPluginInit && | ||
} | ||
const onContextBuildingCbs = []; | ||
const onExecuteCbs = []; | ||
const onParseCbs = []; | ||
const onSubscribeCbs = []; | ||
const onValidateCbs = []; | ||
// A set of before callbacks defined here in order to allow it to be used later | ||
const beforeCallbacks = { | ||
parse: [], | ||
validate: [], | ||
subscribe: [], | ||
execute: [], | ||
context: [], | ||
}; | ||
for (const { onContextBuilding, onExecute, onParse, onSubscribe, onValidate } of plugins) { | ||
onContextBuilding && onContextBuildingCbs.push(onContextBuilding); | ||
onExecute && onExecuteCbs.push(onExecute); | ||
onParse && onParseCbs.push(onParse); | ||
onSubscribe && onSubscribeCbs.push(onSubscribe); | ||
onValidate && onValidateCbs.push(onValidate); | ||
onContextBuilding && beforeCallbacks.context.push(onContextBuilding); | ||
onExecute && beforeCallbacks.execute.push(onExecute); | ||
onParse && beforeCallbacks.parse.push(onParse); | ||
onSubscribe && beforeCallbacks.subscribe.push(onSubscribe); | ||
onValidate && beforeCallbacks.validate.push(onValidate); | ||
} | ||
const customParse = onParseCbs.length | ||
? (source, parseOptions) => { | ||
const customParse = beforeCallbacks.parse.length | ||
? sharedObj => (source, parseOptions) => { | ||
let result = null; | ||
let parseFn = graphql.parse; | ||
const context = sharedObj; | ||
const afterCalls = []; | ||
for (const onParse of onParseCbs) { | ||
for (const onParse of beforeCallbacks.parse) { | ||
const afterFn = onParse({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
params: { source, options: parseOptions }, | ||
@@ -82,2 +147,6 @@ parseFn, | ||
afterCb({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
replaceParseResult: newResult => { | ||
@@ -97,11 +166,16 @@ result = newResult; | ||
} | ||
: graphql.parse; | ||
const customValidate = onValidateCbs.length | ||
? (schema, documentAST, rules, typeInfo, validationOptions) => { | ||
: () => graphql.parse; | ||
const customValidate = beforeCallbacks.validate.length | ||
? initialContext => (schema, documentAST, rules, typeInfo, validationOptions) => { | ||
let actualRules = rules ? [...rules] : undefined; | ||
let validateFn = graphql.validate; | ||
let result = null; | ||
const context = initialContext; | ||
const afterCalls = []; | ||
for (const onValidate of onValidateCbs) { | ||
for (const onValidate of beforeCallbacks.validate) { | ||
const afterFn = onValidate({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
params: { | ||
@@ -135,24 +209,23 @@ schema, | ||
for (const afterCb of afterCalls) { | ||
afterCb({ valid, result }); | ||
afterCb({ | ||
valid, | ||
result, | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
: graphql.validate; | ||
const customContextFactory = onContextBuildingCbs.length | ||
? async (initialContext) => { | ||
: () => graphql.validate; | ||
const customContextFactory = beforeCallbacks.context.length | ||
? initialContext => async () => { | ||
const afterCalls = []; | ||
let context = initialContext; | ||
const afterCalls = []; | ||
for (const onContext of onContextBuildingCbs) { | ||
for (const onContext of beforeCallbacks.context) { | ||
const afterFn = await onContext({ | ||
context, | ||
extendContext: extension => { | ||
if (typeof extension === 'object') { | ||
context = { | ||
...(context || {}), | ||
...extension, | ||
}; | ||
} | ||
else { | ||
throw new Error(`Invalid context extension provided! Expected "object", got: "${JSON.stringify(extension)}" (${typeof extension})`); | ||
} | ||
context = { ...context, ...extension }; | ||
}, | ||
@@ -163,7 +236,12 @@ }); | ||
for (const afterCb of afterCalls) { | ||
afterCb({ context }); | ||
afterCb({ | ||
context, | ||
extendContext: extension => { | ||
context = { ...context, ...extension }; | ||
}, | ||
}); | ||
} | ||
return context; | ||
} | ||
: (ctx) => ctx; | ||
: ctx => () => ctx; | ||
const customSubscribe = async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => { | ||
@@ -185,4 +263,4 @@ const args = argsOrSchema instanceof graphql.GraphQLSchema | ||
const afterCalls = []; | ||
let context = args.contextValue; | ||
for (const onSubscribe of onSubscribeCbs) { | ||
let context = args.contextValue || {}; | ||
for (const onSubscribe of beforeCallbacks.subscribe) { | ||
const after = onSubscribe({ | ||
@@ -194,11 +272,3 @@ subscribeFn, | ||
extendContext: extension => { | ||
if (typeof extension === 'object') { | ||
context = { | ||
...(context || {}), | ||
...extension, | ||
}; | ||
} | ||
else { | ||
throw new Error(`Invalid context extension provided! Expected "object", got: "${JSON.stringify(extension)}" (${typeof extension})`); | ||
} | ||
context = { ...context, ...extension }; | ||
}, | ||
@@ -233,3 +303,3 @@ args, | ||
}; | ||
const customExecute = onExecuteCbs.length | ||
const customExecute = beforeCallbacks.execute.length | ||
? async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => { | ||
@@ -252,4 +322,4 @@ const args = argsOrSchema instanceof graphql.GraphQLSchema | ||
const afterCalls = []; | ||
let context = args.contextValue; | ||
for (const onExecute of onExecuteCbs) { | ||
let context = args.contextValue || {}; | ||
for (const onExecute of beforeCallbacks.execute) { | ||
let stopCalled = false; | ||
@@ -308,54 +378,2 @@ const after = onExecute({ | ||
: graphql.execute; | ||
function prepareSchema() { | ||
if (!schema || schema[trackedSchemaSymbol]) { | ||
return; | ||
} | ||
schema[trackedSchemaSymbol] = true; | ||
const entries = Object.values(schema.getTypeMap()); | ||
for (const type of entries) { | ||
if (!graphql.isIntrospectionType(type) && graphql.isObjectType(type)) { | ||
const fields = Object.values(type.getFields()); | ||
for (const field of fields) { | ||
const originalFn = field.resolve || graphql.defaultFieldResolver; | ||
field.resolve = async (root, args, context, info) => { | ||
if (context && context[resolversHooksSymbol]) { | ||
const hooks = context[resolversHooksSymbol]; | ||
const afterCalls = []; | ||
for (const hook of hooks) { | ||
const afterFn = await hook({ root, args, context, info }); | ||
afterFn && afterCalls.push(afterFn); | ||
} | ||
try { | ||
let result = await originalFn(root, args, context, info); | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result, | ||
setResult: newResult => { | ||
result = newResult; | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
catch (e) { | ||
let resultErr = e; | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result: resultErr, | ||
setResult: newResult => { | ||
resultErr = newResult; | ||
}, | ||
}); | ||
} | ||
throw resultErr; | ||
} | ||
} | ||
else { | ||
return originalFn(root, args, context, info); | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
initDone = true; | ||
@@ -373,17 +391,140 @@ // This is done in order to trigger the first schema available, to allow plugins that needs the schema | ||
} | ||
const envelop = () => { | ||
prepareSchema(); | ||
return { | ||
get schema() { | ||
return schema; | ||
}, | ||
prepareSchema: () => prepareTracedSchema(schema), | ||
parse: customParse, | ||
validate: customValidate, | ||
execute: customExecute, | ||
subscribe: customSubscribe, | ||
contextFactory: customContextFactory, | ||
}; | ||
} | ||
const HR_TO_NS = 1e9; | ||
const NS_TO_MS = 1e6; | ||
const deltaFrom = (hrtime) => { | ||
const delta = process.hrtime(hrtime); | ||
const ns = delta[0] * HR_TO_NS + delta[1]; | ||
return ns / NS_TO_MS; | ||
}; | ||
function traceOrchestrator(orchestrator) { | ||
const createTracer = (name, ctx) => { | ||
const hrtime = process.hrtime(); | ||
return () => { | ||
const time = deltaFrom(hrtime); | ||
ctx._envelopTracing[name] = time; | ||
}; | ||
}; | ||
return { | ||
...orchestrator, | ||
parse: (ctx = {}) => { | ||
const actualFn = orchestrator.parse(ctx); | ||
return (...args) => { | ||
const done = createTracer('parse', ctx); | ||
try { | ||
return actualFn(...args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
validate: (ctx = {}) => { | ||
const actualFn = orchestrator.validate(ctx); | ||
return (...args) => { | ||
const done = createTracer('validate', ctx); | ||
try { | ||
return actualFn(...args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
execute: async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => { | ||
const args = argsOrSchema instanceof graphql.GraphQLSchema | ||
? { | ||
schema: argsOrSchema, | ||
document: document, | ||
rootValue, | ||
contextValue, | ||
variableValues, | ||
operationName, | ||
fieldResolver, | ||
typeResolver, | ||
} | ||
: argsOrSchema; | ||
const done = createTracer('execute', args.contextValue || {}); | ||
try { | ||
const result = await orchestrator.execute(args); | ||
done(); | ||
result.extensions = result.extensions || {}; | ||
result.extensions.envelopTracing = args.contextValue._envelopTracing; | ||
return result; | ||
} | ||
catch (e) { | ||
done(); | ||
throw e; | ||
} | ||
}, | ||
subscribe: async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => { | ||
const args = argsOrSchema instanceof graphql.GraphQLSchema | ||
? { | ||
schema: argsOrSchema, | ||
document: document, | ||
rootValue, | ||
contextValue, | ||
variableValues, | ||
operationName, | ||
fieldResolver, | ||
subscribeFieldResolver, | ||
} | ||
: argsOrSchema; | ||
const done = createTracer('subscribe', args.contextValue || {}); | ||
try { | ||
return await orchestrator.subscribe(args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}, | ||
contextFactory: (ctx = {}) => { | ||
const actualFn = orchestrator.contextFactory(ctx); | ||
return async () => { | ||
const done = createTracer('contextFactory', ctx); | ||
try { | ||
return await actualFn(); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
}; | ||
} | ||
function envelop(options) { | ||
let orchestrator = createEnvelopOrchestrator(options.plugins); | ||
if (options.enableInternalTracing) { | ||
orchestrator = traceOrchestrator(orchestrator); | ||
} | ||
const getEnveloped = (initialContext = {}) => { | ||
// DOTAN: Maybe this could be done as part of onSchemaChange instead of here? | ||
orchestrator.prepareSchema(); | ||
if (options.enableInternalTracing) { | ||
initialContext._envelopTracing = {}; | ||
} | ||
return { | ||
parse: customParse, | ||
validate: customValidate, | ||
contextFactory: customContextFactory, | ||
execute: customExecute, | ||
subscribe: customSubscribe, | ||
get schema() { | ||
return schema; | ||
}, | ||
parse: orchestrator.parse(initialContext), | ||
validate: orchestrator.validate(initialContext), | ||
contextFactory: orchestrator.contextFactory(initialContext), | ||
execute: orchestrator.execute, | ||
subscribe: orchestrator.subscribe, | ||
schema: orchestrator.schema, | ||
}; | ||
}; | ||
envelop._plugins = plugins; | ||
return envelop; | ||
getEnveloped._plugins = options.plugins; | ||
return getEnveloped; | ||
} | ||
@@ -429,4 +570,4 @@ | ||
const HR_TO_NS = 1e9; | ||
const NS_TO_MS = 1e6; | ||
const HR_TO_NS$1 = 1e9; | ||
const NS_TO_MS$1 = 1e6; | ||
const DEFAULT_OPTIONS$1 = { | ||
@@ -440,9 +581,9 @@ onExecutionMeasurement: (args, timing) => console.log(`Operation execution "${args.operationName}" done in ${timing.ms}ms`), | ||
}; | ||
const deltaFrom = (hrtime) => { | ||
const deltaFrom$1 = (hrtime) => { | ||
const delta = process.hrtime(hrtime); | ||
const ns = delta[0] * HR_TO_NS + delta[1]; | ||
const ns = delta[0] * HR_TO_NS$1 + delta[1]; | ||
return { | ||
ns, | ||
get ms() { | ||
return ns / NS_TO_MS; | ||
return ns / NS_TO_MS$1; | ||
}, | ||
@@ -461,3 +602,3 @@ }; | ||
return () => { | ||
options.onContextBuildingMeasurement(deltaFrom(contextStartTime)); | ||
options.onContextBuildingMeasurement(deltaFrom$1(contextStartTime)); | ||
}; | ||
@@ -470,3 +611,3 @@ }; | ||
return () => { | ||
options.onParsingMeasurement(params.source, deltaFrom(parseStartTime)); | ||
options.onParsingMeasurement(params.source, deltaFrom$1(parseStartTime)); | ||
}; | ||
@@ -479,3 +620,3 @@ }; | ||
return () => { | ||
options.onValidationMeasurement(params.documentAST, deltaFrom(validateStartTime)); | ||
options.onValidationMeasurement(params.documentAST, deltaFrom$1(validateStartTime)); | ||
}; | ||
@@ -490,3 +631,3 @@ }; | ||
onExecuteDone: () => { | ||
options.onExecutionMeasurement(args, deltaFrom(executeStartTime)); | ||
options.onExecutionMeasurement(args, deltaFrom$1(executeStartTime)); | ||
}, | ||
@@ -496,3 +637,3 @@ onResolverCalled: ({ info }) => { | ||
return () => { | ||
options.onResolverMeasurement(info, deltaFrom(resolverStartTime)); | ||
options.onResolverMeasurement(info, deltaFrom$1(resolverStartTime)); | ||
}; | ||
@@ -508,3 +649,3 @@ }, | ||
onExecuteDone: () => { | ||
options.onExecutionMeasurement(args, deltaFrom(executeStartTime)); | ||
options.onExecutionMeasurement(args, deltaFrom$1(executeStartTime)); | ||
}, | ||
@@ -521,3 +662,3 @@ }; | ||
onSubscribeResult: () => { | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom(subscribeStartTime)); | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom$1(subscribeStartTime)); | ||
}, | ||
@@ -527,3 +668,3 @@ onResolverCalled: ({ info }) => { | ||
return () => { | ||
options.onResolverMeasurement && options.onResolverMeasurement(info, deltaFrom(resolverStartTime)); | ||
options.onResolverMeasurement && options.onResolverMeasurement(info, deltaFrom$1(resolverStartTime)); | ||
}; | ||
@@ -539,3 +680,3 @@ }, | ||
onSubscribeResult: () => { | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom(subscribeStartTime)); | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom$1(subscribeStartTime)); | ||
}, | ||
@@ -542,0 +683,0 @@ }; |
397
index.esm.js
export * from '@envelop/types'; | ||
import { parse, specifiedRules, validate, execute, isIntrospectionType, isObjectType, defaultFieldResolver, GraphQLSchema, subscribe, getOperationAST, GraphQLError } from 'graphql'; | ||
import { isIntrospectionType, isObjectType, defaultFieldResolver, parse, specifiedRules, validate, execute, GraphQLSchema, subscribe, getOperationAST, GraphQLError } from 'graphql'; | ||
const trackedSchemaSymbol = Symbol('TRACKED_SCHEMA'); | ||
const resolversHooksSymbol = Symbol('RESOLVERS_HOOKS'); | ||
function envelop({ plugins }) { | ||
function prepareTracedSchema(schema) { | ||
if (!schema || schema[trackedSchemaSymbol]) { | ||
return; | ||
} | ||
schema[trackedSchemaSymbol] = true; | ||
const entries = Object.values(schema.getTypeMap()); | ||
for (const type of entries) { | ||
if (!isIntrospectionType(type) && isObjectType(type)) { | ||
const fields = Object.values(type.getFields()); | ||
for (const field of fields) { | ||
const originalFn = field.resolve || defaultFieldResolver; | ||
field.resolve = async (root, args, context, info) => { | ||
if (context && context[resolversHooksSymbol]) { | ||
const hooks = context[resolversHooksSymbol]; | ||
const afterCalls = []; | ||
for (const hook of hooks) { | ||
const afterFn = await hook({ root, args, context, info }); | ||
afterFn && afterCalls.push(afterFn); | ||
} | ||
try { | ||
let result = await originalFn(root, args, context, info); | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result, | ||
setResult: newResult => { | ||
result = newResult; | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
catch (e) { | ||
let resultErr = e; | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result: resultErr, | ||
setResult: newResult => { | ||
resultErr = newResult; | ||
}, | ||
}); | ||
} | ||
throw resultErr; | ||
} | ||
} | ||
else { | ||
return originalFn(root, args, context, info); | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
function createEnvelopOrchestrator(plugins) { | ||
let schema = null; | ||
let initDone = false; | ||
// Define the initial method for replacing the GraphQL schema, this is needed in order | ||
// to allow setting the schema from the onPluginInit callback. We also need to make sure | ||
// here not to call the same plugin that initiated the schema switch. | ||
const replaceSchema = (newSchema, ignorePluginIndex = -1) => { | ||
@@ -25,2 +81,3 @@ schema = newSchema; | ||
}; | ||
// Iterate all plugins and trigger onPluginInit | ||
for (const [i, plugin] of plugins.entries()) { | ||
@@ -36,21 +93,29 @@ plugin.onPluginInit && | ||
} | ||
const onContextBuildingCbs = []; | ||
const onExecuteCbs = []; | ||
const onParseCbs = []; | ||
const onSubscribeCbs = []; | ||
const onValidateCbs = []; | ||
// A set of before callbacks defined here in order to allow it to be used later | ||
const beforeCallbacks = { | ||
parse: [], | ||
validate: [], | ||
subscribe: [], | ||
execute: [], | ||
context: [], | ||
}; | ||
for (const { onContextBuilding, onExecute, onParse, onSubscribe, onValidate } of plugins) { | ||
onContextBuilding && onContextBuildingCbs.push(onContextBuilding); | ||
onExecute && onExecuteCbs.push(onExecute); | ||
onParse && onParseCbs.push(onParse); | ||
onSubscribe && onSubscribeCbs.push(onSubscribe); | ||
onValidate && onValidateCbs.push(onValidate); | ||
onContextBuilding && beforeCallbacks.context.push(onContextBuilding); | ||
onExecute && beforeCallbacks.execute.push(onExecute); | ||
onParse && beforeCallbacks.parse.push(onParse); | ||
onSubscribe && beforeCallbacks.subscribe.push(onSubscribe); | ||
onValidate && beforeCallbacks.validate.push(onValidate); | ||
} | ||
const customParse = onParseCbs.length | ||
? (source, parseOptions) => { | ||
const customParse = beforeCallbacks.parse.length | ||
? sharedObj => (source, parseOptions) => { | ||
let result = null; | ||
let parseFn = parse; | ||
const context = sharedObj; | ||
const afterCalls = []; | ||
for (const onParse of onParseCbs) { | ||
for (const onParse of beforeCallbacks.parse) { | ||
const afterFn = onParse({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
params: { source, options: parseOptions }, | ||
@@ -77,2 +142,6 @@ parseFn, | ||
afterCb({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
replaceParseResult: newResult => { | ||
@@ -92,11 +161,16 @@ result = newResult; | ||
} | ||
: parse; | ||
const customValidate = onValidateCbs.length | ||
? (schema, documentAST, rules, typeInfo, validationOptions) => { | ||
: () => parse; | ||
const customValidate = beforeCallbacks.validate.length | ||
? initialContext => (schema, documentAST, rules, typeInfo, validationOptions) => { | ||
let actualRules = rules ? [...rules] : undefined; | ||
let validateFn = validate; | ||
let result = null; | ||
const context = initialContext; | ||
const afterCalls = []; | ||
for (const onValidate of onValidateCbs) { | ||
for (const onValidate of beforeCallbacks.validate) { | ||
const afterFn = onValidate({ | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
params: { | ||
@@ -130,24 +204,23 @@ schema, | ||
for (const afterCb of afterCalls) { | ||
afterCb({ valid, result }); | ||
afterCb({ | ||
valid, | ||
result, | ||
context, | ||
extendContext: extension => { | ||
Object.assign(context, extension); | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
: validate; | ||
const customContextFactory = onContextBuildingCbs.length | ||
? async (initialContext) => { | ||
: () => validate; | ||
const customContextFactory = beforeCallbacks.context.length | ||
? initialContext => async () => { | ||
const afterCalls = []; | ||
let context = initialContext; | ||
const afterCalls = []; | ||
for (const onContext of onContextBuildingCbs) { | ||
for (const onContext of beforeCallbacks.context) { | ||
const afterFn = await onContext({ | ||
context, | ||
extendContext: extension => { | ||
if (typeof extension === 'object') { | ||
context = { | ||
...(context || {}), | ||
...extension, | ||
}; | ||
} | ||
else { | ||
throw new Error(`Invalid context extension provided! Expected "object", got: "${JSON.stringify(extension)}" (${typeof extension})`); | ||
} | ||
context = { ...context, ...extension }; | ||
}, | ||
@@ -158,7 +231,12 @@ }); | ||
for (const afterCb of afterCalls) { | ||
afterCb({ context }); | ||
afterCb({ | ||
context, | ||
extendContext: extension => { | ||
context = { ...context, ...extension }; | ||
}, | ||
}); | ||
} | ||
return context; | ||
} | ||
: (ctx) => ctx; | ||
: ctx => () => ctx; | ||
const customSubscribe = async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => { | ||
@@ -180,4 +258,4 @@ const args = argsOrSchema instanceof GraphQLSchema | ||
const afterCalls = []; | ||
let context = args.contextValue; | ||
for (const onSubscribe of onSubscribeCbs) { | ||
let context = args.contextValue || {}; | ||
for (const onSubscribe of beforeCallbacks.subscribe) { | ||
const after = onSubscribe({ | ||
@@ -189,11 +267,3 @@ subscribeFn, | ||
extendContext: extension => { | ||
if (typeof extension === 'object') { | ||
context = { | ||
...(context || {}), | ||
...extension, | ||
}; | ||
} | ||
else { | ||
throw new Error(`Invalid context extension provided! Expected "object", got: "${JSON.stringify(extension)}" (${typeof extension})`); | ||
} | ||
context = { ...context, ...extension }; | ||
}, | ||
@@ -228,3 +298,3 @@ args, | ||
}; | ||
const customExecute = onExecuteCbs.length | ||
const customExecute = beforeCallbacks.execute.length | ||
? async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => { | ||
@@ -247,4 +317,4 @@ const args = argsOrSchema instanceof GraphQLSchema | ||
const afterCalls = []; | ||
let context = args.contextValue; | ||
for (const onExecute of onExecuteCbs) { | ||
let context = args.contextValue || {}; | ||
for (const onExecute of beforeCallbacks.execute) { | ||
let stopCalled = false; | ||
@@ -303,54 +373,2 @@ const after = onExecute({ | ||
: execute; | ||
function prepareSchema() { | ||
if (!schema || schema[trackedSchemaSymbol]) { | ||
return; | ||
} | ||
schema[trackedSchemaSymbol] = true; | ||
const entries = Object.values(schema.getTypeMap()); | ||
for (const type of entries) { | ||
if (!isIntrospectionType(type) && isObjectType(type)) { | ||
const fields = Object.values(type.getFields()); | ||
for (const field of fields) { | ||
const originalFn = field.resolve || defaultFieldResolver; | ||
field.resolve = async (root, args, context, info) => { | ||
if (context && context[resolversHooksSymbol]) { | ||
const hooks = context[resolversHooksSymbol]; | ||
const afterCalls = []; | ||
for (const hook of hooks) { | ||
const afterFn = await hook({ root, args, context, info }); | ||
afterFn && afterCalls.push(afterFn); | ||
} | ||
try { | ||
let result = await originalFn(root, args, context, info); | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result, | ||
setResult: newResult => { | ||
result = newResult; | ||
}, | ||
}); | ||
} | ||
return result; | ||
} | ||
catch (e) { | ||
let resultErr = e; | ||
for (const afterFn of afterCalls) { | ||
afterFn({ | ||
result: resultErr, | ||
setResult: newResult => { | ||
resultErr = newResult; | ||
}, | ||
}); | ||
} | ||
throw resultErr; | ||
} | ||
} | ||
else { | ||
return originalFn(root, args, context, info); | ||
} | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
initDone = true; | ||
@@ -368,17 +386,140 @@ // This is done in order to trigger the first schema available, to allow plugins that needs the schema | ||
} | ||
const envelop = () => { | ||
prepareSchema(); | ||
return { | ||
get schema() { | ||
return schema; | ||
}, | ||
prepareSchema: () => prepareTracedSchema(schema), | ||
parse: customParse, | ||
validate: customValidate, | ||
execute: customExecute, | ||
subscribe: customSubscribe, | ||
contextFactory: customContextFactory, | ||
}; | ||
} | ||
const HR_TO_NS = 1e9; | ||
const NS_TO_MS = 1e6; | ||
const deltaFrom = (hrtime) => { | ||
const delta = process.hrtime(hrtime); | ||
const ns = delta[0] * HR_TO_NS + delta[1]; | ||
return ns / NS_TO_MS; | ||
}; | ||
function traceOrchestrator(orchestrator) { | ||
const createTracer = (name, ctx) => { | ||
const hrtime = process.hrtime(); | ||
return () => { | ||
const time = deltaFrom(hrtime); | ||
ctx._envelopTracing[name] = time; | ||
}; | ||
}; | ||
return { | ||
...orchestrator, | ||
parse: (ctx = {}) => { | ||
const actualFn = orchestrator.parse(ctx); | ||
return (...args) => { | ||
const done = createTracer('parse', ctx); | ||
try { | ||
return actualFn(...args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
validate: (ctx = {}) => { | ||
const actualFn = orchestrator.validate(ctx); | ||
return (...args) => { | ||
const done = createTracer('validate', ctx); | ||
try { | ||
return actualFn(...args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
execute: async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, typeResolver) => { | ||
const args = argsOrSchema instanceof GraphQLSchema | ||
? { | ||
schema: argsOrSchema, | ||
document: document, | ||
rootValue, | ||
contextValue, | ||
variableValues, | ||
operationName, | ||
fieldResolver, | ||
typeResolver, | ||
} | ||
: argsOrSchema; | ||
const done = createTracer('execute', args.contextValue || {}); | ||
try { | ||
const result = await orchestrator.execute(args); | ||
done(); | ||
result.extensions = result.extensions || {}; | ||
result.extensions.envelopTracing = args.contextValue._envelopTracing; | ||
return result; | ||
} | ||
catch (e) { | ||
done(); | ||
throw e; | ||
} | ||
}, | ||
subscribe: async (argsOrSchema, document, rootValue, contextValue, variableValues, operationName, fieldResolver, subscribeFieldResolver) => { | ||
const args = argsOrSchema instanceof GraphQLSchema | ||
? { | ||
schema: argsOrSchema, | ||
document: document, | ||
rootValue, | ||
contextValue, | ||
variableValues, | ||
operationName, | ||
fieldResolver, | ||
subscribeFieldResolver, | ||
} | ||
: argsOrSchema; | ||
const done = createTracer('subscribe', args.contextValue || {}); | ||
try { | ||
return await orchestrator.subscribe(args); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}, | ||
contextFactory: (ctx = {}) => { | ||
const actualFn = orchestrator.contextFactory(ctx); | ||
return async () => { | ||
const done = createTracer('contextFactory', ctx); | ||
try { | ||
return await actualFn(); | ||
} | ||
finally { | ||
done(); | ||
} | ||
}; | ||
}, | ||
}; | ||
} | ||
function envelop(options) { | ||
let orchestrator = createEnvelopOrchestrator(options.plugins); | ||
if (options.enableInternalTracing) { | ||
orchestrator = traceOrchestrator(orchestrator); | ||
} | ||
const getEnveloped = (initialContext = {}) => { | ||
// DOTAN: Maybe this could be done as part of onSchemaChange instead of here? | ||
orchestrator.prepareSchema(); | ||
if (options.enableInternalTracing) { | ||
initialContext._envelopTracing = {}; | ||
} | ||
return { | ||
parse: customParse, | ||
validate: customValidate, | ||
contextFactory: customContextFactory, | ||
execute: customExecute, | ||
subscribe: customSubscribe, | ||
get schema() { | ||
return schema; | ||
}, | ||
parse: orchestrator.parse(initialContext), | ||
validate: orchestrator.validate(initialContext), | ||
contextFactory: orchestrator.contextFactory(initialContext), | ||
execute: orchestrator.execute, | ||
subscribe: orchestrator.subscribe, | ||
schema: orchestrator.schema, | ||
}; | ||
}; | ||
envelop._plugins = plugins; | ||
return envelop; | ||
getEnveloped._plugins = options.plugins; | ||
return getEnveloped; | ||
} | ||
@@ -424,4 +565,4 @@ | ||
const HR_TO_NS = 1e9; | ||
const NS_TO_MS = 1e6; | ||
const HR_TO_NS$1 = 1e9; | ||
const NS_TO_MS$1 = 1e6; | ||
const DEFAULT_OPTIONS$1 = { | ||
@@ -435,9 +576,9 @@ onExecutionMeasurement: (args, timing) => console.log(`Operation execution "${args.operationName}" done in ${timing.ms}ms`), | ||
}; | ||
const deltaFrom = (hrtime) => { | ||
const deltaFrom$1 = (hrtime) => { | ||
const delta = process.hrtime(hrtime); | ||
const ns = delta[0] * HR_TO_NS + delta[1]; | ||
const ns = delta[0] * HR_TO_NS$1 + delta[1]; | ||
return { | ||
ns, | ||
get ms() { | ||
return ns / NS_TO_MS; | ||
return ns / NS_TO_MS$1; | ||
}, | ||
@@ -456,3 +597,3 @@ }; | ||
return () => { | ||
options.onContextBuildingMeasurement(deltaFrom(contextStartTime)); | ||
options.onContextBuildingMeasurement(deltaFrom$1(contextStartTime)); | ||
}; | ||
@@ -465,3 +606,3 @@ }; | ||
return () => { | ||
options.onParsingMeasurement(params.source, deltaFrom(parseStartTime)); | ||
options.onParsingMeasurement(params.source, deltaFrom$1(parseStartTime)); | ||
}; | ||
@@ -474,3 +615,3 @@ }; | ||
return () => { | ||
options.onValidationMeasurement(params.documentAST, deltaFrom(validateStartTime)); | ||
options.onValidationMeasurement(params.documentAST, deltaFrom$1(validateStartTime)); | ||
}; | ||
@@ -485,3 +626,3 @@ }; | ||
onExecuteDone: () => { | ||
options.onExecutionMeasurement(args, deltaFrom(executeStartTime)); | ||
options.onExecutionMeasurement(args, deltaFrom$1(executeStartTime)); | ||
}, | ||
@@ -491,3 +632,3 @@ onResolverCalled: ({ info }) => { | ||
return () => { | ||
options.onResolverMeasurement(info, deltaFrom(resolverStartTime)); | ||
options.onResolverMeasurement(info, deltaFrom$1(resolverStartTime)); | ||
}; | ||
@@ -503,3 +644,3 @@ }, | ||
onExecuteDone: () => { | ||
options.onExecutionMeasurement(args, deltaFrom(executeStartTime)); | ||
options.onExecutionMeasurement(args, deltaFrom$1(executeStartTime)); | ||
}, | ||
@@ -516,3 +657,3 @@ }; | ||
onSubscribeResult: () => { | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom(subscribeStartTime)); | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom$1(subscribeStartTime)); | ||
}, | ||
@@ -522,3 +663,3 @@ onResolverCalled: ({ info }) => { | ||
return () => { | ||
options.onResolverMeasurement && options.onResolverMeasurement(info, deltaFrom(resolverStartTime)); | ||
options.onResolverMeasurement && options.onResolverMeasurement(info, deltaFrom$1(resolverStartTime)); | ||
}; | ||
@@ -534,3 +675,3 @@ }, | ||
onSubscribeResult: () => { | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom(subscribeStartTime)); | ||
options.onSubscriptionMeasurement && options.onSubscriptionMeasurement(args, deltaFrom$1(subscribeStartTime)); | ||
}, | ||
@@ -537,0 +678,0 @@ }; |
{ | ||
"name": "@envelop/core", | ||
"version": "0.2.2", | ||
"version": "0.3.0-alpha-5108c55.0", | ||
"sideEffects": false, | ||
@@ -9,3 +9,3 @@ "peerDependencies": { | ||
"dependencies": { | ||
"@envelop/types": "0.1.4" | ||
"@envelop/types": "0.2.0-alpha-5108c55.0" | ||
}, | ||
@@ -12,0 +12,0 @@ "repository": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
168459
19
1534
+ Added@envelop/types@0.2.0-alpha-5108c55.0(transitive)
- Removed@envelop/types@0.1.4(transitive)