UNPKG

semantic-network

Version:

A utility library for manipulating a list of links that form a semantic interface to a network of resources.

235 lines 9.52 kB
import { LinkUtil } from 'semantic-link'; import { state } from '../types/types'; import anylogger from 'anylogger'; import { LinkRelation } from '../linkRelation'; import { Status } from '../representation/status'; import { SingletonMerger } from '../representation/singletonMerger'; import { instanceOfCollection } from './instanceOf/instanceOfCollection'; import { instanceOfTrackedRepresentation } from './instanceOf/instanceOfTrackedRepresentation'; import { CheckHeaders } from '../representation/checkHeaders'; import { dateToGMTHeader } from './dateToGMTHeader'; const log = anylogger('TrackedRepresentationUtil'); export class TrackedRepresentationUtil { /** * Return back the internal {@link State} object for tracking and introspection * @param resource */ static getState(resource) { const tracking = resource[state]; if (!tracking) { const uri = LinkUtil.getUri(resource, LinkRelation.Self); if (uri) { log.debug('state not found on %s', uri); } else { log.debug('state not found on unknown'); } } return tracking; } /** * Set the headers inside the internal {@link State} object for tracking and introspection */ static setHeaders(resource, headers) { const tracking = resource[state]; if (tracking) { tracking.headers = headers; } return tracking; } /** * Helper to set resource to {@link Status.stale} so that the cache forces need fetch */ static setStateStale(resource) { this.setState(resource, Status.stale); } /** * Helper to set resource to {@link Status.staleFromETag} so that the cache forces need fetch */ static setStateStaleFromETag(resource) { this.setState(resource, Status.staleFromETag); } /** * Looks through into the {@link State} headers for the ETag */ static getETag(resource) { const state = this.getState(resource); // permissive naming strategy for eTags const { headers: { etag, ETag, eTag } } = Object.assign({}, state); return etag || ETag || eTag; } /** * Looks through into the {@link State} headers for the ETag */ static getFeedETag(resource) { const state = this.getState(resource); // permissive naming strategy for eTags const { feedHeaders: { etag, ETag, eTag } } = Object.assign({}, state); return etag || ETag || eTag; } /** * Looks through into the {@link State} headers for the 'last-modified' */ static getFeedLastModified(resource) { const state = this.getState(resource); // permissive naming strategy for eTags const { feedHeaders: { 'last-modified': lastModified } } = Object.assign({}, state); return lastModified; } /** * Sets the value of the feed eTag and if null is provided, it is cleared */ static setFeedETag(resource, eTag, lastModified) { const state = this.getState(resource); // ensure that UTC dates are converted across to GMT headers const lastModifiedHeader = dateToGMTHeader(lastModified); if (eTag) { state.feedHeaders = Object.assign(Object.assign(Object.assign({}, state.feedHeaders), { etag: eTag }), (lastModifiedHeader && { 'last-modified': lastModifiedHeader })); } else { delete state.feedHeaders.etag; } } /** * Checks if an eTag exists based on looking through into the {@link State} headers for the ETag */ static hasETag(resource) { return this.getETag(resource) !== undefined; } /** * Checks if an eTag exists based on looking through into the {@link State} headers for the ETag */ static hasFeedETag(resource) { return this.getFeedETag(resource) !== undefined; } /** * Checks if the header eTag matches the feed eTag. It is deemed stale when both eTags are present and different * suggesting that the latest is not present */ static hasStaleFeedETag(resource) { const feedETag = this.getFeedETag(resource); const requestETag = this.getETag(resource); return feedETag !== undefined && requestETag !== undefined && requestETag !== feedETag; } /** * Checks the named child object is tracked on the resource. * * A resource keeps a set of child singletons and a set of child collections. This utility * provides a logical 'in' operator on those sets. * * Note: field name ideally comes in as K only, but in practice it also needs to be dealt with as arbitrary string * as soon as it is known to be a tracked representation then it can cast string to K (rather than deal with * string in the subsequent methods */ static isTracked(resource, name) { return instanceOfTrackedRepresentation(resource) && (this.isSingletonTracked(resource, name) || this.isCollectionTracked(resource, name)); } /** * Checks the resource is currently tracked in either as a singleton or a collection */ static getTrackedFields(resource) { return instanceOfTrackedRepresentation(resource) ? [...this.getState(resource).collection, ...this.getState(resource).singleton] : []; } /** * Checks whether the resource requires an across-the-wire fetch based on the state flags. * * We can only do a fetch when we actually have a potentially valid uri and that we haven't already * got the resource. Currently, the forceLoad allows an override which is an initial cache busting * strategy that will need improvement * * Simple cache bust strategy which is an override switch. To be expanded as needed. Currently, only * cache bust on {@link Status.hydrated} resources. There is no time-based, refresh strategy at this point. * */ static needsFetchFromState(resource, options) { const { forceLoad = false } = Object.assign({}, options); const { status = undefined } = this.getState(resource); if (status) { const fetch = /*status === Status.unknown ||*/ status === Status.locationOnly || status === Status.stale || status === Status.staleFromETag || (forceLoad && status === Status.hydrated); if (fetch) { log.debug('fetch resource \'%s\': %s', status.toString(), LinkUtil.getUri(resource, LinkRelation.Self)); } else { log.debug('fetch resource \'%s\' not required: %s', status.toString(), LinkUtil.getUri(resource, LinkRelation.Self)); } return fetch; } else { log.warn('status not found (on state): %s', LinkUtil.getUri(resource, LinkRelation.Self)); return true; } } /** * Respects conditional headers from the server on whether to push back through the application cache. Without it, * client developers use {@link ResourceFetchOptions.forceLoad} option too often because requests do not respect the server cache-control * headers. * * Note: this code will not attempt to reimplement request headers (that is what browsers already do). However, what * you may find is inconsistent behaviours between browsers on request cache control headers * * @see https://gertjans.home.xs4all.nl/javascript/cache-control.html */ static needsFetchFromHeaders(resource, options) { const { checkHeaderStrategies = CheckHeaders.defaultStrategies, } = Object.assign({}, options); const { headers = {} } = this.getState(resource); const now = new Date(); for (const strategy of checkHeaderStrategies) { if (strategy(headers, now)) { return true; } } return false; } /** * * Returns target. * * @param target * @param prop * @param resource * @param options */ static add(target, prop, resource, options) { if (instanceOfTrackedRepresentation(target)) { // add as a tracked collection/singleton on state if (instanceOfCollection(resource)) { this.getState(target).collection.add(prop); } else { this.getState(target).singleton.add(prop); } SingletonMerger.add(target, prop, resource, options); } else { log.warn('target is not a tracked representation and cannot add resource; \'%s\'', LinkUtil.getUri(target, LinkRelation.Self)); } return target; } static setState(resource, status) { if (instanceOfTrackedRepresentation(resource)) { const state = TrackedRepresentationUtil.getState(resource); state.status = status; } } /** * Checks the resource is currently tracked as a singleton */ static isSingletonTracked(resource, name) { return this.getState(resource).singleton.has(name); } /** * Checks the resource is currently tracked as a collection */ static isCollectionTracked(resource, name) { return this.getState(resource).collection.has(name); } } //# sourceMappingURL=trackedRepresentationUtil.js.map