arrayConnection.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true,
  4. });
  5. exports.connectionFromArray = connectionFromArray;
  6. exports.connectionFromPromisedArray = connectionFromPromisedArray;
  7. exports.connectionFromArraySlice = connectionFromArraySlice;
  8. exports.connectionFromPromisedArraySlice = connectionFromPromisedArraySlice;
  9. exports.offsetToCursor = offsetToCursor;
  10. exports.cursorToOffset = cursorToOffset;
  11. exports.cursorForObjectInConnection = cursorForObjectInConnection;
  12. exports.getOffsetWithDefault = getOffsetWithDefault;
  13. var _base = require('../utils/base64');
  14. /**
  15. * A simple function that accepts an array and connection arguments, and returns
  16. * a connection object for use in GraphQL. It uses array offsets as pagination,
  17. * so pagination will only work if the array is static.
  18. */
  19. function connectionFromArray(data, args) {
  20. return connectionFromArraySlice(data, args, {
  21. sliceStart: 0,
  22. arrayLength: data.length,
  23. });
  24. }
  25. /**
  26. * A version of `connectionFromArray` that takes a promised array, and returns a
  27. * promised connection.
  28. */
  29. function connectionFromPromisedArray(dataPromise, args) {
  30. return dataPromise.then((data) => connectionFromArray(data, args));
  31. }
  32. /**
  33. * Given a slice (subset) of an array, returns a connection object for use in
  34. * GraphQL.
  35. *
  36. * This function is similar to `connectionFromArray`, but is intended for use
  37. * cases where you know the cardinality of the connection, consider it too large
  38. * to materialize the entire array, and instead wish pass in a slice of the
  39. * total result large enough to cover the range specified in `args`.
  40. */
  41. function connectionFromArraySlice(arraySlice, args, meta) {
  42. const { after, before, first, last } = args;
  43. const { sliceStart, arrayLength } = meta;
  44. const sliceEnd = sliceStart + arraySlice.length;
  45. let startOffset = Math.max(sliceStart, 0);
  46. let endOffset = Math.min(sliceEnd, arrayLength);
  47. const afterOffset = getOffsetWithDefault(after, -1);
  48. if (0 <= afterOffset && afterOffset < arrayLength) {
  49. startOffset = Math.max(startOffset, afterOffset + 1);
  50. }
  51. const beforeOffset = getOffsetWithDefault(before, endOffset);
  52. if (0 <= beforeOffset && beforeOffset < arrayLength) {
  53. endOffset = Math.min(endOffset, beforeOffset);
  54. }
  55. if (typeof first === 'number') {
  56. if (first < 0) {
  57. throw new Error('Argument "first" must be a non-negative integer');
  58. }
  59. endOffset = Math.min(endOffset, startOffset + first);
  60. }
  61. if (typeof last === 'number') {
  62. if (last < 0) {
  63. throw new Error('Argument "last" must be a non-negative integer');
  64. }
  65. startOffset = Math.max(startOffset, endOffset - last);
  66. }
  67. // If supplied slice is too large, trim it down before mapping over it.
  68. const slice = arraySlice.slice(
  69. startOffset - sliceStart,
  70. endOffset - sliceStart,
  71. );
  72. const edges = slice.map((value, index) => ({
  73. cursor: offsetToCursor(startOffset + index),
  74. node: value,
  75. }));
  76. const firstEdge = edges[0];
  77. const lastEdge = edges[edges.length - 1];
  78. const lowerBound = after != null ? afterOffset + 1 : 0;
  79. const upperBound = before != null ? beforeOffset : arrayLength;
  80. return {
  81. edges,
  82. pageInfo: {
  83. startCursor: firstEdge ? firstEdge.cursor : null,
  84. endCursor: lastEdge ? lastEdge.cursor : null,
  85. hasPreviousPage:
  86. typeof last === 'number' ? startOffset > lowerBound : false,
  87. hasNextPage: typeof first === 'number' ? endOffset < upperBound : false,
  88. },
  89. };
  90. }
  91. /**
  92. * A version of `connectionFromArraySlice` that takes a promised array slice,
  93. * and returns a promised connection.
  94. */
  95. function connectionFromPromisedArraySlice(dataPromise, args, arrayInfo) {
  96. return dataPromise.then((data) =>
  97. connectionFromArraySlice(data, args, arrayInfo),
  98. );
  99. }
  100. const PREFIX = 'arrayconnection:';
  101. /**
  102. * Creates the cursor string from an offset.
  103. */
  104. function offsetToCursor(offset) {
  105. return (0, _base.base64)(PREFIX + offset.toString());
  106. }
  107. /**
  108. * Extracts the offset from the cursor string.
  109. */
  110. function cursorToOffset(cursor) {
  111. return parseInt((0, _base.unbase64)(cursor).substring(PREFIX.length), 10);
  112. }
  113. /**
  114. * Return the cursor associated with an object in an array.
  115. */
  116. function cursorForObjectInConnection(data, object) {
  117. const offset = data.indexOf(object);
  118. if (offset === -1) {
  119. return null;
  120. }
  121. return offsetToCursor(offset);
  122. }
  123. /**
  124. * Given an optional cursor and a default offset, returns the offset
  125. * to use; if the cursor contains a valid offset, that will be used,
  126. * otherwise it will be the default.
  127. */
  128. function getOffsetWithDefault(cursor, defaultOffset) {
  129. if (typeof cursor !== 'string') {
  130. return defaultOffset;
  131. }
  132. const offset = cursorToOffset(cursor);
  133. return isNaN(offset) ? defaultOffset : offset;
  134. }