@aws-amplify/datastore
Version:
AppSyncLocal support for aws-amplify
437 lines • 20.8 kB
JavaScript
import { __asyncValues, __awaiter, __generator, __values } from "tslib";
import { ConsoleLogger as Logger } from '@aws-amplify/core';
import { ModelPredicateCreator } from '../../predicates';
import { isPredicateObj, } from '../../types';
import { getStorename, getIndexKeys, extractPrimaryKeyValues, traverseModel, validatePredicate, isModelConstructor, extractPrimaryKeyFieldNames, } from '../../util';
import { ModelRelationship } from '../relationship';
var logger = new Logger('DataStore');
var DB_NAME = 'amplify-datastore';
var StorageAdapterBase = /** @class */ (function () {
function StorageAdapterBase() {
this.dbName = DB_NAME;
}
/**
* Initializes local DB
*
* @param theSchema
* @param namespaceResolver
* @param modelInstanceCreator
* @param getModelConstructorByModelName
* @param sessionId
*/
StorageAdapterBase.prototype.setUp = function (theSchema, namespaceResolver, modelInstanceCreator, getModelConstructorByModelName, sessionId) {
return __awaiter(this, void 0, void 0, function () {
var _a, error_1;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, this.preSetUpChecks()];
case 1:
_b.sent();
if (!!this.initPromise) return [3 /*break*/, 2];
this.initPromise = new Promise(function (res, rej) {
_this.resolve = res;
_this.reject = rej;
});
return [3 /*break*/, 4];
case 2: return [4 /*yield*/, this.initPromise];
case 3:
_b.sent();
return [2 /*return*/];
case 4:
if (sessionId) {
this.dbName = DB_NAME + "-" + sessionId;
}
this.schema = theSchema;
this.namespaceResolver = namespaceResolver;
this.modelInstanceCreator = modelInstanceCreator;
this.getModelConstructorByModelName = getModelConstructorByModelName;
_b.label = 5;
case 5:
_b.trys.push([5, 8, , 9]);
if (!!this.db) return [3 /*break*/, 7];
_a = this;
return [4 /*yield*/, this.initDb()];
case 6:
_a.db = _b.sent();
this.resolve();
_b.label = 7;
case 7: return [3 /*break*/, 9];
case 8:
error_1 = _b.sent();
this.reject(error_1);
return [3 /*break*/, 9];
case 9: return [2 /*return*/];
}
});
});
};
/**
* @param modelConstructor
* @returns local DB table name
*/
StorageAdapterBase.prototype.getStorenameForModel = function (modelConstructor) {
var namespace = this.namespaceResolver(modelConstructor);
var modelName = modelConstructor.name;
return getStorename(namespace, modelName);
};
/**
*
* @param model - instantiated model record
* @returns the record's primary key values
*/
StorageAdapterBase.prototype.getIndexKeyValuesFromModel = function (model) {
var modelConstructor = Object.getPrototypeOf(model)
.constructor;
var namespaceName = this.namespaceResolver(modelConstructor);
var keys = getIndexKeys(this.schema.namespaces[namespaceName], modelConstructor.name);
return extractPrimaryKeyValues(model, keys);
};
/**
* Common metadata for `save` operation
* used by individual storage adapters
*
* @param model
*/
StorageAdapterBase.prototype.saveMetadata = function (model) {
var _this = this;
var modelConstructor = Object.getPrototypeOf(model)
.constructor;
var storeName = this.getStorenameForModel(modelConstructor);
var namespaceName = this.namespaceResolver(modelConstructor);
var connectedModels = traverseModel(modelConstructor.name, model, this.schema.namespaces[namespaceName], this.modelInstanceCreator, this.getModelConstructorByModelName);
var set = new Set();
var connectionStoreNames = Object.values(connectedModels).map(function (_a) {
var modelName = _a.modelName, item = _a.item, instance = _a.instance;
var storeName = getStorename(namespaceName, modelName);
set.add(storeName);
var keys = getIndexKeys(_this.schema.namespaces[namespaceName], modelName);
return { storeName: storeName, item: item, instance: instance, keys: keys };
});
var modelKeyValues = this.getIndexKeyValuesFromModel(model);
return { storeName: storeName, set: set, connectionStoreNames: connectionStoreNames, modelKeyValues: modelKeyValues };
};
/**
* Enforces conditional save. Throws if condition is not met.
* used by individual storage adapters
*
* @param model
*/
StorageAdapterBase.prototype.validateSaveCondition = function (condition, fromDB) {
if (!(condition && fromDB)) {
return;
}
var predicates = ModelPredicateCreator.getPredicates(condition);
var _a = predicates, predicateObjs = _a.predicates, type = _a.type;
var isValid = validatePredicate(fromDB, type, predicateObjs);
if (!isValid) {
var msg = 'Conditional update failed';
logger.error(msg, { model: fromDB, condition: predicateObjs });
throw new Error(msg);
}
};
/**
* Instantiate models from POJO records returned from the database
*
* @param namespaceName - string model namespace
* @param srcModelName - string model name
* @param records - array of uninstantiated records
* @returns
*/
StorageAdapterBase.prototype.load = function (namespaceName, srcModelName, records) {
return __awaiter(this, void 0, void 0, function () {
var namespace, relations, connectionStoreNames, modelConstructor;
var _this = this;
return __generator(this, function (_a) {
namespace = this.schema.namespaces[namespaceName];
relations = namespace.relationships[srcModelName].relationTypes;
connectionStoreNames = relations.map(function (_a) {
var modelName = _a.modelName;
return getStorename(namespaceName, modelName);
});
modelConstructor = this.getModelConstructorByModelName(namespaceName, srcModelName);
if (connectionStoreNames.length === 0) {
return [2 /*return*/, records.map(function (record) {
return _this.modelInstanceCreator(modelConstructor, record);
})];
}
return [2 /*return*/, records.map(function (record) {
return _this.modelInstanceCreator(modelConstructor, record);
})];
});
});
};
/**
* Extracts operands from a predicate group into an array of key values
* Used in the query method
*
* @param predicates - predicate group
* @param keyPath - string array of key names ['id', 'sortKey']
* @returns string[] of key values
*
* @example
* ```js
* { and:[{ id: { eq: 'abc' }}, { sortKey: { eq: 'def' }}] }
* ```
* Becomes
* ```
* ['abc', 'def']
* ```
*/
StorageAdapterBase.prototype.keyValueFromPredicate = function (predicates, keyPath) {
var e_1, _a;
var predicateObjs = predicates.predicates;
if (predicateObjs.length !== keyPath.length) {
return;
}
var keyValues = [];
var _loop_1 = function (key) {
var predicateObj = predicateObjs.find(function (p) {
// it's a relevant predicate object only if it's an equality
// operation for a key field from the key:
return isPredicateObj(p) &&
p.field === key &&
p.operator === 'eq' &&
p.operand !== null &&
p.operand !== undefined;
});
predicateObj && keyValues.push(predicateObj.operand);
};
try {
for (var keyPath_1 = __values(keyPath), keyPath_1_1 = keyPath_1.next(); !keyPath_1_1.done; keyPath_1_1 = keyPath_1.next()) {
var key = keyPath_1_1.value;
_loop_1(key);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (keyPath_1_1 && !keyPath_1_1.done && (_a = keyPath_1.return)) _a.call(keyPath_1);
}
finally { if (e_1) throw e_1.error; }
}
return keyValues.length === keyPath.length ? keyValues : undefined;
};
/**
* Common metadata for `query` operation
* used by individual storage adapters
*
* @param modelConstructor
* @param predicate
* @param pagination
*/
StorageAdapterBase.prototype.queryMetadata = function (modelConstructor, predicate, pagination) {
var storeName = this.getStorenameForModel(modelConstructor);
var namespaceName = this.namespaceResolver(modelConstructor);
var predicates = predicate && ModelPredicateCreator.getPredicates(predicate);
var keyPath = getIndexKeys(this.schema.namespaces[namespaceName], modelConstructor.name);
var queryByKey = predicates && this.keyValueFromPredicate(predicates, keyPath);
var hasSort = pagination && pagination.sort;
var hasPagination = pagination && pagination.limit;
return {
storeName: storeName,
namespaceName: namespaceName,
queryByKey: queryByKey,
predicates: predicates,
hasSort: hasSort,
hasPagination: hasPagination,
};
};
/**
* Delete record
* Cascades to related records (for Has One and Has Many relationships)
*
* @param modelOrModelConstructor
* @param condition
* @returns
*/
StorageAdapterBase.prototype.delete = function (modelOrModelConstructor, condition) {
return __awaiter(this, void 0, void 0, function () {
var deleteQueue, modelConstructor, namespace, models, deletedModels, deletedModels, model, modelConstructor, namespaceName, storeName, keyValues, fromDB, msg, predicates, _a, predicateObjs, type, isValid, msg, deletedModels;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, this.preOpCheck()];
case 1:
_b.sent();
deleteQueue = [];
if (!isModelConstructor(modelOrModelConstructor)) return [3 /*break*/, 9];
modelConstructor = modelOrModelConstructor;
namespace = this.namespaceResolver(modelConstructor);
return [4 /*yield*/, this.query(modelConstructor, condition)];
case 2:
models = _b.sent();
if (!(condition !== undefined)) return [3 /*break*/, 5];
return [4 /*yield*/, this.deleteTraverse(models, modelConstructor, namespace, deleteQueue)];
case 3:
_b.sent();
return [4 /*yield*/, this.deleteItem(deleteQueue)];
case 4:
_b.sent();
deletedModels = deleteQueue.reduce(function (acc, _a) {
var items = _a.items;
return acc.concat(items);
}, []);
return [2 /*return*/, [models, deletedModels]];
case 5: return [4 /*yield*/, this.deleteTraverse(models, modelConstructor, namespace, deleteQueue)];
case 6:
_b.sent();
return [4 /*yield*/, this.deleteItem(deleteQueue)];
case 7:
_b.sent();
deletedModels = deleteQueue.reduce(function (acc, _a) {
var items = _a.items;
return acc.concat(items);
}, []);
return [2 /*return*/, [models, deletedModels]];
case 8: return [3 /*break*/, 16];
case 9:
model = modelOrModelConstructor;
modelConstructor = Object.getPrototypeOf(model)
.constructor;
namespaceName = this.namespaceResolver(modelConstructor);
storeName = this.getStorenameForModel(modelConstructor);
if (!condition) return [3 /*break*/, 12];
keyValues = this.getIndexKeyValuesFromModel(model);
return [4 /*yield*/, this._get(storeName, keyValues)];
case 10:
fromDB = _b.sent();
if (fromDB === undefined) {
msg = 'Model instance not found in storage';
logger.warn(msg, { model: model });
return [2 /*return*/, [[model], []]];
}
predicates = ModelPredicateCreator.getPredicates(condition);
_a = predicates, predicateObjs = _a.predicates, type = _a.type;
isValid = validatePredicate(fromDB, type, predicateObjs);
if (!isValid) {
msg = 'Conditional update failed';
logger.error(msg, { model: fromDB, condition: predicateObjs });
throw new Error(msg);
}
return [4 /*yield*/, this.deleteTraverse([model], modelConstructor, namespaceName, deleteQueue)];
case 11:
_b.sent();
return [3 /*break*/, 14];
case 12: return [4 /*yield*/, this.deleteTraverse([model], modelConstructor, namespaceName, deleteQueue)];
case 13:
_b.sent();
_b.label = 14;
case 14: return [4 /*yield*/, this.deleteItem(deleteQueue)];
case 15:
_b.sent();
deletedModels = deleteQueue.reduce(function (acc, _a) {
var items = _a.items;
return acc.concat(items);
}, []);
return [2 /*return*/, [[model], deletedModels]];
case 16: return [2 /*return*/];
}
});
});
};
/**
* Recursively traverse relationship graph and add
* all Has One and Has Many relations to `deleteQueue` param
*
* Actual deletion of records added to `deleteQueue` occurs in the `delete` method
*
* @param models
* @param modelConstructor
* @param namespace
* @param deleteQueue
*/
StorageAdapterBase.prototype.deleteTraverse = function (models, modelConstructor, namespace, deleteQueue) {
var models_1, models_1_1;
var e_2, _a, e_3, _b;
return __awaiter(this, void 0, void 0, function () {
var cascadingRelationTypes, model, modelDefinition, modelMeta, relationships, relationships_1, relationships_1_1, r, queryObject, relatedRecords, e_3_1, e_2_1;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
cascadingRelationTypes = ['HAS_ONE', 'HAS_MANY'];
_c.label = 1;
case 1:
_c.trys.push([1, 19, 20, 25]);
models_1 = __asyncValues(models);
_c.label = 2;
case 2: return [4 /*yield*/, models_1.next()];
case 3:
if (!(models_1_1 = _c.sent(), !models_1_1.done)) return [3 /*break*/, 18];
model = models_1_1.value;
modelDefinition = this.schema.namespaces[namespace].models[modelConstructor.name];
modelMeta = {
builder: modelConstructor,
schema: modelDefinition,
pkField: extractPrimaryKeyFieldNames(modelDefinition),
};
relationships = ModelRelationship.allFrom(modelMeta).filter(function (r) {
return cascadingRelationTypes.includes(r.type);
});
_c.label = 4;
case 4:
_c.trys.push([4, 11, 12, 17]);
relationships_1 = __asyncValues(relationships);
_c.label = 5;
case 5: return [4 /*yield*/, relationships_1.next()];
case 6:
if (!(relationships_1_1 = _c.sent(), !relationships_1_1.done)) return [3 /*break*/, 10];
r = relationships_1_1.value;
queryObject = r.createRemoteQueryObject(model);
if (!(queryObject !== null)) return [3 /*break*/, 9];
return [4 /*yield*/, this.query(r.remoteModelConstructor, ModelPredicateCreator.createFromFlatEqualities(r.remoteDefinition, queryObject))];
case 7:
relatedRecords = _c.sent();
return [4 /*yield*/, this.deleteTraverse(relatedRecords, r.remoteModelConstructor, namespace, deleteQueue)];
case 8:
_c.sent();
_c.label = 9;
case 9: return [3 /*break*/, 5];
case 10: return [3 /*break*/, 17];
case 11:
e_3_1 = _c.sent();
e_3 = { error: e_3_1 };
return [3 /*break*/, 17];
case 12:
_c.trys.push([12, , 15, 16]);
if (!(relationships_1_1 && !relationships_1_1.done && (_b = relationships_1.return))) return [3 /*break*/, 14];
return [4 /*yield*/, _b.call(relationships_1)];
case 13:
_c.sent();
_c.label = 14;
case 14: return [3 /*break*/, 16];
case 15:
if (e_3) throw e_3.error;
return [7 /*endfinally*/];
case 16: return [7 /*endfinally*/];
case 17: return [3 /*break*/, 2];
case 18: return [3 /*break*/, 25];
case 19:
e_2_1 = _c.sent();
e_2 = { error: e_2_1 };
return [3 /*break*/, 25];
case 20:
_c.trys.push([20, , 23, 24]);
if (!(models_1_1 && !models_1_1.done && (_a = models_1.return))) return [3 /*break*/, 22];
return [4 /*yield*/, _a.call(models_1)];
case 21:
_c.sent();
_c.label = 22;
case 22: return [3 /*break*/, 24];
case 23:
if (e_2) throw e_2.error;
return [7 /*endfinally*/];
case 24: return [7 /*endfinally*/];
case 25:
deleteQueue.push({
storeName: getStorename(namespace, modelConstructor.name),
items: models,
});
return [2 /*return*/];
}
});
});
};
return StorageAdapterBase;
}());
export { StorageAdapterBase };
//# sourceMappingURL=StorageAdapterBase.js.map