@apollo/server
Advanced tools
Comparing version 4.0.5 to 4.1.0
@@ -582,2 +582,3 @@ "use strict"; | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead: null, | ||
}, options); | ||
@@ -588,3 +589,3 @@ return response; | ||
exports.ApolloServer = ApolloServer; | ||
async function internalExecuteOperation({ server, graphQLRequest, internals, schemaDerivedData, }, options) { | ||
async function internalExecuteOperation({ server, graphQLRequest, internals, schemaDerivedData, sharedResponseHTTPGraphQLHead, }, options) { | ||
const requestContext = { | ||
@@ -596,3 +597,3 @@ logger: server.logger, | ||
response: { | ||
http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(), | ||
http: sharedResponseHTTPGraphQLHead ?? (0, runHttpQuery_js_1.newHTTPGraphQLHead)(), | ||
}, | ||
@@ -602,2 +603,3 @@ contextValue: cloneObject(options?.contextValue ?? {}), | ||
overallCachePolicy: (0, cachePolicy_js_1.newCachePolicy)(), | ||
requestIsBatched: sharedResponseHTTPGraphQLHead !== null, | ||
}; | ||
@@ -604,0 +606,0 @@ try { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.packageVersion = void 0; | ||
exports.packageVersion = "4.0.5"; | ||
exports.packageVersion = "4.1.0"; | ||
//# sourceMappingURL=packageVersion.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.runPotentiallyBatchedHttpQuery = exports.runBatchHttpQuery = void 0; | ||
exports.runPotentiallyBatchedHttpQuery = void 0; | ||
const runHttpQuery_js_1 = require("./runHttpQuery.js"); | ||
const internalErrorClasses_js_1 = require("./internalErrorClasses.js"); | ||
async function runBatchHttpQuery(server, batchRequest, body, contextValue, schemaDerivedData, internals) { | ||
async function runBatchedHttpQuery({ server, batchRequest, body, contextValue, schemaDerivedData, internals, }) { | ||
if (body.length === 0) { | ||
throw new internalErrorClasses_js_1.BadRequestError('No operations found in request.'); | ||
} | ||
const combinedResponseHead = (0, runHttpQuery_js_1.newHTTPGraphQLHead)(); | ||
const sharedResponseHTTPGraphQLHead = (0, runHttpQuery_js_1.newHTTPGraphQLHead)(); | ||
const responseBodies = await Promise.all(body.map(async (bodyPiece) => { | ||
@@ -16,27 +16,41 @@ const singleRequest = { | ||
}; | ||
const response = await (0, runHttpQuery_js_1.runHttpQuery)(server, singleRequest, contextValue, schemaDerivedData, internals); | ||
const response = await (0, runHttpQuery_js_1.runHttpQuery)({ | ||
server, | ||
httpRequest: singleRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
sharedResponseHTTPGraphQLHead, | ||
}); | ||
if (response.body.kind === 'chunked') { | ||
throw Error('Incremental delivery is not implemented for batch requests'); | ||
} | ||
for (const [key, value] of response.headers) { | ||
combinedResponseHead.headers.set(key, value); | ||
} | ||
if (response.status) { | ||
combinedResponseHead.status = response.status; | ||
} | ||
return response.body.string; | ||
})); | ||
return { | ||
...combinedResponseHead, | ||
...sharedResponseHTTPGraphQLHead, | ||
body: { kind: 'complete', string: `[${responseBodies.join(',')}]` }, | ||
}; | ||
} | ||
exports.runBatchHttpQuery = runBatchHttpQuery; | ||
async function runPotentiallyBatchedHttpQuery(server, httpGraphQLRequest, contextValue, schemaDerivedData, internals) { | ||
if (!(httpGraphQLRequest.method === 'POST' && | ||
Array.isArray(httpGraphQLRequest.body))) { | ||
return await (0, runHttpQuery_js_1.runHttpQuery)(server, httpGraphQLRequest, contextValue, schemaDerivedData, internals); | ||
return await (0, runHttpQuery_js_1.runHttpQuery)({ | ||
server, | ||
httpRequest: httpGraphQLRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
sharedResponseHTTPGraphQLHead: null, | ||
}); | ||
} | ||
if (internals.allowBatchedHttpRequests) { | ||
return await runBatchHttpQuery(server, httpGraphQLRequest, httpGraphQLRequest.body, contextValue, schemaDerivedData, internals); | ||
return await runBatchedHttpQuery({ | ||
server, | ||
batchRequest: httpGraphQLRequest, | ||
body: httpGraphQLRequest.body, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
}); | ||
} | ||
@@ -43,0 +57,0 @@ throw new internalErrorClasses_js_1.BadRequestError('Operation batching disabled.'); |
@@ -124,12 +124,20 @@ "use strict"; | ||
const { response, overallCachePolicy } = requestContext; | ||
const policyIfCacheable = overallCachePolicy.policyIfCacheable(); | ||
const existingCacheControlHeader = parseExistingCacheControlHeader(response.http.headers.get('cache-control')); | ||
if (existingCacheControlHeader.kind === 'unparsable') { | ||
return; | ||
} | ||
const cachePolicy = (0, cachePolicy_js_1.newCachePolicy)(); | ||
cachePolicy.replace(overallCachePolicy); | ||
if (existingCacheControlHeader.kind === 'parsable-and-cacheable') { | ||
cachePolicy.restrict(existingCacheControlHeader.hint); | ||
} | ||
const policyIfCacheable = cachePolicy.policyIfCacheable(); | ||
if (policyIfCacheable && | ||
existingCacheControlHeader.kind !== 'uncacheable' && | ||
response.body.kind === 'single' && | ||
!response.body.singleResult.errors && | ||
response.http) { | ||
!response.body.singleResult.errors) { | ||
response.http.headers.set('cache-control', `max-age=${policyIfCacheable.maxAge}, ${policyIfCacheable.scope.toLowerCase()}`); | ||
} | ||
else if (calculateHttpHeaders !== 'if-cacheable' && | ||
!response.http.headers.has('cache-control')) { | ||
response.http.headers.set('cache-control', 'no-store'); | ||
else if (calculateHttpHeaders !== 'if-cacheable') { | ||
response.http.headers.set('cache-control', CACHE_CONTROL_HEADER_UNCACHEABLE); | ||
} | ||
@@ -142,2 +150,23 @@ }, | ||
exports.ApolloServerPluginCacheControl = ApolloServerPluginCacheControl; | ||
const CACHE_CONTROL_HEADER_CACHEABLE_REGEXP = /^max-age=(\d+), (public|private)$/; | ||
const CACHE_CONTROL_HEADER_UNCACHEABLE = 'no-store'; | ||
function parseExistingCacheControlHeader(header) { | ||
if (!header) { | ||
return { kind: 'no-header' }; | ||
} | ||
if (header === CACHE_CONTROL_HEADER_UNCACHEABLE) { | ||
return { kind: 'uncacheable' }; | ||
} | ||
const match = CACHE_CONTROL_HEADER_CACHEABLE_REGEXP.exec(header); | ||
if (!match) { | ||
return { kind: 'unparsable' }; | ||
} | ||
return { | ||
kind: 'parsable-and-cacheable', | ||
hint: { | ||
maxAge: +match[1], | ||
scope: match[2] === 'public' ? 'PUBLIC' : 'PRIVATE', | ||
}, | ||
}; | ||
} | ||
function cacheAnnotationFromDirectives(directives) { | ||
@@ -144,0 +173,0 @@ if (!directives) |
@@ -77,3 +77,3 @@ "use strict"; | ||
} | ||
async function runHttpQuery(server, httpRequest, contextValue, schemaDerivedData, internals) { | ||
async function runHttpQuery({ server, httpRequest, contextValue, schemaDerivedData, internals, sharedResponseHTTPGraphQLHead, }) { | ||
let graphQLRequest; | ||
@@ -127,2 +127,3 @@ switch (httpRequest.method) { | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead, | ||
}, { contextValue }); | ||
@@ -129,0 +130,0 @@ if (graphQLResponse.body.kind === 'single') { |
@@ -93,3 +93,3 @@ import type { Logger } from '@apollo/utils.logger'; | ||
} | ||
export declare function internalExecuteOperation<TContext extends BaseContext>({ server, graphQLRequest, internals, schemaDerivedData, }: { | ||
export declare function internalExecuteOperation<TContext extends BaseContext>({ server, graphQLRequest, internals, schemaDerivedData, sharedResponseHTTPGraphQLHead, }: { | ||
server: ApolloServer<TContext>; | ||
@@ -99,2 +99,3 @@ graphQLRequest: GraphQLRequest; | ||
schemaDerivedData: SchemaDerivedData; | ||
sharedResponseHTTPGraphQLHead: HTTPGraphQLHead | null; | ||
}, options: ExecuteOperationOptions<TContext>): Promise<GraphQLResponse>; | ||
@@ -101,0 +102,0 @@ export declare type ImplicitlyInstallablePlugin<TContext extends BaseContext> = ApolloServerPlugin<TContext> & { |
@@ -553,2 +553,3 @@ import { isNodeLike } from '@apollo/utils.isnodelike'; | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead: null, | ||
}, options); | ||
@@ -558,3 +559,3 @@ return response; | ||
} | ||
export async function internalExecuteOperation({ server, graphQLRequest, internals, schemaDerivedData, }, options) { | ||
export async function internalExecuteOperation({ server, graphQLRequest, internals, schemaDerivedData, sharedResponseHTTPGraphQLHead, }, options) { | ||
const requestContext = { | ||
@@ -566,3 +567,3 @@ logger: server.logger, | ||
response: { | ||
http: newHTTPGraphQLHead(), | ||
http: sharedResponseHTTPGraphQLHead ?? newHTTPGraphQLHead(), | ||
}, | ||
@@ -572,2 +573,3 @@ contextValue: cloneObject(options?.contextValue ?? {}), | ||
overallCachePolicy: newCachePolicy(), | ||
requestIsBatched: sharedResponseHTTPGraphQLHead !== null, | ||
}; | ||
@@ -574,0 +576,0 @@ try { |
@@ -34,2 +34,3 @@ import type { WithRequired } from '@apollo/utils.withrequired'; | ||
readonly overallCachePolicy: CachePolicy; | ||
readonly requestIsBatched: boolean; | ||
} | ||
@@ -36,0 +37,0 @@ export declare type GraphQLRequestContextDidResolveSource<TContext extends BaseContext> = WithRequired<GraphQLRequestContext<TContext>, 'source' | 'queryHash'>; |
@@ -1,2 +0,2 @@ | ||
export declare const packageVersion = "4.0.5"; | ||
export declare const packageVersion = "4.1.0"; | ||
//# sourceMappingURL=packageVersion.d.ts.map |
@@ -1,2 +0,2 @@ | ||
export const packageVersion = "4.0.5"; | ||
export const packageVersion = "4.1.0"; | ||
//# sourceMappingURL=packageVersion.js.map |
import type { BaseContext, HTTPGraphQLRequest, HTTPGraphQLResponse } from './externalTypes/index.js'; | ||
import type { ApolloServer, ApolloServerInternals, SchemaDerivedData } from './ApolloServer'; | ||
export declare function runBatchHttpQuery<TContext extends BaseContext>(server: ApolloServer<TContext>, batchRequest: HTTPGraphQLRequest, body: unknown[], contextValue: TContext, schemaDerivedData: SchemaDerivedData, internals: ApolloServerInternals<TContext>): Promise<HTTPGraphQLResponse>; | ||
export declare function runPotentiallyBatchedHttpQuery<TContext extends BaseContext>(server: ApolloServer<TContext>, httpGraphQLRequest: HTTPGraphQLRequest, contextValue: TContext, schemaDerivedData: SchemaDerivedData, internals: ApolloServerInternals<TContext>): Promise<HTTPGraphQLResponse>; | ||
//# sourceMappingURL=httpBatching.d.ts.map |
import { newHTTPGraphQLHead, runHttpQuery } from './runHttpQuery.js'; | ||
import { BadRequestError } from './internalErrorClasses.js'; | ||
export async function runBatchHttpQuery(server, batchRequest, body, contextValue, schemaDerivedData, internals) { | ||
async function runBatchedHttpQuery({ server, batchRequest, body, contextValue, schemaDerivedData, internals, }) { | ||
if (body.length === 0) { | ||
throw new BadRequestError('No operations found in request.'); | ||
} | ||
const combinedResponseHead = newHTTPGraphQLHead(); | ||
const sharedResponseHTTPGraphQLHead = newHTTPGraphQLHead(); | ||
const responseBodies = await Promise.all(body.map(async (bodyPiece) => { | ||
@@ -13,16 +13,17 @@ const singleRequest = { | ||
}; | ||
const response = await runHttpQuery(server, singleRequest, contextValue, schemaDerivedData, internals); | ||
const response = await runHttpQuery({ | ||
server, | ||
httpRequest: singleRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
sharedResponseHTTPGraphQLHead, | ||
}); | ||
if (response.body.kind === 'chunked') { | ||
throw Error('Incremental delivery is not implemented for batch requests'); | ||
} | ||
for (const [key, value] of response.headers) { | ||
combinedResponseHead.headers.set(key, value); | ||
} | ||
if (response.status) { | ||
combinedResponseHead.status = response.status; | ||
} | ||
return response.body.string; | ||
})); | ||
return { | ||
...combinedResponseHead, | ||
...sharedResponseHTTPGraphQLHead, | ||
body: { kind: 'complete', string: `[${responseBodies.join(',')}]` }, | ||
@@ -34,6 +35,20 @@ }; | ||
Array.isArray(httpGraphQLRequest.body))) { | ||
return await runHttpQuery(server, httpGraphQLRequest, contextValue, schemaDerivedData, internals); | ||
return await runHttpQuery({ | ||
server, | ||
httpRequest: httpGraphQLRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
sharedResponseHTTPGraphQLHead: null, | ||
}); | ||
} | ||
if (internals.allowBatchedHttpRequests) { | ||
return await runBatchHttpQuery(server, httpGraphQLRequest, httpGraphQLRequest.body, contextValue, schemaDerivedData, internals); | ||
return await runBatchedHttpQuery({ | ||
server, | ||
batchRequest: httpGraphQLRequest, | ||
body: httpGraphQLRequest.body, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
}); | ||
} | ||
@@ -40,0 +55,0 @@ throw new BadRequestError('Operation batching disabled.'); |
@@ -118,12 +118,20 @@ import { getNamedType, isCompositeType, isInterfaceType, isObjectType, responsePathAsArray, } from 'graphql'; | ||
const { response, overallCachePolicy } = requestContext; | ||
const policyIfCacheable = overallCachePolicy.policyIfCacheable(); | ||
const existingCacheControlHeader = parseExistingCacheControlHeader(response.http.headers.get('cache-control')); | ||
if (existingCacheControlHeader.kind === 'unparsable') { | ||
return; | ||
} | ||
const cachePolicy = newCachePolicy(); | ||
cachePolicy.replace(overallCachePolicy); | ||
if (existingCacheControlHeader.kind === 'parsable-and-cacheable') { | ||
cachePolicy.restrict(existingCacheControlHeader.hint); | ||
} | ||
const policyIfCacheable = cachePolicy.policyIfCacheable(); | ||
if (policyIfCacheable && | ||
existingCacheControlHeader.kind !== 'uncacheable' && | ||
response.body.kind === 'single' && | ||
!response.body.singleResult.errors && | ||
response.http) { | ||
!response.body.singleResult.errors) { | ||
response.http.headers.set('cache-control', `max-age=${policyIfCacheable.maxAge}, ${policyIfCacheable.scope.toLowerCase()}`); | ||
} | ||
else if (calculateHttpHeaders !== 'if-cacheable' && | ||
!response.http.headers.has('cache-control')) { | ||
response.http.headers.set('cache-control', 'no-store'); | ||
else if (calculateHttpHeaders !== 'if-cacheable') { | ||
response.http.headers.set('cache-control', CACHE_CONTROL_HEADER_UNCACHEABLE); | ||
} | ||
@@ -135,2 +143,23 @@ }, | ||
} | ||
const CACHE_CONTROL_HEADER_CACHEABLE_REGEXP = /^max-age=(\d+), (public|private)$/; | ||
const CACHE_CONTROL_HEADER_UNCACHEABLE = 'no-store'; | ||
function parseExistingCacheControlHeader(header) { | ||
if (!header) { | ||
return { kind: 'no-header' }; | ||
} | ||
if (header === CACHE_CONTROL_HEADER_UNCACHEABLE) { | ||
return { kind: 'uncacheable' }; | ||
} | ||
const match = CACHE_CONTROL_HEADER_CACHEABLE_REGEXP.exec(header); | ||
if (!match) { | ||
return { kind: 'unparsable' }; | ||
} | ||
return { | ||
kind: 'parsable-and-cacheable', | ||
hint: { | ||
maxAge: +match[1], | ||
scope: match[2] === 'public' ? 'PUBLIC' : 'PRIVATE', | ||
}, | ||
}; | ||
} | ||
function cacheAnnotationFromDirectives(directives) { | ||
@@ -137,0 +166,0 @@ if (!directives) |
import type { BaseContext, HTTPGraphQLHead, HTTPGraphQLRequest, HTTPGraphQLResponse } from './externalTypes/index.js'; | ||
import { ApolloServer, ApolloServerInternals, SchemaDerivedData } from './ApolloServer.js'; | ||
import { FormattedExecutionResult } from 'graphql'; | ||
export declare function runHttpQuery<TContext extends BaseContext>(server: ApolloServer<TContext>, httpRequest: HTTPGraphQLRequest, contextValue: TContext, schemaDerivedData: SchemaDerivedData, internals: ApolloServerInternals<TContext>): Promise<HTTPGraphQLResponse>; | ||
export declare function runHttpQuery<TContext extends BaseContext>({ server, httpRequest, contextValue, schemaDerivedData, internals, sharedResponseHTTPGraphQLHead, }: { | ||
server: ApolloServer<TContext>; | ||
httpRequest: HTTPGraphQLRequest; | ||
contextValue: TContext; | ||
schemaDerivedData: SchemaDerivedData; | ||
internals: ApolloServerInternals<TContext>; | ||
sharedResponseHTTPGraphQLHead: HTTPGraphQLHead | null; | ||
}): Promise<HTTPGraphQLResponse>; | ||
export declare function prettyJSONStringify(value: FormattedExecutionResult): string; | ||
@@ -6,0 +13,0 @@ export declare function newHTTPGraphQLHead(status?: number): HTTPGraphQLHead; |
@@ -71,3 +71,3 @@ import { chooseContentTypeForSingleResultResponse, internalExecuteOperation, MEDIA_TYPES, } from './ApolloServer.js'; | ||
} | ||
export async function runHttpQuery(server, httpRequest, contextValue, schemaDerivedData, internals) { | ||
export async function runHttpQuery({ server, httpRequest, contextValue, schemaDerivedData, internals, sharedResponseHTTPGraphQLHead, }) { | ||
let graphQLRequest; | ||
@@ -121,2 +121,3 @@ switch (httpRequest.method) { | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead, | ||
}, { contextValue }); | ||
@@ -123,0 +124,0 @@ if (graphQLResponse.body.kind === 'single') { |
{ | ||
"name": "@apollo/server", | ||
"version": "4.0.5", | ||
"version": "4.1.0", | ||
"description": "Core engine for Apollo GraphQL server", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -1200,2 +1200,3 @@ import { isNodeLike } from '@apollo/utils.isnodelike'; | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead: null, | ||
}, | ||
@@ -1219,2 +1220,3 @@ options, | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead, | ||
}: { | ||
@@ -1225,2 +1227,3 @@ server: ApolloServer<TContext>; | ||
schemaDerivedData: SchemaDerivedData; | ||
sharedResponseHTTPGraphQLHead: HTTPGraphQLHead | null; | ||
}, | ||
@@ -1235,3 +1238,3 @@ options: ExecuteOperationOptions<TContext>, | ||
response: { | ||
http: newHTTPGraphQLHead(), | ||
http: sharedResponseHTTPGraphQLHead ?? newHTTPGraphQLHead(), | ||
}, | ||
@@ -1256,2 +1259,3 @@ // We clone the context because there are some assumptions that every operation | ||
overallCachePolicy: newCachePolicy(), | ||
requestIsBatched: sharedResponseHTTPGraphQLHead !== null, | ||
}; | ||
@@ -1258,0 +1262,0 @@ |
@@ -73,2 +73,9 @@ // This file defines the GraphQLRequestContext type which is an argument to most | ||
readonly overallCachePolicy: CachePolicy; | ||
/** | ||
* True if this request is part of a potentially multi-operation batch. Note | ||
* that if this is true, `response.http` will be shared with the other | ||
* operations in the batch. | ||
*/ | ||
readonly requestIsBatched: boolean; | ||
} | ||
@@ -75,0 +82,0 @@ |
@@ -1,1 +0,1 @@ | ||
export const packageVersion = "4.0.5"; | ||
export const packageVersion = "4.1.0"; |
@@ -14,10 +14,17 @@ import type { | ||
export async function runBatchHttpQuery<TContext extends BaseContext>( | ||
server: ApolloServer<TContext>, | ||
batchRequest: HTTPGraphQLRequest, | ||
body: unknown[], | ||
contextValue: TContext, | ||
schemaDerivedData: SchemaDerivedData, | ||
internals: ApolloServerInternals<TContext>, | ||
): Promise<HTTPGraphQLResponse> { | ||
async function runBatchedHttpQuery<TContext extends BaseContext>({ | ||
server, | ||
batchRequest, | ||
body, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
}: { | ||
server: ApolloServer<TContext>; | ||
batchRequest: HTTPGraphQLRequest; | ||
body: unknown[]; | ||
contextValue: TContext; | ||
schemaDerivedData: SchemaDerivedData; | ||
internals: ApolloServerInternals<TContext>; | ||
}): Promise<HTTPGraphQLResponse> { | ||
if (body.length === 0) { | ||
@@ -27,3 +34,9 @@ throw new BadRequestError('No operations found in request.'); | ||
const combinedResponseHead = newHTTPGraphQLHead(); | ||
// This single HTTPGraphQLHead is shared across all the operations in the | ||
// batch. This means that any changes to response headers or status code from | ||
// one operation can be immediately seen by other operations. Plugins that set | ||
// response headers or status code can then choose to combine the data they | ||
// are setting with data that may already be there from another operation as | ||
// they choose. | ||
const sharedResponseHTTPGraphQLHead = newHTTPGraphQLHead(); | ||
const responseBodies = await Promise.all( | ||
@@ -36,9 +49,10 @@ body.map(async (bodyPiece: unknown) => { | ||
const response = await runHttpQuery( | ||
const response = await runHttpQuery({ | ||
server, | ||
singleRequest, | ||
httpRequest: singleRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
); | ||
sharedResponseHTTPGraphQLHead, | ||
}); | ||
@@ -50,11 +64,2 @@ if (response.body.kind === 'chunked') { | ||
} | ||
for (const [key, value] of response.headers) { | ||
// Override any similar header set in other responses. | ||
combinedResponseHead.headers.set(key, value); | ||
} | ||
// If two responses both want to set the status code, one of them will win. | ||
// Note that the normal success case leaves status empty. | ||
if (response.status) { | ||
combinedResponseHead.status = response.status; | ||
} | ||
return response.body.string; | ||
@@ -64,3 +69,3 @@ }), | ||
return { | ||
...combinedResponseHead, | ||
...sharedResponseHTTPGraphQLHead, | ||
body: { kind: 'complete', string: `[${responseBodies.join(',')}]` }, | ||
@@ -85,21 +90,22 @@ }; | ||
) { | ||
return await runHttpQuery( | ||
return await runHttpQuery({ | ||
server, | ||
httpGraphQLRequest, | ||
httpRequest: httpGraphQLRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
); | ||
sharedResponseHTTPGraphQLHead: null, | ||
}); | ||
} | ||
if (internals.allowBatchedHttpRequests) { | ||
return await runBatchHttpQuery( | ||
return await runBatchedHttpQuery({ | ||
server, | ||
httpGraphQLRequest, | ||
httpGraphQLRequest.body as unknown[], | ||
batchRequest: httpGraphQLRequest, | ||
body: httpGraphQLRequest.body as unknown[], | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
); | ||
}); | ||
} | ||
throw new BadRequestError('Operation batching disabled.'); | ||
} |
@@ -275,9 +275,36 @@ import type { ApolloServerPlugin } from '../../externalTypes/index.js'; | ||
const policyIfCacheable = overallCachePolicy.policyIfCacheable(); | ||
// Look to see if something has already set the cache-control header. | ||
// This could be a different plugin... or it could be this very plugin | ||
// operating on a different operation in the same batched HTTP | ||
// request. | ||
const existingCacheControlHeader = parseExistingCacheControlHeader( | ||
response.http.headers.get('cache-control'), | ||
); | ||
// If the feature is enabled, there is a non-trivial cache policy, | ||
// there are no errors, and we actually can write headers, write the | ||
// header. | ||
// If the header contains something other than a value that this | ||
// plugin sets, then we leave it alone. We don't want to mangle | ||
// something important that you set! That said, it's probably best to | ||
// have only one piece of code that writes to a given header, so you | ||
// should probably set `calculateHttpHeaders: false` on this plugin. | ||
if (existingCacheControlHeader.kind === 'unparsable') { | ||
return; | ||
} | ||
const cachePolicy = newCachePolicy(); | ||
cachePolicy.replace(overallCachePolicy); | ||
if (existingCacheControlHeader.kind === 'parsable-and-cacheable') { | ||
cachePolicy.restrict(existingCacheControlHeader.hint); | ||
} | ||
const policyIfCacheable = cachePolicy.policyIfCacheable(); | ||
if ( | ||
// This code path is only for if we believe it is cacheable. | ||
policyIfCacheable && | ||
// Either there wasn't a cache-control header already, or we've | ||
// already incorporated it into policyIfCacheable. (If we couldn't | ||
// parse it, that means some other plugin or mechanism set the | ||
// header. This is confusing, so we just don't make any more | ||
// changes. You should probably set `calculateHttpHeaders` to false | ||
// in that case and only set the header from one place.) | ||
existingCacheControlHeader.kind !== 'uncacheable' && | ||
// At least for now, we don't set cache-control headers for | ||
@@ -289,4 +316,3 @@ // incremental delivery responses, since we don't know if a later | ||
response.body.kind === 'single' && | ||
!response.body.singleResult.errors && | ||
response.http | ||
!response.body.singleResult.errors | ||
) { | ||
@@ -299,10 +325,12 @@ response.http.headers.set( | ||
); | ||
} else if ( | ||
calculateHttpHeaders !== 'if-cacheable' && | ||
!response.http.headers.has('cache-control') | ||
) { | ||
} else if (calculateHttpHeaders !== 'if-cacheable') { | ||
// The response is not cacheable, so make sure it doesn't get | ||
// cached. This is especially important for GET requests, because | ||
// browsers and other agents cache many GET requests by default. | ||
response.http.headers.set('cache-control', 'no-store'); | ||
// (But if some other plugin set the header to a value that this | ||
// plugin does not produce, we don't do anything.) | ||
response.http.headers.set( | ||
'cache-control', | ||
CACHE_CONTROL_HEADER_UNCACHEABLE, | ||
); | ||
} | ||
@@ -315,2 +343,34 @@ }, | ||
const CACHE_CONTROL_HEADER_CACHEABLE_REGEXP = | ||
/^max-age=(\d+), (public|private)$/; | ||
const CACHE_CONTROL_HEADER_UNCACHEABLE = 'no-store'; | ||
type ExistingCacheControlHeader = | ||
| { kind: 'no-header' } | ||
| { kind: 'uncacheable' } | ||
| { kind: 'parsable-and-cacheable'; hint: CacheHint } | ||
| { kind: 'unparsable' }; | ||
function parseExistingCacheControlHeader( | ||
header: string | undefined, | ||
): ExistingCacheControlHeader { | ||
if (!header) { | ||
return { kind: 'no-header' }; | ||
} | ||
if (header === CACHE_CONTROL_HEADER_UNCACHEABLE) { | ||
return { kind: 'uncacheable' }; | ||
} | ||
const match = CACHE_CONTROL_HEADER_CACHEABLE_REGEXP.exec(header); | ||
if (!match) { | ||
return { kind: 'unparsable' }; | ||
} | ||
return { | ||
kind: 'parsable-and-cacheable', | ||
hint: { | ||
maxAge: +match[1], | ||
scope: match[2] === 'public' ? 'PUBLIC' : 'PRIVATE', | ||
}, | ||
}; | ||
} | ||
function cacheAnnotationFromDirectives( | ||
@@ -317,0 +377,0 @@ directives: ReadonlyArray<DirectiveNode> | undefined, |
@@ -329,5 +329,5 @@ import type { GraphQLError, DocumentNode } from 'graphql'; | ||
/** | ||
* Default timeout for each individual attempt to send a report to Apollo. | ||
* (This is for each HTTP POST, not for all potential retries.) Defaults to 30 | ||
* seconds (30000ms). | ||
* Timeout for each individual attempt to send a report to Apollo. (This is | ||
* for each HTTP POST, not for all potential retries.) Defaults to 30 seconds | ||
* (30000ms). | ||
*/ | ||
@@ -334,0 +334,0 @@ requestTimeoutMs?: number; |
@@ -117,9 +117,17 @@ import type { | ||
export async function runHttpQuery<TContext extends BaseContext>( | ||
server: ApolloServer<TContext>, | ||
httpRequest: HTTPGraphQLRequest, | ||
contextValue: TContext, | ||
schemaDerivedData: SchemaDerivedData, | ||
internals: ApolloServerInternals<TContext>, | ||
): Promise<HTTPGraphQLResponse> { | ||
export async function runHttpQuery<TContext extends BaseContext>({ | ||
server, | ||
httpRequest, | ||
contextValue, | ||
schemaDerivedData, | ||
internals, | ||
sharedResponseHTTPGraphQLHead, | ||
}: { | ||
server: ApolloServer<TContext>; | ||
httpRequest: HTTPGraphQLRequest; | ||
contextValue: TContext; | ||
schemaDerivedData: SchemaDerivedData; | ||
internals: ApolloServerInternals<TContext>; | ||
sharedResponseHTTPGraphQLHead: HTTPGraphQLHead | null; | ||
}): Promise<HTTPGraphQLResponse> { | ||
let graphQLRequest: GraphQLRequest; | ||
@@ -202,2 +210,3 @@ | ||
schemaDerivedData, | ||
sharedResponseHTTPGraphQLHead, | ||
}, | ||
@@ -204,0 +213,0 @@ { contextValue }, |
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
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
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
1342550
23649