UNPKG

@aws-amplify/datastore

Version:

AppSyncLocal support for aws-amplify

996 lines • 61.8 kB
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