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
JavaScript
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