UNPKG

semantic-network

Version:

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

415 lines (411 loc) 21.3 kB
import { __asyncValues, __awaiter } from "tslib"; import { LinkUtil } from 'semantic-link'; import { ApiUtil } from '../apiUtil'; import { noopResolver } from '../representation/resourceMergeFactory'; import { LinkRelation } from '../linkRelation'; import { Differencer } from './differencer'; import anylogger from 'anylogger'; import { RepresentationUtil } from '../utils/representationUtil'; const log = anylogger('SyncUtil'); export class SyncUtil { static syncResources(resource, document, strategies = [], options) { return __awaiter(this, void 0, void 0, function* () { const { strategyBatchSize = undefined } = Object.assign({}, options); if (strategyBatchSize === 0 || !strategyBatchSize) { log.debug('sync strategy resource: parallel'); return () => __awaiter(this, void 0, void 0, function* () { return yield Promise.all(strategies.map((strategy) => __awaiter(this, void 0, void 0, function* () { return yield strategy({ resource, document, options, }); }))); }); } else { return () => __awaiter(this, void 0, void 0, function* () { var _a, e_1, _b, _c; const results = []; try { for (var _d = true, strategies_1 = __asyncValues(strategies), strategies_1_1; strategies_1_1 = yield strategies_1.next(), _a = strategies_1_1.done, !_a; _d = true) { _c = strategies_1_1.value; _d = false; const strategy = _c; const result = yield strategy({ resource, document, options, }); results.push(result); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (!_d && !_a && (_b = strategies_1.return)) yield _b.call(strategies_1); } finally { if (e_1) throw e_1.error; } } return results; }); } }); } static tailRecursionThroughStrategies(strategies, syncInfos, options) { var _a, strategies_2, strategies_2_1, _b, syncInfos_1, syncInfos_1_1; var _c, e_2, _d, _e, _f, e_3, _g, _h; return __awaiter(this, void 0, void 0, function* () { const { strategyBatchSize = undefined } = Object.assign({}, options); try { for (_a = true, strategies_2 = __asyncValues(strategies); strategies_2_1 = yield strategies_2.next(), _c = strategies_2_1.done, !_c; _a = true) { _e = strategies_2_1.value; _a = false; const strategy = _e; if (strategyBatchSize === 0 || !strategyBatchSize) { // invoke a parallel strategy when want to go for it log.debug('sync strategy tail: parallel'); yield Promise.all(syncInfos.map((syncInfo) => __awaiter(this, void 0, void 0, function* () { yield strategy({ resource: syncInfo.resource, document: syncInfo.document, options, }); }))); } else { // invoke a sequential strategy - and for now, single at a time log.debug('sync strategy tail: sequential '); try { /* await syncInfos.reduce( async (acc, curr) => { return await strategy({ resource: curr.resource, document: curr.document, options, }); }, Promise.resolve()); */ for (_b = true, syncInfos_1 = (e_3 = void 0, __asyncValues(syncInfos)); syncInfos_1_1 = yield syncInfos_1.next(), _f = syncInfos_1_1.done, !_f; _b = true) { _h = syncInfos_1_1.value; _b = false; const syncInfo = _h; yield strategy({ resource: syncInfo.resource, document: syncInfo.document, options, }); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (!_b && !_f && (_g = syncInfos_1.return)) yield _g.call(syncInfos_1); } finally { if (e_3) throw e_3.error; } } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (!_a && !_c && (_d = strategies_2.return)) yield _d.call(strategies_2); } finally { if (e_2) throw e_2.error; } } }); } static syncResourceInCollection(resource, document, options) { return __awaiter(this, void 0, void 0, function* () { const { resolver = noopResolver, findResourceInCollectionStrategy = this.defaultFindResourceInCollectionStrategy, forceCreate = false, } = Object.assign({}, options); // locate the document in the collection items // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore not sure why this typing isn't working const item = findResourceInCollectionStrategy(resource, { where: document }); // check whether to update or create if (item && !forceCreate) { // synchronise the item in the collection from the server const result = yield ApiUtil.get(resource, Object.assign(Object.assign({}, options), { where: item })); if (result) { const resource = yield ApiUtil.update(result, document, options); if (resource) { const uri = LinkUtil.getUri(resource, LinkRelation.Self); log.debug('sync resource \'update\' in collection %s', uri); return { resource: resource, document: document, action: 'update', }; } else { log.warn('sync resource \'update\' failed in collection %s', LinkUtil.getUri(resource, LinkRelation.Self)); } } } else { // add the document to the collection and add a mapping const result = yield ApiUtil.create(document, Object.assign(Object.assign({}, options), { createContext: resource })); if (result) { const resultUri = LinkUtil.getUri(result, LinkRelation.Self); const originalUri = LinkUtil.getUri(document, LinkRelation.Self); if (originalUri && resultUri && originalUri !== resultUri) { resolver.add(originalUri, resultUri); } else { log.debug('sync resource \'create\' new not mapped \'%s\'', resultUri); } log.debug('sync resource \'create\' in collection %s', resultUri); return { resource: result, document: document, action: 'create', }; } else { log.warn('sync resource \'create\' failed in collection %s', LinkUtil.getUri(resource, LinkRelation.Self)); } } }); } static syncInfos(strategies, options) { return (syncInfo) => __awaiter(this, void 0, void 0, function* () { var _a, e_4, _b, _c; const { strategyBatchSize = undefined } = Object.assign({}, (options)); if (strategyBatchSize === 0 || !strategyBatchSize) { yield Promise.all(strategies.map((strategy) => __awaiter(this, void 0, void 0, function* () { yield strategy({ resource: syncInfo.resource, document: syncInfo.document, options, }); }))); } else { try { for (var _d = true, strategies_3 = __asyncValues(strategies), strategies_3_1; strategies_3_1 = yield strategies_3.next(), _a = strategies_3_1.done, !_a; _d = true) { _c = strategies_3_1.value; _d = false; const strategy = _c; yield strategy({ resource: syncInfo.resource, document: syncInfo.document, options, }); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (!_d && !_a && (_b = strategies_3.return)) yield _b.call(strategies_3); } finally { if (e_4) throw e_4.error; } } } /* // note: currently both parallel and sequential are same because the set n = 1 for await (const strategy of strategies) { if (strategyBatchSize === 0 || !strategyBatchSize) { // invoke a parallel strategy when want to go for it log.debug('sync strategy info: parallel'); await Promise.all([syncInfo].map(async syncInfo => { await strategy({ resource: syncInfo.resource, document: syncInfo.document, options, }); })); } else { // invoke a sequential strategy - and for now, single at a time log.debug('sync strategy info: sequential'); /!* await [syncInfo].reduce( async (acc, curr) => { return await strategy({ resource: curr.resource, document: curr.document, options, }); }, Promise.resolve()); *!/ for await (const syncInfo1 of [syncInfo]) { await strategy({ resource: syncInfo1.resource, document: syncInfo1.document, options, }); } } } */ return syncInfo.resource; }); } } SyncUtil.defaultFindResourceInCollectionStrategy = RepresentationUtil.findInCollection; SyncUtil.synchroniseCollection = function (collectionResource, collectionDocument, options) { return __awaiter(this, void 0, void 0, function* () { const { resolver = noopResolver, readonly, contributeonly, } = Object.assign({}, options); /** * Delete a resource from the local state cache */ const deleteResourceAndUpdateResolver = (deleteResource) => __awaiter(this, void 0, void 0, function* () { const result = yield ApiUtil.delete(deleteResource, Object.assign(Object.assign({}, options), { // TODO: this is unlikely to be used 'on' doesn't exist in delete addStateOn: collectionResource })); if (result) { const uri = LinkUtil.getUri(deleteResource, LinkRelation.Self); if (uri) { log.debug('sync delete on collection \'%s\'', LinkUtil.getUri(collectionResource, LinkRelation.Self)); resolver.remove(uri); } } else { log.debug('sync not deleted on collection \'%s\' %s', LinkUtil.getUri(collectionResource, LinkRelation.Self), LinkUtil.getUri(deleteResource, LinkRelation.Self)); } }); /** * Update a resource and remember the URI mapping so that if a reference to the * network of data resource is required we can resolve a document reference to * real resource in our network. */ const updateResourceAndUpdateResolver = (updateResource, updateDataDocument) => __awaiter(this, void 0, void 0, function* () { const result = yield ApiUtil.get(updateResource, options); if (result) { const update = yield ApiUtil.update(result, updateDataDocument, options); if (update) { const uriOriginal = LinkUtil.getUri(updateDataDocument, LinkRelation.Self); const uriResult = LinkUtil.getUri(updateResource, LinkRelation.Self); if (uriOriginal && uriResult) { log.debug('sync update on \'%s\'', LinkUtil.getUri(update, LinkRelation.Self)); resolver.update(uriOriginal, uriResult); } } } else { log.debug('sync not updated on %s', LinkUtil.getUri(updateResource, LinkRelation.Self)); } }); /** * */ const createResourceAndUpdateResolver = (createDataDocument) => __awaiter(this, void 0, void 0, function* () { const result = yield ApiUtil.create(createDataDocument, Object.assign(Object.assign({}, options), { createContext: collectionResource })); const uriOriginal = LinkUtil.getUri(createDataDocument, LinkRelation.Self); if (result) { const uriResult = LinkUtil.getUri(result, LinkRelation.Self); if (uriOriginal && uriResult) { log.debug('sync create on collection \'%s\'', LinkUtil.getUri(collectionResource, LinkRelation.Self)); resolver.add(uriOriginal, uriResult); } } else { log.debug('sync on collection not created \'%s\' for %s', LinkUtil.getUri(collectionResource, LinkRelation.Self), uriOriginal); } // TODO: returning undefined changes the interface T | undefined on the CreateStrategy return result; }); /** * A read-only collection needs have an item deleted. We don't delete it * but can add it to our mapping resolver anyway. * * We don't expect to come in here, but we will if the document supplied * has fewer items that the current network of data (likely from time to time). */ const deleteReadonlyResourceAndUpdateResolver = (collectionResourceItem) => __awaiter(this, void 0, void 0, function* () { const uri = LinkUtil.getUri(collectionResourceItem, LinkRelation.Self); if (uri) { resolver.remove(uri); } }); /** * Don't make request back to update, just remember the URI mapping so that if a reference to the * network of data resource is required we can resolve a document reference to the real resource in * our network. */ const updateReadonlyResourceAndUpdateResolver = (collectionResourceItem, updateDataDocument) => __awaiter(this, void 0, void 0, function* () { const uri = LinkUtil.getUri(updateDataDocument, LinkRelation.Self); const uri1 = LinkUtil.getUri(collectionResourceItem, LinkRelation.Self); if (uri && uri1) { resolver.update(uri, uri1); } }); /** * A read-only collection is missing a URI. This is likely to cause problems because * the URI will not be resolvable, because no matching resource can be found. */ const createReadonlyResourceAndUpdateResolver = () => __awaiter(this, void 0, void 0, function* () { // TODO: update interface return undefined; }); /** * A contribute-only collection needs have an item removed. We send a DELETE request * back to the server on the collection URI with a payload containing the URI of the * removed item */ const removeContributeOnlyResourceAndUpdateResolver = (deleteResource) => __awaiter(this, void 0, void 0, function* () { const result = yield ApiUtil.delete(collectionResource, Object.assign(Object.assign({}, options), { where: deleteResource })); if (result) { const uri = LinkUtil.getUri(deleteResource, LinkRelation.Self); log.debug('sync on collection delete \'%s\'', LinkUtil.getUri(collectionResource, LinkRelation.Self)); if (uri) { resolver.remove(uri); } } else { log.debug('sync delete \'%s\' %s', LinkUtil.getUri(deleteResource, LinkRelation.Self)); } }); /** * Don't make request back to update, just remember the URI mapping so that if a reference to the * network of data resource is required we can resolve a document reference to the real resource in * our network. */ const updateContributeOnlyResourceAndUpdateResolver = (collectionResourceItem, updateDataDocument) => __awaiter(this, void 0, void 0, function* () { // at this point, it is the same implementation as the read-only form return yield updateReadonlyResourceAndUpdateResolver(collectionResourceItem, updateDataDocument); }); /** * A contribute-only collection is missing a URI. This is likely to cause problems because * the URI will not be resolvable, because no matching resource can be found. It will then attempt to * add the item to the collection */ const addContributeOnlyResourceAndUpdateResolver = (createDataDocument) => __awaiter(this, void 0, void 0, function* () { return yield createResourceAndUpdateResolver(createDataDocument); }); const makeOptions = () => { if (contributeonly) { log.debug(`contribute-only collection '${LinkUtil.getUri(collectionResource, LinkRelation.Self)}'`); return { createStrategy: addContributeOnlyResourceAndUpdateResolver, updateStrategy: updateContributeOnlyResourceAndUpdateResolver, deleteStrategy: removeContributeOnlyResourceAndUpdateResolver, }; // If the caller has signalled that the collection is read-only, or the collection // if missing a 'create-form' representation then we assume that the NOD can // not be changed. } else if (readonly || !LinkUtil.matches(collectionResource, LinkRelation.CreateForm)) { log.debug(`read-only collection '${LinkUtil.getUri(collectionResource, LinkRelation.Self)}'`); return { createStrategy: createReadonlyResourceAndUpdateResolver, updateStrategy: updateReadonlyResourceAndUpdateResolver, deleteStrategy: deleteReadonlyResourceAndUpdateResolver, }; } else { log.debug(`updatable collection '${LinkUtil.getUri(collectionResource, LinkRelation.Self)}'`); return { createStrategy: createResourceAndUpdateResolver, updateStrategy: updateResourceAndUpdateResolver, deleteStrategy: deleteResourceAndUpdateResolver, }; } }; return yield Differencer.difference(collectionResource, collectionDocument, Object.assign(Object.assign({}, options), makeOptions())); }); }; //# sourceMappingURL=syncUtil.js.map