index.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.__testing__ = exports.plugin = exports.CacheScope = void 0;
  4. const graphql_1 = require("graphql");
  5. var CacheScope;
  6. (function (CacheScope) {
  7. CacheScope["Public"] = "PUBLIC";
  8. CacheScope["Private"] = "PRIVATE";
  9. })(CacheScope = exports.CacheScope || (exports.CacheScope = {}));
  10. exports.plugin = (options = Object.create(null)) => ({
  11. requestDidStart(requestContext) {
  12. const defaultMaxAge = options.defaultMaxAge || 0;
  13. const hints = new Map();
  14. function setOverallCachePolicyWhenUnset() {
  15. if (!requestContext.overallCachePolicy) {
  16. requestContext.overallCachePolicy = computeOverallCachePolicy(hints);
  17. }
  18. }
  19. return {
  20. executionDidStart: () => ({
  21. executionDidEnd: () => setOverallCachePolicyWhenUnset(),
  22. willResolveField({ info }) {
  23. let hint = {};
  24. const targetType = graphql_1.getNamedType(info.returnType);
  25. if (targetType instanceof graphql_1.GraphQLObjectType ||
  26. targetType instanceof graphql_1.GraphQLInterfaceType) {
  27. if (targetType.astNode) {
  28. hint = mergeHints(hint, cacheHintFromDirectives(targetType.astNode.directives));
  29. }
  30. }
  31. const fieldDef = info.parentType.getFields()[info.fieldName];
  32. if (fieldDef.astNode) {
  33. hint = mergeHints(hint, cacheHintFromDirectives(fieldDef.astNode.directives));
  34. }
  35. if ((targetType instanceof graphql_1.GraphQLObjectType ||
  36. targetType instanceof graphql_1.GraphQLInterfaceType ||
  37. !info.path.prev) &&
  38. hint.maxAge === undefined) {
  39. hint.maxAge = defaultMaxAge;
  40. }
  41. if (hint.maxAge !== undefined || hint.scope !== undefined) {
  42. addHint(hints, info.path, hint);
  43. }
  44. info.cacheControl = {
  45. setCacheHint: (hint) => {
  46. addHint(hints, info.path, hint);
  47. },
  48. cacheHint: hint,
  49. };
  50. },
  51. }),
  52. responseForOperation() {
  53. setOverallCachePolicyWhenUnset();
  54. return null;
  55. },
  56. willSendResponse(requestContext) {
  57. const { response, overallCachePolicy: overallCachePolicyOverride, } = requestContext;
  58. if (response.errors) {
  59. return;
  60. }
  61. const overallCachePolicy = overallCachePolicyOverride ||
  62. (requestContext.overallCachePolicy =
  63. computeOverallCachePolicy(hints));
  64. if (overallCachePolicy &&
  65. options.calculateHttpHeaders &&
  66. response.http) {
  67. response.http.headers.set('Cache-Control', `max-age=${overallCachePolicy.maxAge}, ${overallCachePolicy.scope.toLowerCase()}`);
  68. }
  69. if (options.stripFormattedExtensions !== false)
  70. return;
  71. const extensions = response.extensions || (response.extensions = Object.create(null));
  72. if (typeof extensions.cacheControl !== 'undefined') {
  73. throw new Error("The cacheControl information already existed.");
  74. }
  75. extensions.cacheControl = {
  76. version: 1,
  77. hints: Array.from(hints).map(([path, hint]) => (Object.assign({ path: [...graphql_1.responsePathAsArray(path)] }, hint))),
  78. };
  79. }
  80. };
  81. }
  82. });
  83. function cacheHintFromDirectives(directives) {
  84. if (!directives)
  85. return undefined;
  86. const cacheControlDirective = directives.find(directive => directive.name.value === 'cacheControl');
  87. if (!cacheControlDirective)
  88. return undefined;
  89. if (!cacheControlDirective.arguments)
  90. return undefined;
  91. const maxAgeArgument = cacheControlDirective.arguments.find(argument => argument.name.value === 'maxAge');
  92. const scopeArgument = cacheControlDirective.arguments.find(argument => argument.name.value === 'scope');
  93. return {
  94. maxAge: maxAgeArgument &&
  95. maxAgeArgument.value &&
  96. maxAgeArgument.value.kind === 'IntValue'
  97. ? parseInt(maxAgeArgument.value.value)
  98. : undefined,
  99. scope: scopeArgument &&
  100. scopeArgument.value &&
  101. scopeArgument.value.kind === 'EnumValue'
  102. ? scopeArgument.value.value
  103. : undefined,
  104. };
  105. }
  106. function mergeHints(hint, otherHint) {
  107. if (!otherHint)
  108. return hint;
  109. return {
  110. maxAge: otherHint.maxAge !== undefined ? otherHint.maxAge : hint.maxAge,
  111. scope: otherHint.scope || hint.scope,
  112. };
  113. }
  114. function computeOverallCachePolicy(hints) {
  115. let lowestMaxAge = undefined;
  116. let scope = CacheScope.Public;
  117. for (const hint of hints.values()) {
  118. if (hint.maxAge !== undefined) {
  119. lowestMaxAge =
  120. lowestMaxAge !== undefined
  121. ? Math.min(lowestMaxAge, hint.maxAge)
  122. : hint.maxAge;
  123. }
  124. if (hint.scope === CacheScope.Private) {
  125. scope = CacheScope.Private;
  126. }
  127. }
  128. return lowestMaxAge
  129. ? {
  130. maxAge: lowestMaxAge,
  131. scope,
  132. }
  133. : undefined;
  134. }
  135. function addHint(hints, path, hint) {
  136. const existingCacheHint = hints.get(path);
  137. if (existingCacheHint) {
  138. hints.set(path, mergeHints(existingCacheHint, hint));
  139. }
  140. else {
  141. hints.set(path, hint);
  142. }
  143. }
  144. exports.__testing__ = {
  145. addHint,
  146. computeOverallCachePolicy,
  147. };
  148. //# sourceMappingURL=index.js.map