UNPKG

@aws-amplify/datastore

Version:

AppSyncLocal support for aws-amplify

511 lines • 26.5 kB
import { __assign, __awaiter, __generator, __read, __rest, __values } from "tslib"; import { Logger, Mutex } from '@aws-amplify/core'; import PushStream from 'zen-push'; import { ModelPredicateCreator } from '../predicates'; import { OpType, QueryOne, isTargetNameAssociation, } from '../types'; import { isModelConstructor, STORAGE, validatePredicate, valuesEqual, } from '../util'; import { getIdentifierValue } from '../sync/utils'; import getDefaultAdapter from './adapter/getDefaultAdapter'; var logger = new Logger('DataStore'); var StorageClass = /** @class */ (function () { function StorageClass(schema, namespaceResolver, getModelConstructorByModelName, modelInstanceCreator, adapter, sessionId) { this.schema = schema; this.namespaceResolver = namespaceResolver; this.getModelConstructorByModelName = getModelConstructorByModelName; this.modelInstanceCreator = modelInstanceCreator; this.adapter = adapter; this.sessionId = sessionId; this.adapter = this.adapter || getDefaultAdapter(); this.pushStream = new PushStream(); } StorageClass.getNamespace = function () { var namespace = { name: STORAGE, relationships: {}, enums: {}, models: {}, nonModels: {}, }; return namespace; }; StorageClass.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { var resolve, reject; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(this.initialized !== undefined)) return [3 /*break*/, 2]; return [4 /*yield*/, this.initialized]; case 1: _a.sent(); return [2 /*return*/]; case 2: logger.debug('Starting Storage'); this.initialized = new Promise(function (res, rej) { resolve = res; reject = rej; }); this.adapter.setUp(this.schema, this.namespaceResolver, this.modelInstanceCreator, this.getModelConstructorByModelName, this.sessionId).then(resolve, reject); return [4 /*yield*/, this.initialized]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }; StorageClass.prototype.save = function (model, condition, mutator, patchesTuple) { return __awaiter(this, void 0, void 0, function () { var result; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.init()]; case 1: _a.sent(); if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.save(model, condition)]; case 2: result = _a.sent(); result.forEach(function (r) { var _a = __read(r, 2), savedElement = _a[0], opType = _a[1]; // truthy when save is called by the Merger var syncResponse = !!mutator; var updateMutationInput; // don't attempt to calc mutation input when storage.save // is called by Merger, i.e., when processing an AppSync response if ((opType === OpType.UPDATE || opType === OpType.INSERT) && !syncResponse) { // // TODO: LOOK!!! // the `model` used here is in effect regardless of what model // comes back from adapter.save(). // Prior to fix, SQLite adapter had been returning two models // of different types, resulting in invalid outbox entries. // // the bug is essentially fixed in SQLite adapter. // leaving as-is, because it's currently unclear whether anything // depends on this remaining as-is. // updateMutationInput = _this.getChangedFieldsInput(model, savedElement, patchesTuple); // // an update without changed user fields // => don't create mutationEvent if (updateMutationInput === null) { return result; } } var element = updateMutationInput || savedElement; var modelConstructor = Object.getPrototypeOf(savedElement) .constructor; _this.pushStream.next({ model: modelConstructor, opType: opType, element: element, mutator: mutator, condition: (condition && ModelPredicateCreator.getPredicates(condition, false)) || null, savedElement: savedElement, }); }); return [2 /*return*/, result]; } }); }); }; StorageClass.prototype.delete = function (modelOrModelConstructor, condition, mutator) { return __awaiter(this, void 0, void 0, function () { var models, deleted, modelConstructor, namespaceName, modelDefinition, modelIds; var _a; var _this = this; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.init()]; case 1: _b.sent(); if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.delete(modelOrModelConstructor, condition)]; case 2: _a = __read.apply(void 0, [_b.sent(), 2]), models = _a[0], deleted = _a[1]; modelConstructor = isModelConstructor(modelOrModelConstructor) ? modelOrModelConstructor : Object.getPrototypeOf(modelOrModelConstructor || {}) .constructor; namespaceName = this.namespaceResolver(modelConstructor); modelDefinition = this.schema.namespaces[namespaceName].models[modelConstructor.name]; modelIds = new Set(models.map(function (model) { var modelId = getIdentifierValue(modelDefinition, model); return modelId; })); if (!isModelConstructor(modelOrModelConstructor) && !Array.isArray(deleted)) { deleted = [deleted]; } deleted.forEach(function (model) { var modelConstructor = Object.getPrototypeOf(model) .constructor; var theCondition; if (!isModelConstructor(modelOrModelConstructor)) { var modelId = getIdentifierValue(modelDefinition, model); theCondition = modelIds.has(modelId) ? ModelPredicateCreator.getPredicates(condition, false) : undefined; } _this.pushStream.next({ model: modelConstructor, opType: OpType.DELETE, element: model, mutator: mutator, condition: theCondition || null, }); }); return [2 /*return*/, [models, deleted]]; } }); }); }; StorageClass.prototype.query = function (modelConstructor, predicate, pagination) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.init()]; case 1: _a.sent(); if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.query(modelConstructor, predicate, pagination)]; case 2: return [2 /*return*/, _a.sent()]; } }); }); }; StorageClass.prototype.queryOne = function (modelConstructor, firstOrLast) { if (firstOrLast === void 0) { firstOrLast = QueryOne.FIRST; } return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.init()]; case 1: _a.sent(); if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.queryOne(modelConstructor, firstOrLast)]; case 2: return [2 /*return*/, _a.sent()]; } }); }); }; StorageClass.prototype.observe = function (modelConstructor, predicate, skipOwn) { var listenToAll = !modelConstructor; var _a = (predicate && ModelPredicateCreator.getPredicates(predicate, false)) || {}, predicates = _a.predicates, type = _a.type; var result = this.pushStream.observable .filter(function (_a) { var mutator = _a.mutator; return !skipOwn || mutator !== skipOwn; }) .map(function (_a) { var _mutator = _a.mutator, message = __rest(_a, ["mutator"]); return message; }); if (!listenToAll) { result = result.filter(function (_a) { var model = _a.model, element = _a.element; if (modelConstructor !== model) { return false; } if (!!predicates && !!type) { return validatePredicate(element, type, predicates); } return true; }); } return result; }; StorageClass.prototype.clear = function (completeObservable) { if (completeObservable === void 0) { completeObservable = true; } return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: this.initialized = undefined; if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.clear()]; case 1: _a.sent(); if (completeObservable) { this.pushStream.complete(); } return [2 /*return*/]; } }); }); }; StorageClass.prototype.batchSave = function (modelConstructor, items, mutator) { return __awaiter(this, void 0, void 0, function () { var result; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.init()]; case 1: _a.sent(); if (!this.adapter) { throw new Error('Storage adapter is missing'); } return [4 /*yield*/, this.adapter.batchSave(modelConstructor, items)]; case 2: result = _a.sent(); result.forEach(function (_a) { var _b = __read(_a, 2), element = _b[0], opType = _b[1]; _this.pushStream.next({ model: modelConstructor, opType: opType, element: element, mutator: mutator, condition: null, }); }); return [2 /*return*/, result]; } }); }); }; // returns null if no user fields were changed (determined by value comparison) StorageClass.prototype.getChangedFieldsInput = function (model, originalElement, patchesTuple) { var e_1, _a; var _b; var containsPatches = patchesTuple && patchesTuple.length; if (!containsPatches) { return null; } var _c = __read(patchesTuple, 2), patches = _c[0], source = _c[1]; var updatedElement = {}; // extract array of updated fields from patches var updatedFields = (patches.map(function (patch) { return patch.path && patch.path[0]; })); // check model def for association and replace with targetName if exists var modelConstructor = Object.getPrototypeOf(model) .constructor; var namespace = this.namespaceResolver(modelConstructor); var fields = this.schema.namespaces[namespace].models[modelConstructor.name].fields; var _d = ((_b = this.schema.namespaces[namespace].keys) === null || _b === void 0 ? void 0 : _b[modelConstructor.name]) || {}, primaryKey = _d.primaryKey, _e = _d.compositeKeys, compositeKeys = _e === void 0 ? [] : _e; // set original values for these fields updatedFields.forEach(function (field) { var e_2, _a, e_3, _b, e_4, _c, e_5, _d, e_6, _e; var _f; var targetNames = isTargetNameAssociation((_f = fields[field]) === null || _f === void 0 ? void 0 : _f.association); if (Array.isArray(targetNames)) { try { // if field refers to a belongsTo relation, use the target field instead for (var targetNames_1 = __values(targetNames), targetNames_1_1 = targetNames_1.next(); !targetNames_1_1.done; targetNames_1_1 = targetNames_1.next()) { var targetName = targetNames_1_1.value; // check field values by value. Ignore unchanged fields if (!valuesEqual(source[targetName], originalElement[targetName])) { // if the field was updated to 'undefined', replace with 'null' for compatibility with JSON and GraphQL updatedElement[targetName] = originalElement[targetName] === undefined ? null : originalElement[targetName]; try { for (var compositeKeys_1 = (e_3 = void 0, __values(compositeKeys)), compositeKeys_1_1 = compositeKeys_1.next(); !compositeKeys_1_1.done; compositeKeys_1_1 = compositeKeys_1.next()) { var fieldSet = compositeKeys_1_1.value; // include all of the fields that comprise the composite key if (fieldSet.has(targetName)) { try { for (var fieldSet_1 = (e_4 = void 0, __values(fieldSet)), fieldSet_1_1 = fieldSet_1.next(); !fieldSet_1_1.done; fieldSet_1_1 = fieldSet_1.next()) { var compositeField = fieldSet_1_1.value; updatedElement[compositeField] = originalElement[compositeField]; } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (fieldSet_1_1 && !fieldSet_1_1.done && (_c = fieldSet_1.return)) _c.call(fieldSet_1); } finally { if (e_4) throw e_4.error; } } } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (compositeKeys_1_1 && !compositeKeys_1_1.done && (_b = compositeKeys_1.return)) _b.call(compositeKeys_1); } finally { if (e_3) throw e_3.error; } } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (targetNames_1_1 && !targetNames_1_1.done && (_a = targetNames_1.return)) _a.call(targetNames_1); } finally { if (e_2) throw e_2.error; } } } else { // Backwards compatibility pre-CPK // if field refers to a belongsTo relation, use the target field instead var key = targetNames || field; // check field values by value. Ignore unchanged fields if (!valuesEqual(source[key], originalElement[key])) { // if the field was updated to 'undefined', replace with 'null' for compatibility with JSON and GraphQL updatedElement[key] = originalElement[key] === undefined ? null : originalElement[key]; try { for (var compositeKeys_2 = __values(compositeKeys), compositeKeys_2_1 = compositeKeys_2.next(); !compositeKeys_2_1.done; compositeKeys_2_1 = compositeKeys_2.next()) { var fieldSet = compositeKeys_2_1.value; // include all of the fields that comprise the composite key if (fieldSet.has(key)) { try { for (var fieldSet_2 = (e_6 = void 0, __values(fieldSet)), fieldSet_2_1 = fieldSet_2.next(); !fieldSet_2_1.done; fieldSet_2_1 = fieldSet_2.next()) { var compositeField = fieldSet_2_1.value; updatedElement[compositeField] = originalElement[compositeField]; } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (fieldSet_2_1 && !fieldSet_2_1.done && (_e = fieldSet_2.return)) _e.call(fieldSet_2); } finally { if (e_6) throw e_6.error; } } } } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (compositeKeys_2_1 && !compositeKeys_2_1.done && (_d = compositeKeys_2.return)) _d.call(compositeKeys_2); } finally { if (e_5) throw e_5.error; } } } } }); // Exit early when there are no changes introduced in the update mutation if (Object.keys(updatedElement).length === 0) { return null; } // include field(s) from custom PK if one is specified for the model if (primaryKey && primaryKey.length) { try { for (var primaryKey_1 = __values(primaryKey), primaryKey_1_1 = primaryKey_1.next(); !primaryKey_1_1.done; primaryKey_1_1 = primaryKey_1.next()) { var pkField = primaryKey_1_1.value; updatedElement[pkField] = originalElement[pkField]; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (primaryKey_1_1 && !primaryKey_1_1.done && (_a = primaryKey_1.return)) _a.call(primaryKey_1); } finally { if (e_1) throw e_1.error; } } } var id = originalElement.id, _version = originalElement._version, _lastChangedAt = originalElement._lastChangedAt, _deleted = originalElement._deleted; // For update mutations we only want to send fields with changes // and the required internal fields return __assign(__assign({}, updatedElement), { id: id, _version: _version, _lastChangedAt: _lastChangedAt, _deleted: _deleted }); }; return StorageClass; }()); var ExclusiveStorage = /** @class */ (function () { function ExclusiveStorage(schema, namespaceResolver, getModelConstructorByModelName, modelInstanceCreator, adapter, sessionId) { this.mutex = new Mutex(); this.storage = new StorageClass(schema, namespaceResolver, getModelConstructorByModelName, modelInstanceCreator, adapter, sessionId); } ExclusiveStorage.prototype.runExclusive = function (fn) { return this.mutex.runExclusive(fn.bind(this, this.storage)); }; ExclusiveStorage.prototype.save = function (model, condition, mutator, patchesTuple) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.runExclusive(function (storage) { return storage.save(model, condition, mutator, patchesTuple); })]; }); }); }; ExclusiveStorage.prototype.delete = function (modelOrModelConstructor, condition, mutator) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.runExclusive(function (storage) { if (isModelConstructor(modelOrModelConstructor)) { var modelConstructor = modelOrModelConstructor; return storage.delete(modelConstructor, condition, mutator); } else { var model = modelOrModelConstructor; return storage.delete(model, condition, mutator); } })]; }); }); }; ExclusiveStorage.prototype.query = function (modelConstructor, predicate, pagination) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.runExclusive(function (storage) { return storage.query(modelConstructor, predicate, pagination); })]; }); }); }; ExclusiveStorage.prototype.queryOne = function (modelConstructor, firstOrLast) { if (firstOrLast === void 0) { firstOrLast = QueryOne.FIRST; } return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.runExclusive(function (storage) { return storage.queryOne(modelConstructor, firstOrLast); })]; }); }); }; ExclusiveStorage.getNamespace = function () { return StorageClass.getNamespace(); }; ExclusiveStorage.prototype.observe = function (modelConstructor, predicate, skipOwn) { return this.storage.observe(modelConstructor, predicate, skipOwn); }; ExclusiveStorage.prototype.clear = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.runExclusive(function (storage) { return storage.clear(); })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; ExclusiveStorage.prototype.batchSave = function (modelConstructor, items) { return this.storage.batchSave(modelConstructor, items); }; ExclusiveStorage.prototype.init = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.storage.init()]; }); }); }; return ExclusiveStorage; }()); export { ExclusiveStorage }; //# sourceMappingURL=storage.js.map