Source: object-graph-bfs-iterator.ts

  1. /* eslint-disable no-await-in-loop */
  2. /**
  3. * @author Michael Hasenstein <hasenstein@yahoo.com>
  4. * @copyright REFINIO GmbH 2023
  5. * @license CC-BY-NC-SA-2.5; portions MIT License
  6. * @version 0.0.1
  7. */
  8. /**
  9. * @module
  10. */
  11. import {createError} from './errors';
  12. import type {
  13. IteratorCbParam,
  14. IteratorCbParamBlobType,
  15. IteratorCbParamClobType,
  16. IteratorCbParamIdType,
  17. IteratorCbParamObjType,
  18. ObjectGraphIteratorOptions
  19. } from './object-graph-bottom-up-iterator';
  20. import {
  21. ID_LINK_ITERATION,
  22. ID_LINK_ITERATION_VALUES,
  23. STOP_ITERATOR_ERROR
  24. } from './object-graph-bottom-up-iterator';
  25. import {HashLinkType} from './object-to-microdata';
  26. import type {BLOB, CLOB, HashTypes} from './recipes';
  27. import {getObject} from './storage-unversioned-objects';
  28. import {getIdObject} from './storage-versioned-objects';
  29. import {fileSize} from './system/storage-base';
  30. import {getAllHashesForIdHashes, getLatestHashesForIdHashes} from './util/hashes-from-id';
  31. import {findLinkedHashesInObject} from './util/object-find-links';
  32. import {createSimpleQueue} from './util/queue';
  33. import type {SHA256Hash, SHA256IdHash} from './util/type-checks';
  34. interface IteratorOptions {
  35. cb: (param: IteratorCbParam) => void | Promise<void>;
  36. idLinkIteration: (typeof ID_LINK_ITERATION)[keyof typeof ID_LINK_ITERATION];
  37. includeFileSize: boolean;
  38. }
  39. interface QItemObj {
  40. type: typeof HashLinkType.OBJ;
  41. hash: SHA256Hash;
  42. path: ReadonlyArray<SHA256Hash | SHA256IdHash>;
  43. }
  44. interface QItemId {
  45. type: typeof HashLinkType.ID;
  46. hash: SHA256IdHash;
  47. path: ReadonlyArray<SHA256Hash | SHA256IdHash>;
  48. }
  49. interface QItemBlob {
  50. type: typeof HashLinkType.BLOB;
  51. hash: SHA256Hash<BLOB>;
  52. path: ReadonlyArray<SHA256Hash | SHA256IdHash>;
  53. }
  54. interface QItemClob {
  55. type: typeof HashLinkType.CLOB;
  56. hash: SHA256Hash<CLOB>;
  57. path: ReadonlyArray<SHA256Hash | SHA256IdHash>;
  58. }
  59. type QItem = QItemObj | QItemId | QItemBlob | QItemClob;
  60. async function iterate(
  61. root: SHA256Hash,
  62. {cb, idLinkIteration, includeFileSize}: IteratorOptions
  63. ): Promise<void> {
  64. // This should only ever be possible at iteration start, but we place it in the recursive
  65. // function just in case there is anything weird in the data.
  66. if (root === undefined) {
  67. throw createError('OGI-WALK1');
  68. }
  69. if (!ID_LINK_ITERATION_VALUES.has(idLinkIteration)) {
  70. throw createError('OGI-WALK2', {all: ID_LINK_ITERATION_VALUES, val: idLinkIteration});
  71. }
  72. const visited = new Set<SHA256Hash<HashTypes> | SHA256IdHash>();
  73. const q = createSimpleQueue<Readonly<QItem>>();
  74. q.enqueue({
  75. type: HashLinkType.OBJ,
  76. hash: root,
  77. path: []
  78. });
  79. while (!q.isEmpty()) {
  80. const {type, hash, path} = q.dequeue();
  81. if (visited.has(hash)) {
  82. continue;
  83. }
  84. visited.add(hash);
  85. const size = includeFileSize ? await fileSize(hash) : undefined;
  86. if (type === HashLinkType.BLOB || type === HashLinkType.CLOB) {
  87. await cb({
  88. type,
  89. hash,
  90. path,
  91. size
  92. } as const as IteratorCbParamBlobType | IteratorCbParamClobType);
  93. continue;
  94. }
  95. // Form here: type is one of "obj" or "id"
  96. const obj = type === HashLinkType.OBJ ? await getObject(hash) : await getIdObject(hash);
  97. const links = findLinkedHashesInObject(obj);
  98. await cb({
  99. type,
  100. hash,
  101. obj,
  102. links,
  103. path,
  104. size
  105. } as const as IteratorCbParamObjType | IteratorCbParamIdType);
  106. for (const ref of links.references) {
  107. q.enqueue({
  108. type: HashLinkType.OBJ,
  109. hash: ref,
  110. path: [...path, hash]
  111. });
  112. }
  113. if (idLinkIteration === ID_LINK_ITERATION.ID_OBJECT) {
  114. for (const idRef of links.idReferences) {
  115. q.enqueue({
  116. type: HashLinkType.ID,
  117. hash: idRef,
  118. path: [...path, hash]
  119. });
  120. }
  121. } else if (idLinkIteration === ID_LINK_ITERATION.LATEST_VERSION) {
  122. const hashesForIdReferences = await getLatestHashesForIdHashes(links.idReferences);
  123. for (const {hash: childHash} of hashesForIdReferences) {
  124. q.enqueue({
  125. type: HashLinkType.OBJ,
  126. hash: childHash,
  127. path: [...path, hash]
  128. });
  129. }
  130. } else if (idLinkIteration === ID_LINK_ITERATION.ALL_VERSIONS) {
  131. const hashesForIdReferences = await getAllHashesForIdHashes(links.idReferences);
  132. for (const {hash: childHash} of hashesForIdReferences) {
  133. q.enqueue({
  134. type: HashLinkType.OBJ,
  135. hash: childHash,
  136. path: [...path, hash]
  137. });
  138. }
  139. }
  140. for (const ref of links.blobs) {
  141. q.enqueue({
  142. type: HashLinkType.BLOB,
  143. hash: ref,
  144. path: [...path, hash]
  145. });
  146. }
  147. for (const ref of links.clobs) {
  148. q.enqueue({
  149. type: HashLinkType.CLOB,
  150. hash: ref,
  151. path: [...path, hash]
  152. });
  153. }
  154. }
  155. }
  156. export async function iterateGraphFromObjectBFS(
  157. hash: SHA256Hash,
  158. cb: (param: IteratorCbParam) => void | Promise<void>,
  159. {
  160. idLinkIteration = ID_LINK_ITERATION.ID_OBJECT,
  161. includeFileSize = false
  162. }: ObjectGraphIteratorOptions = {}
  163. ): Promise<boolean> {
  164. try {
  165. await iterate(hash, {
  166. cb,
  167. idLinkIteration,
  168. includeFileSize
  169. });
  170. } catch (err) {
  171. // If the callback function throws this particular error the iterator just stops, but
  172. // the overall promise status remains a success.
  173. if (err.name === STOP_ITERATOR_ERROR) {
  174. return false;
  175. }
  176. throw err;
  177. }
  178. return true;
  179. }