@aws-amplify/datastore
Version:
AppSyncLocal support for aws-amplify
996 lines • 61.8 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __spread = (this && this.__spread) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};
import { Amplify, ConsoleLogger as Logger, Hub, JS } from '@aws-amplify/core';
import { immerable, produce, setAutoFreeze, enablePatches, } from 'immer';
import { v4 as uuid4 } from 'uuid';
import Observable from 'zen-observable-ts';
import { defaultAuthStrategy, multiAuthStrategy } from '../authModeStrategies';
import { isPredicatesAll, ModelPredicateCreator, ModelSortPredicateCreator, } from '../predicates';
import { ExclusiveStorage as Storage } from '../storage/storage';
import { ControlMessage, SyncEngine } from '../sync';
import { GraphQLScalarType, isGraphQLScalarType, AuthModeStrategyType, isNonModelFieldType, isModelFieldType, } from '../types';
import { DATASTORE, establishRelationAndKeys, exhaustiveCheck, isModelConstructor, monotonicUlidFactory, STORAGE, SYNC, USER, isNullOrUndefined, registerNonModelClass, sortCompareFunction, DeferredCallbackResolver, } from '../util';
setAutoFreeze(true);
enablePatches();
var logger = new Logger('DataStore');
var ulid = monotonicUlidFactory(Date.now());
var isNode = JS.browserOrNode().isNode;
var SETTING_SCHEMA_VERSION = 'schemaVersion';
var schema;
var modelNamespaceMap = new WeakMap();
// stores data for crafting the correct update mutation input for a model
// Patch[] - array of changed fields and metadata
// PersistentModel - the source model, used for diffing object-type fields
var modelPatchesMap = new WeakMap();
var getModelDefinition = function (modelConstructor) {
var namespace = modelNamespaceMap.get(modelConstructor);
return schema.namespaces[namespace].models[modelConstructor.name];
};
var isValidModelConstructor = function (obj) {
return isModelConstructor(obj) && modelNamespaceMap.has(obj);
};
var namespaceResolver = function (modelConstructor) {
return modelNamespaceMap.get(modelConstructor);
};
// exporting syncClasses for testing outbox.test.ts
export var syncClasses;
var userClasses;
var dataStoreClasses;
var storageClasses;
var initSchema = function (userSchema) {
var _a;
if (schema !== undefined) {
console.warn('The schema has already been initialized');
return userClasses;
}
logger.log('validating schema', { schema: userSchema });
var internalUserNamespace = __assign({ name: USER }, userSchema);
logger.log('DataStore', 'Init models');
userClasses = createTypeClasses(internalUserNamespace);
logger.log('DataStore', 'Models initialized');
var dataStoreNamespace = getNamespace();
var storageNamespace = Storage.getNamespace();
var syncNamespace = SyncEngine.getNamespace();
dataStoreClasses = createTypeClasses(dataStoreNamespace);
storageClasses = createTypeClasses(storageNamespace);
syncClasses = createTypeClasses(syncNamespace);
schema = {
namespaces: (_a = {},
_a[dataStoreNamespace.name] = dataStoreNamespace,
_a[internalUserNamespace.name] = internalUserNamespace,
_a[storageNamespace.name] = storageNamespace,
_a[syncNamespace.name] = syncNamespace,
_a),
version: userSchema.version,
};
Object.keys(schema.namespaces).forEach(function (namespace) {
var e_1, _a;
var _b = __read(establishRelationAndKeys(schema.namespaces[namespace]), 2), relations = _b[0], keys = _b[1];
schema.namespaces[namespace].relationships = relations;
schema.namespaces[namespace].keys = keys;
var modelAssociations = new Map();
Object.values(schema.namespaces[namespace].models).forEach(function (model) {
var connectedModels = [];
Object.values(model.fields)
.filter(function (field) {
return field.association &&
field.association.connectionType === 'BELONGS_TO' &&
field.type.model !== model.name;
})
.forEach(function (field) {
return connectedModels.push(field.type.model);
});
modelAssociations.set(model.name, connectedModels);
});
var result = new Map();
var count = 1000;
while (true && count > 0) {
if (modelAssociations.size === 0) {
break;
}
count--;
if (count === 0) {
throw new Error('Models are not topologically sortable. Please verify your schema.');
}
try {
for (var _c = (e_1 = void 0, __values(Array.from(modelAssociations.keys()))), _d = _c.next(); !_d.done; _d = _c.next()) {
var modelName = _d.value;
var parents = modelAssociations.get(modelName);
if (parents.every(function (x) { return result.has(x); })) {
result.set(modelName, parents);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
Array.from(result.keys()).forEach(function (x) { return modelAssociations.delete(x); });
}
schema.namespaces[namespace].modelTopologicalOrdering = result;
});
return userClasses;
};
var createTypeClasses = function (namespace) {
var classes = {};
Object.entries(namespace.models).forEach(function (_a) {
var _b = __read(_a, 2), modelName = _b[0], modelDefinition = _b[1];
var clazz = createModelClass(modelDefinition);
classes[modelName] = clazz;
modelNamespaceMap.set(clazz, namespace.name);
});
Object.entries(namespace.nonModels || {}).forEach(function (_a) {
var _b = __read(_a, 2), typeName = _b[0], typeDefinition = _b[1];
var clazz = createNonModelClass(typeDefinition);
classes[typeName] = clazz;
});
return classes;
};
var instancesMetadata = new WeakSet();
function modelInstanceCreator(modelConstructor, init) {
instancesMetadata.add(init);
return new modelConstructor(init);
}
var validateModelFields = function (modelDefinition) { return function (k, v) {
var fieldDefinition = modelDefinition.fields[k];
if (fieldDefinition !== undefined) {
var type = fieldDefinition.type, isRequired_1 = fieldDefinition.isRequired, isArrayNullable = fieldDefinition.isArrayNullable, name_1 = fieldDefinition.name, isArray = fieldDefinition.isArray;
if (((!isArray && isRequired_1) || (isArray && !isArrayNullable)) &&
(v === null || v === undefined)) {
throw new Error("Field " + name_1 + " is required");
}
if (isGraphQLScalarType(type)) {
var jsType_1 = GraphQLScalarType.getJSType(type);
var validateScalar_1 = GraphQLScalarType.getValidationFunction(type);
if (type === 'AWSJSON') {
if (typeof v === jsType_1) {
return;
}
if (typeof v === 'string') {
try {
JSON.parse(v);
return;
}
catch (error) {
throw new Error("Field " + name_1 + " is an invalid JSON object. " + v);
}
}
}
if (isArray) {
var errorTypeText = jsType_1;
if (!isRequired_1) {
errorTypeText = jsType_1 + " | null | undefined";
}
if (!Array.isArray(v) && !isArrayNullable) {
throw new Error("Field " + name_1 + " should be of type [" + errorTypeText + "], " + typeof v + " received. " + v);
}
if (!isNullOrUndefined(v) &&
v.some(function (e) {
return isNullOrUndefined(e) ? isRequired_1 : typeof e !== jsType_1;
})) {
var elemTypes = v
.map(function (e) { return (e === null ? 'null' : typeof e); })
.join(',');
throw new Error("All elements in the " + name_1 + " array should be of type " + errorTypeText + ", [" + elemTypes + "] received. " + v);
}
if (validateScalar_1 && !isNullOrUndefined(v)) {
var validationStatus = v.map(function (e) {
if (!isNullOrUndefined(e)) {
return validateScalar_1(e);
}
else if (isNullOrUndefined(e) && !isRequired_1) {
return true;
}
else {
return false;
}
});
if (!validationStatus.every(function (s) { return s; })) {
throw new Error("All elements in the " + name_1 + " array should be of type " + type + ", validation failed for one or more elements. " + v);
}
}
}
else if (!isRequired_1 && v === undefined) {
return;
}
else if (typeof v !== jsType_1 && v !== null) {
throw new Error("Field " + name_1 + " should be of type " + jsType_1 + ", " + typeof v + " received. " + v);
}
else if (!isNullOrUndefined(v) &&
validateScalar_1 &&
!validateScalar_1(v)) {
throw new Error("Field " + name_1 + " should be of type " + type + ", validation failed. " + v);
}
}
}
}; };
var castInstanceType = function (modelDefinition, k, v) {
var _a = modelDefinition.fields[k] || {}, isArray = _a.isArray, type = _a.type;
// attempt to parse stringified JSON
if (typeof v === 'string' &&
(isArray ||
type === 'AWSJSON' ||
isNonModelFieldType(type) ||
isModelFieldType(type))) {
try {
return JSON.parse(v);
}
catch (_b) {
// if JSON is invalid, don't throw and let modelValidator handle it
}
}
// cast from numeric representation of boolean to JS boolean
if (typeof v === 'number' && type === 'Boolean') {
return Boolean(v);
}
return v;
};
var initializeInstance = function (init, modelDefinition, draft) {
var modelValidator = validateModelFields(modelDefinition);
Object.entries(init).forEach(function (_a) {
var _b = __read(_a, 2), k = _b[0], v = _b[1];
var parsedValue = castInstanceType(modelDefinition, k, v);
modelValidator(k, parsedValue);
draft[k] = parsedValue;
});
};
var createModelClass = function (modelDefinition) {
var clazz = /** @class */ (function () {
function Model(init) {
var instance = produce(this, function (draft) {
initializeInstance(init, modelDefinition, draft);
var modelInstanceMetadata = instancesMetadata.has(init)
? init
: {};
var _id = modelInstanceMetadata.id, _version = modelInstanceMetadata._version, _lastChangedAt = modelInstanceMetadata._lastChangedAt, _deleted = modelInstanceMetadata._deleted;
// instancesIds are set by modelInstanceCreator, it is accessible only internally
var isInternal = _id !== null && _id !== undefined;
var id = isInternal
? _id
: modelDefinition.syncable
? uuid4()
: ulid();
if (!isInternal) {
checkReadOnlyPropertyOnCreate(draft, modelDefinition);
}
draft.id = id;
if (modelDefinition.syncable) {
draft._version = _version;
draft._lastChangedAt = _lastChangedAt;
draft._deleted = _deleted;
}
});
return instance;
}
Model.copyOf = function (source, fn) {
var modelConstructor = Object.getPrototypeOf(source || {}).constructor;
if (!isValidModelConstructor(modelConstructor)) {
var msg = 'The source object is not a valid model';
logger.error(msg, { source: source });
throw new Error(msg);
}
var patches;
var model = produce(source, function (draft) {
fn(draft);
draft.id = source.id;
var modelValidator = validateModelFields(modelDefinition);
Object.entries(draft).forEach(function (_a) {
var _b = __read(_a, 2), k = _b[0], v = _b[1];
var parsedValue = castInstanceType(modelDefinition, k, v);
modelValidator(k, parsedValue);
});
}, function (p) { return (patches = p); });
if (patches.length) {
modelPatchesMap.set(model, [patches, source]);
checkReadOnlyPropertyOnUpdate(patches, modelDefinition);
}
return model;
};
// "private" method (that's hidden via `Setting`) for `withSSRContext` to use
// to gain access to `modelInstanceCreator` and `clazz` for persisting IDs from server to client.
Model.fromJSON = function (json) {
var _this = this;
if (Array.isArray(json)) {
return json.map(function (init) { return _this.fromJSON(init); });
}
var instance = modelInstanceCreator(clazz, json);
var modelValidator = validateModelFields(modelDefinition);
Object.entries(instance).forEach(function (_a) {
var _b = __read(_a, 2), k = _b[0], v = _b[1];
modelValidator(k, v);
});
return instance;
};
return Model;
}());
clazz[immerable] = true;
Object.defineProperty(clazz, 'name', { value: modelDefinition.name });
return clazz;
};
var checkReadOnlyPropertyOnCreate = function (draft, modelDefinition) {
var modelKeys = Object.keys(draft);
var fields = modelDefinition.fields;
modelKeys.forEach(function (key) {
if (fields[key] && fields[key].isReadOnly) {
throw new Error(key + " is read-only.");
}
});
};
var checkReadOnlyPropertyOnUpdate = function (patches, modelDefinition) {
var patchArray = patches.map(function (p) { return [p.path[0], p.value]; });
var fields = modelDefinition.fields;
patchArray.forEach(function (_a) {
var _b = __read(_a, 2), key = _b[0], val = _b[1];
if (!val || !fields[key])
return;
if (fields[key].isReadOnly) {
throw new Error(key + " is read-only.");
}
});
};
var createNonModelClass = function (typeDefinition) {
var clazz = /** @class */ (function () {
function Model(init) {
var instance = produce(this, function (draft) {
initializeInstance(init, typeDefinition, draft);
});
return instance;
}
return Model;
}());
clazz[immerable] = true;
Object.defineProperty(clazz, 'name', { value: typeDefinition.name });
registerNonModelClass(clazz);
return clazz;
};
function isQueryOne(obj) {
return typeof obj === 'string';
}
function defaultConflictHandler(conflictData) {
var localModel = conflictData.localModel, modelConstructor = conflictData.modelConstructor, remoteModel = conflictData.remoteModel;
var _version = remoteModel._version;
return modelInstanceCreator(modelConstructor, __assign(__assign({}, localModel), { _version: _version }));
}
function defaultErrorHandler(error) {
logger.warn(error);
}
function getModelConstructorByModelName(namespaceName, modelName) {
var result;
switch (namespaceName) {
case DATASTORE:
result = dataStoreClasses[modelName];
break;
case USER:
result = userClasses[modelName];
break;
case SYNC:
result = syncClasses[modelName];
break;
case STORAGE:
result = storageClasses[modelName];
break;
default:
exhaustiveCheck(namespaceName);
break;
}
if (isValidModelConstructor(result)) {
return result;
}
else {
var msg = "Model name is not valid for namespace. modelName: " + modelName + ", namespace: " + namespaceName;
logger.error(msg);
throw new Error(msg);
}
}
function checkSchemaVersion(storage, version) {
return __awaiter(this, void 0, void 0, function () {
var Setting, modelDefinition;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
Setting = dataStoreClasses.Setting;
modelDefinition = schema.namespaces[DATASTORE].models.Setting;
return [4 /*yield*/, storage.runExclusive(function (s) { return __awaiter(_this, void 0, void 0, function () {
var _a, schemaVersionSetting, storedValue;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, s.query(Setting, ModelPredicateCreator.createFromExisting(modelDefinition, function (c) {
// @ts-ignore Argument of type '"eq"' is not assignable to parameter of type 'never'.
return c.key('eq', SETTING_SCHEMA_VERSION);
}), { page: 0, limit: 1 })];
case 1:
_a = __read.apply(void 0, [_b.sent(), 1]), schemaVersionSetting = _a[0];
if (!(schemaVersionSetting !== undefined &&
schemaVersionSetting.value !== undefined)) return [3 /*break*/, 4];
storedValue = JSON.parse(schemaVersionSetting.value);
if (!(storedValue !== version)) return [3 /*break*/, 3];
return [4 /*yield*/, s.clear(false)];
case 2:
_b.sent();
_b.label = 3;
case 3: return [3 /*break*/, 6];
case 4: return [4 /*yield*/, s.save(modelInstanceCreator(Setting, {
key: SETTING_SCHEMA_VERSION,
value: JSON.stringify(version),
}))];
case 5:
_b.sent();
_b.label = 6;
case 6: return [2 /*return*/];
}
});
}); })];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
}
var syncSubscription;
function getNamespace() {
var namespace = {
name: DATASTORE,
relationships: {},
enums: {},
nonModels: {},
models: {
Setting: {
name: 'Setting',
pluralName: 'Settings',
syncable: false,
fields: {
id: {
name: 'id',
type: 'ID',
isRequired: true,
isArray: false,
},
key: {
name: 'key',
type: 'String',
isRequired: true,
isArray: false,
},
value: {
name: 'value',
type: 'String',
isRequired: true,
isArray: false,
},
},
},
},
};
return namespace;
}
var DataStore = /** @class */ (function () {
function DataStore() {
var _this = this;
this.amplifyConfig = {};
this.syncPredicates = new WeakMap();
this.start = function () { return __awaiter(_this, void 0, void 0, function () {
var aws_appsync_graphqlEndpoint, _a, fullSyncIntervalInMilliseconds;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!(this.initialized === undefined)) return [3 /*break*/, 1];
logger.debug('Starting DataStore');
this.initialized = new Promise(function (res, rej) {
_this.initResolve = res;
_this.initReject = rej;
});
return [3 /*break*/, 3];
case 1: return [4 /*yield*/, this.initialized];
case 2:
_b.sent();
return [2 /*return*/];
case 3:
this.storage = new Storage(schema, namespaceResolver, getModelConstructorByModelName, modelInstanceCreator, this.storageAdapter, this.sessionId);
return [4 /*yield*/, this.storage.init()];
case 4:
_b.sent();
return [4 /*yield*/, checkSchemaVersion(this.storage, schema.version)];
case 5:
_b.sent();
aws_appsync_graphqlEndpoint = this.amplifyConfig.aws_appsync_graphqlEndpoint;
if (!aws_appsync_graphqlEndpoint) return [3 /*break*/, 7];
logger.debug('GraphQL endpoint available', aws_appsync_graphqlEndpoint);
_a = this;
return [4 /*yield*/, this.processSyncExpressions()];
case 6:
_a.syncPredicates = _b.sent();
this.sync = new SyncEngine(schema, namespaceResolver, syncClasses, userClasses, this.storage, modelInstanceCreator, this.maxRecordsToSync, this.syncPageSize, this.conflictHandler, this.errorHandler, this.syncPredicates, this.amplifyConfig, this.authModeStrategy);
fullSyncIntervalInMilliseconds = this.fullSyncInterval * 1000 * 60;
syncSubscription = this.sync
.start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
.subscribe({
next: function (_a) {
var type = _a.type, data = _a.data;
// In Node, we need to wait for queries to be synced to prevent returning empty arrays.
// In the Browser, we can begin returning data once subscriptions are in place.
var readyType = isNode
? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
: ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;
if (type === readyType) {
_this.initResolve();
}
Hub.dispatch('datastore', {
event: type,
data: data,
});
},
error: function (err) {
logger.warn('Sync error', err);
_this.initReject();
},
});
return [3 /*break*/, 8];
case 7:
logger.warn("Data won't be synchronized. No GraphQL endpoint configured. Did you forget `Amplify.configure(awsconfig)`?", {
config: this.amplifyConfig,
});
this.initResolve();
_b.label = 8;
case 8: return [4 /*yield*/, this.initialized];
case 9:
_b.sent();
return [2 /*return*/];
}
});
}); };
this.query = function (modelConstructor, idOrCriteria, paginationProducer) { return __awaiter(_this, void 0, void 0, function () {
var msg, modelDefinition, predicate, pagination, result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.start()];
case 1:
_a.sent();
//#region Input validation
if (!isValidModelConstructor(modelConstructor)) {
msg = 'Constructor is not for a valid model';
logger.error(msg, { modelConstructor: modelConstructor });
throw new Error(msg);
}
if (typeof idOrCriteria === 'string') {
if (paginationProducer !== undefined) {
logger.warn('Pagination is ignored when querying by id');
}
}
modelDefinition = getModelDefinition(modelConstructor);
if (isQueryOne(idOrCriteria)) {
predicate = ModelPredicateCreator.createForId(modelDefinition, idOrCriteria);
}
else {
if (isPredicatesAll(idOrCriteria)) {
// Predicates.ALL means "all records", so no predicate (undefined)
predicate = undefined;
}
else {
predicate = ModelPredicateCreator.createFromExisting(modelDefinition, idOrCriteria);
}
}
pagination = this.processPagination(modelDefinition, paginationProducer);
//#endregion
logger.debug('params ready', {
modelConstructor: modelConstructor,
predicate: ModelPredicateCreator.getPredicates(predicate, false),
pagination: __assign(__assign({}, pagination), { sort: ModelSortPredicateCreator.getPredicates(pagination && pagination.sort, false) }),
});
return [4 /*yield*/, this.storage.query(modelConstructor, predicate, pagination)];
case 2:
result = _a.sent();
return [2 /*return*/, isQueryOne(idOrCriteria) ? result[0] : result];
}
});
}); };
this.save = function (model, condition) { return __awaiter(_this, void 0, void 0, function () {
var patchesTuple, modelConstructor, msg, modelDefinition, producedCondition, _a, savedModel;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, this.start()];
case 1:
_b.sent();
patchesTuple = modelPatchesMap.get(model);
modelConstructor = model
? model.constructor
: undefined;
if (!isValidModelConstructor(modelConstructor)) {
msg = 'Object is not an instance of a valid model';
logger.error(msg, { model: model });
throw new Error(msg);
}
modelDefinition = getModelDefinition(modelConstructor);
producedCondition = ModelPredicateCreator.createFromExisting(modelDefinition, condition);
return [4 /*yield*/, this.storage.runExclusive(function (s) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, s.save(model, producedCondition, undefined, patchesTuple)];
case 1:
_a.sent();
return [2 /*return*/, s.query(modelConstructor, ModelPredicateCreator.createForId(modelDefinition, model.id))];
}
});
}); })];
case 2:
_a = __read.apply(void 0, [_b.sent(), 1]), savedModel = _a[0];
return [2 /*return*/, savedModel];
}
});
}); };
this.setConflictHandler = function (config) {
var configDataStore = config.DataStore;
var conflictHandlerIsDefault = function () {
return _this.conflictHandler === defaultConflictHandler;
};
if (configDataStore && configDataStore.conflictHandler) {
return configDataStore.conflictHandler;
}
if (conflictHandlerIsDefault() && config.conflictHandler) {
return config.conflictHandler;
}
return _this.conflictHandler || defaultConflictHandler;
};
this.setErrorHandler = function (config) {
var configDataStore = config.DataStore;
var errorHandlerIsDefault = function () {
return _this.errorHandler === defaultErrorHandler;
};
if (configDataStore && configDataStore.errorHandler) {
return configDataStore.errorHandler;
}
if (errorHandlerIsDefault() && config.errorHandler) {
return config.errorHandler;
}
return _this.errorHandler || defaultErrorHandler;
};
this.delete = function (modelOrConstructor, idOrCriteria) { return __awaiter(_this, void 0, void 0, function () {
var condition, msg, modelConstructor, msg, msg, _a, deleted, model, modelConstructor, msg, modelDefinition, idPredicate, msg, _b, _c, deleted;
return __generator(this, function (_d) {
switch (_d.label) {
case 0: return [4 /*yield*/, this.start()];
case 1:
_d.sent();
if (!modelOrConstructor) {
msg = 'Model or Model Constructor required';
logger.error(msg, { modelOrConstructor: modelOrConstructor });
throw new Error(msg);
}
if (!isValidModelConstructor(modelOrConstructor)) return [3 /*break*/, 3];
modelConstructor = modelOrConstructor;
if (!idOrCriteria) {
msg = 'Id to delete or criteria required. Do you want to delete all? Pass Predicates.ALL';
logger.error(msg, { idOrCriteria: idOrCriteria });
throw new Error(msg);
}
if (typeof idOrCriteria === 'string') {
condition = ModelPredicateCreator.createForId(getModelDefinition(modelConstructor), idOrCriteria);
}
else {
condition = ModelPredicateCreator.createFromExisting(getModelDefinition(modelConstructor),
/**
* idOrCriteria is always a ProducerModelPredicate<T>, never a symbol.
* The symbol is used only for typing purposes. e.g. see Predicates.ALL
*/
idOrCriteria);
if (!condition || !ModelPredicateCreator.isValidPredicate(condition)) {
msg = 'Criteria required. Do you want to delete all? Pass Predicates.ALL';
logger.error(msg, { condition: condition });
throw new Error(msg);
}
}
return [4 /*yield*/, this.storage.delete(modelConstructor, condition)];
case 2:
_a = __read.apply(void 0, [_d.sent(), 1]), deleted = _a[0];
return [2 /*return*/, deleted];
case 3:
model = modelOrConstructor;
modelConstructor = Object.getPrototypeOf(model || {})
.constructor;
if (!isValidModelConstructor(modelConstructor)) {
msg = 'Object is not an instance of a valid model';
logger.error(msg, { model: model });
throw new Error(msg);
}
modelDefinition = getModelDefinition(modelConstructor);
idPredicate = ModelPredicateCreator.createForId(modelDefinition, model.id);
if (idOrCriteria) {
if (typeof idOrCriteria !== 'function') {
msg = 'Invalid criteria';
logger.error(msg, { idOrCriteria: idOrCriteria });
throw new Error(msg);
}
condition = idOrCriteria(idPredicate);
}
else {
condition = idPredicate;
}
return [4 /*yield*/, this.storage.delete(model, condition)];
case 4:
_b = __read.apply(void 0, [_d.sent(), 1]), _c = __read(_b[0], 1), deleted = _c[0];
return [2 /*return*/, deleted];
}
});
}); };
this.observe = function (modelOrConstructor, idOrCriteria) {
var predicate;
var modelConstructor = modelOrConstructor && isValidModelConstructor(modelOrConstructor)
? modelOrConstructor
: undefined;
if (modelOrConstructor && modelConstructor === undefined) {
var model = modelOrConstructor;
var modelConstructor_1 = model && Object.getPrototypeOf(model).constructor;
if (isValidModelConstructor(modelConstructor_1)) {
if (idOrCriteria) {
logger.warn('idOrCriteria is ignored when using a model instance', {
model: model,
idOrCriteria: idOrCriteria,
});
}
return _this.observe(modelConstructor_1, model.id);
}
else {
var msg = 'The model is not an instance of a PersistentModelConstructor';
logger.error(msg, { model: model });
throw new Error(msg);
}
}
if (idOrCriteria !== undefined && modelConstructor === undefined) {
var msg = 'Cannot provide criteria without a modelConstructor';
logger.error(msg, idOrCriteria);
throw new Error(msg);
}
if (modelConstructor && !isValidModelConstructor(modelConstructor)) {
var msg = 'Constructor is not for a valid model';
logger.error(msg, { modelConstructor: modelConstructor });
throw new Error(msg);
}
if (typeof idOrCriteria === 'string') {
predicate = ModelPredicateCreator.createForId(getModelDefinition(modelConstructor), idOrCriteria);
}
else {
predicate =
modelConstructor &&
ModelPredicateCreator.createFromExisting(getModelDefinition(modelConstructor), idOrCriteria);
}
return new Observable(function (observer) {
var handle;
(function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.start()];
case 1:
_a.sent();
handle = this.storage
.observe(modelConstructor, predicate)
.filter(function (_a) {
var model = _a.model;
return namespaceResolver(model) === USER;
})
.subscribe(observer);
return [2 /*return*/];
}
});
}); })();
return function () {
if (handle) {
handle.unsubscribe();
}
};
});
};
this.observeQuery = function (model, criteria, options) {
return new Observable(function (observer) {
var items = new Map();
var itemsChanged = new Map();
var deletedItemIds = [];
var handle;
var generateAndEmitSnapshot = function () {
var snapshot = generateSnapshot();
emitSnapshot(snapshot);
};
// a mechanism to return data after X amount of seconds OR after the
// "limit" (itemsChanged >= this.syncPageSize) has been reached, whichever comes first
var limitTimerRace = new DeferredCallbackResolver({
callback: generateAndEmitSnapshot,
errorHandler: observer.error,
maxInterval: 2000,
});
var sort = (options || {}).sort;
var sortOptions = sort ? { sort: sort } : undefined;
(function () { return __awaiter(_this, void 0, void 0, function () {
var err_1;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.query(model, criteria, sortOptions)];
case 1:
// first, query and return any locally-available records
(_a.sent()).forEach(function (item) {
return items.set(item.id, item);
});
// observe the model and send a stream of updates (debounced)
handle = this.observe(model,
// @ts-ignore TODO: fix this TSlint error
criteria).subscribe(function (_a) {
var element = _a.element, model = _a.model, opType = _a.opType;
var _b, _c;
// Flag items which have been recently deleted
// NOTE: Merging of separate operations to the same model instance is handled upstream
// in the `mergePage` method within src/sync/merger.ts. The final state of a model instance
// depends on the LATEST record (for a given id).
if (opType === 'DELETE') {
deletedItemIds.push(element.id);
}
else {
itemsChanged.set(element.id, element);
}
var isSynced = (_c = (_b = _this.sync) === null || _b === void 0 ? void 0 : _b.getModelSyncedStatus(model)) !== null && _c !== void 0 ? _c : false;
var limit = itemsChanged.size - deletedItemIds.length >= _this.syncPageSize;
if (limit || isSynced) {
limitTimerRace.resolve();
}
// kicks off every subsequent race as results sync down
limitTimerRace.start();
});
// returns a set of initial/locally-available results
generateAndEmitSnapshot();
return [3 /*break*/, 3];
case 2:
err_1 = _a.sent();
observer.error(err_1);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
}); })();
// TODO: abstract this function into a util file to be able to write better unit tests
var generateSnapshot = function () {
var _a, _b;
var isSynced = (_b = (_a = _this.sync) === null || _a === void 0 ? void 0 : _a.getModelSyncedStatus(model)) !== null && _b !== void 0 ? _b : false;
var itemsArray = __spread(Array.from(items.values()), Array.from(itemsChanged.values()));
if (options === null || options === void 0 ? void 0 : options.sort) {
sortItems(itemsArray);
}
items.clear();
itemsArray.forEach(function (item) { return items.set(item.id, item); });
// remove deleted items from the final result set
deletedItemIds.forEach(function (id) { return items.delete(id); });
return {
items: Array.from(items.values()),
isSynced: isSynced,
};
};
var emitSnapshot = function (snapshot) {
// send the generated snapshot to the primary subscription
observer.next(snapshot);
// reset the changed items sets
itemsChanged.clear();
deletedItemIds = [];
};
var sortItems = function (itemsToSort) {
var modelDefinition = getModelDefinition(model);
var pagination = _this.processPagination(modelDefinition, options);
var sortPredicates = ModelSortPredicateCreator.getPredicates(pagination.sort);
if (sortPredicates.length) {
var compareFn = sortCompareFunction(sortPredicates);
itemsToSort.sort(compareFn);
}
};
// send one last snapshot when the model is fully synced
var hubCallback = function (_a) {
var payload = _a.payload;
var _b;
var event = payload.event, data = payload.data;
if (event === ControlMessage.SYNC_ENGINE_MODEL_SYNCED &&
((_b = data === null || data === void 0 ? void 0 : data.model) === null || _b === void 0 ? void 0 : _b.name) === model.name) {
generateAndEmitSnapshot();
Hub.remove('api', hubCallback);
}
};
Hub.listen('datastore', hubCallback);
return function () {
if (handle) {
handle.unsubscribe();
}
};
});
};
this.configure = function (config) {
if (config === void 0) { config = {}; }
var configDataStore = config.DataStore, configAuthModeStrategyType = config.authModeStrategyType, configConflictHandler = config.conflictHandl