|
| 1 | +import { isEntityReference } from './halHelpers' |
| 2 | +import LoadingCollection from './LoadingCollection' |
| 3 | +import ResourceInterface from './interfaces/ResourceInterface' |
| 4 | +import CollectionInterface from './interfaces/CollectionInterface' |
| 5 | +import { Link } from './interfaces/StoreData' |
| 6 | +import Resource from './Resource' |
| 7 | + |
| 8 | +/** |
| 9 | + * Filter out items that are marked as deleting (eager removal) |
| 10 | + */ |
| 11 | +function filterDeleting (array: Array<ResourceInterface>): Array<ResourceInterface> { |
| 12 | + return array.filter(entry => !entry._meta.deleting) |
| 13 | +} |
| 14 | + |
| 15 | +class Collection extends Resource { |
| 16 | + /** |
| 17 | + * Get items excluding ones marked as 'deleting' (eager remove) |
| 18 | + * The items property should always be a getter, in order to make the call to mapArrayOfEntityReferences |
| 19 | + * lazy, since that potentially fetches a large number of entities from the API. |
| 20 | + */ |
| 21 | + public get items (): Array<ResourceInterface> { |
| 22 | + return filterDeleting(this._mapArrayOfEntityReferences(this._storeData.items)) |
| 23 | + } |
| 24 | + |
| 25 | + /** |
| 26 | + * Get all items including ones marked as 'deleting' (lazy remove) |
| 27 | + */ |
| 28 | + public get allItems (): Array<ResourceInterface> { |
| 29 | + return this._mapArrayOfEntityReferences(this._storeData.items) |
| 30 | + } |
| 31 | + |
| 32 | + /** |
| 33 | + * Returns a promise that resolves to the collection object, once all items have been loaded |
| 34 | + */ |
| 35 | + public $loadItems () :Promise<CollectionInterface> { |
| 36 | + return this._itemLoader(this._storeData.items) |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * Returns a promise that resolves to the collection object, once all items have been loaded |
| 41 | + */ |
| 42 | + private _itemLoader (array: Array<Link>) : Promise<CollectionInterface> { |
| 43 | + if (!this._containsUnknownEntityReference(array)) { |
| 44 | + return Promise.resolve(this as unknown as CollectionInterface) // we know that this object must be of type CollectionInterface |
| 45 | + } |
| 46 | + |
| 47 | + // eager loading of 'fetchAllUri' (e.g. parent for embedded collections) |
| 48 | + if (this.config.avoidNPlusOneRequests) { |
| 49 | + return this.apiActions.reload(this as unknown as CollectionInterface) as Promise<CollectionInterface> // we know that reload resolves to a type CollectionInterface |
| 50 | + |
| 51 | + // no eager loading: replace each reference (Link) with a Resource (ResourceInterface) |
| 52 | + } else { |
| 53 | + const arrayWithReplacedReferences = this._replaceEntityReferences(array) |
| 54 | + |
| 55 | + return Promise.all( |
| 56 | + arrayWithReplacedReferences.map(entry => entry._meta.load) |
| 57 | + ).then(() => this as unknown as CollectionInterface) // we know that this object must be of type CollectionInterface |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * Given an array, replaces any entity references in the array with the entity loaded from the Vuex store |
| 63 | + * (or from the API if necessary), and returns that as a new array. In case some of the entity references in |
| 64 | + * the array have not finished loading yet, returns a LoadingCollection instead. |
| 65 | + * @param array possibly mixed array of values and references |
| 66 | + * @param fetchAllUri URI that allows fetching all array items in a single network request, if known |
| 67 | + * @param fetchAllProperty property in the entity from fetchAllUri that will contain the array |
| 68 | + * @returns array the new array with replaced items, or a LoadingCollection if any of the array |
| 69 | + * elements is still loading. |
| 70 | + */ |
| 71 | + private _mapArrayOfEntityReferences (array: Array<Link>): Array<ResourceInterface> { |
| 72 | + if (!this._containsUnknownEntityReference(array)) { |
| 73 | + return this._replaceEntityReferences(array) |
| 74 | + } |
| 75 | + |
| 76 | + const itemsLoaded = this._itemLoader(array).then(() => this._replaceEntityReferences(array)) |
| 77 | + |
| 78 | + // eager loading of 'fetchAllUri' (e.g. parent for embedded collections) |
| 79 | + if (this.config.avoidNPlusOneRequests) { |
| 80 | + return LoadingCollection.create(itemsLoaded) |
| 81 | + |
| 82 | + // no eager loading: replace each reference (Link) with a Resource (ResourceInterface) |
| 83 | + } else { |
| 84 | + return LoadingCollection.create(itemsLoaded, this._replaceEntityReferences(array)) |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + /** |
| 89 | + * Replace each item in array with a proper Resource (or LoadingResource) |
| 90 | + */ |
| 91 | + private _replaceEntityReferences (array: Array<Link>): Array<ResourceInterface> { |
| 92 | + return array |
| 93 | + .filter(entry => isEntityReference(entry)) |
| 94 | + .map(entry => this.apiActions.get(entry.href)) |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Returns true if any of the items within 'array' is not yet known to the API (meaning it has never been loaded) |
| 99 | + */ |
| 100 | + private _containsUnknownEntityReference (array: Array<Link>): boolean { |
| 101 | + return array.some(entry => isEntityReference(entry) && this.apiActions.isUnknown(entry.href)) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +export default Collection |
0 commit comments