UNPKG

@aws-amplify/datastore

Version:

AppSyncLocal support for aws-amplify

1,098 lines (1,097 loc) • 121 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var api_1 = require("@aws-amplify/api"); var auth_1 = require("@aws-amplify/auth"); var cache_1 = require("@aws-amplify/cache"); var core_1 = require("@aws-amplify/core"); var immer_1 = require("immer"); var uuid_1 = require("uuid"); var zen_observable_ts_1 = tslib_1.__importDefault(require("zen-observable-ts")); var authModeStrategies_1 = require("../authModeStrategies"); var predicates_1 = require("../predicates"); var storage_1 = require("../storage/storage"); var relationship_1 = require("../storage/relationship"); var sync_1 = require("../sync"); var types_1 = require("../types"); var util_1 = require("../util"); var next_1 = require("../predicates/next"); var utils_1 = require("../sync/utils"); immer_1.setAutoFreeze(true); immer_1.enablePatches(); var logger = new core_1.ConsoleLogger('DataStore'); var ulid = util_1.monotonicUlidFactory(Date.now()); var isNode = core_1.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); var definition = namespace ? schema.namespaces[namespace].models[modelConstructor.name] : undefined; return definition; }; /** * Determines whether the given object is a Model Constructor that DataStore can * safely use to construct objects and discover related metadata. * * @param obj The object to test. */ var isValidModelConstructor = function (obj) { return util_1.isModelConstructor(obj) && modelNamespaceMap.has(obj); }; var namespaceResolver = function (modelConstructor) { var resolver = modelNamespaceMap.get(modelConstructor); if (!resolver) { throw new Error("Namespace Resolver for '" + modelConstructor.name + "' not found! This is probably a bug in '@amplify-js/datastore'."); } return resolver; }; /** * Creates a predicate without any conditions that can be passed to customer * code to have conditions added to it. * * For example, in this query: * * ```ts * await DataStore.query( * Model, * item => item.field.eq('value') * ); * ``` * * `buildSeedPredicate(Model)` is used to create `item`, which is passed to the * predicate function, which in turn uses that "seed" predicate (`item`) to build * a predicate tree. * * @param modelConstructor The model the predicate will query. */ var buildSeedPredicate = function (modelConstructor) { if (!modelConstructor) throw new Error('Missing modelConstructor'); var modelSchema = getModelDefinition(modelConstructor); if (!modelSchema) throw new Error('Missing modelSchema'); var pks = util_1.extractPrimaryKeyFieldNames(modelSchema); if (!pks) throw new Error('Could not determine PK'); return next_1.recursivePredicateFor({ builder: modelConstructor, schema: modelSchema, pkField: pks, }); }; var userClasses; var dataStoreClasses; var storageClasses; /** * Maps a model to its related models for memoization/immutability. */ var modelInstanceAssociationsMap = new WeakMap(); /** * Describes whether and to what a model is attached for lazy loading purposes. */ var ModelAttachment; (function (ModelAttachment) { /** * Model doesn't lazy load from any data source. * * Related entity properties provided at instantiation are returned * via the respective lazy interfaces when their properties are invoked. */ ModelAttachment["Detached"] = "Detached"; /** * Model lazy loads from the global DataStore. */ ModelAttachment["DataStore"] = "DataStore"; /** * Demonstrative. Not yet implemented. */ ModelAttachment["API"] = "API"; })(ModelAttachment || (ModelAttachment = {})); /** * Tells us which data source a model is attached to (lazy loads from). * * If `Deatched`, the model's lazy properties will only ever return properties * from memory provided at construction time. */ var attachedModelInstances = new WeakMap(); /** * Registers a model instance against a data source (DataStore, API, or * Detached/None). * * The API option is demonstrative. Lazy loading against API is not yet * implemented. * * @param result A model instance or array of instances * @param attachment A ModelAttachment data source * @returns passes the `result` back through after attachment */ function attached(result, attachment) { if (Array.isArray(result)) { result.map(function (record) { return attached(record, attachment); }); } else { result && attachedModelInstances.set(result, attachment); } return result; } exports.attached = attached; /** * Determines what source a model instance should lazy load from. * * If the instace was never explicitly registered, it is detached by default. * * @param instance A model instance */ exports.getAttachment = function (instance) { return attachedModelInstances.has(instance) ? attachedModelInstances.get(instance) : ModelAttachment.Detached; }; 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 }); checkSchemaCodegenVersion(userSchema.codegenVersion); var internalUserNamespace = tslib_1.__assign({ name: util_1.USER }, userSchema); logger.log('DataStore', 'Init models'); userClasses = createTypeClasses(internalUserNamespace); logger.log('DataStore', 'Models initialized'); var dataStoreNamespace = getNamespace(); var storageNamespace = storage_1.ExclusiveStorage.getNamespace(); var syncNamespace = sync_1.SyncEngine.getNamespace(); dataStoreClasses = createTypeClasses(dataStoreNamespace); storageClasses = createTypeClasses(storageNamespace); exports.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, codegenVersion: userSchema.codegenVersion, }; Object.keys(schema.namespaces).forEach(function (namespace) { var e_1, _a; var _b = tslib_1.__read(util_1.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 e_2, _a, e_3, _b; 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); // Precompute model info (such as pk fields) so that downstream schema consumers // (such as predicate builders) don't have to reach back into "DataStore" space // to go looking for it. Object.values(model.fields).forEach(function (field) { var relatedModel = userClasses[field.type.model]; if (util_1.isModelConstructor(relatedModel)) { Object.defineProperty(field.type, 'modelConstructor', { get: function () { var relatedModelDefinition = getModelDefinition(relatedModel); if (!relatedModelDefinition) throw new Error("Could not find model definition for " + relatedModel.name); return { builder: relatedModel, schema: relatedModelDefinition, pkField: util_1.extractPrimaryKeyFieldNames(relatedModelDefinition), }; }, }); } }); // compatibility with legacy/pre-PK codegen for lazy loading to inject // index fields into the model definition. // definition.cloudFields = { ...definition.fields }; var indexes = schema.namespaces[namespace].relationships[model.name].indexes; var indexFields = new Set(); try { for (var indexes_1 = tslib_1.__values(indexes), indexes_1_1 = indexes_1.next(); !indexes_1_1.done; indexes_1_1 = indexes_1.next()) { var index = indexes_1_1.value; try { for (var _c = (e_3 = void 0, tslib_1.__values(index[1])), _d = _c.next(); !_d.done; _d = _c.next()) { var indexField = _d.value; indexFields.add(indexField); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_d && !_d.done && (_b = _c.return)) _b.call(_c); } finally { if (e_3) throw e_3.error; } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (indexes_1_1 && !indexes_1_1.done && (_a = indexes_1.return)) _a.call(indexes_1); } finally { if (e_2) throw e_2.error; } } model.allFields = tslib_1.__assign(tslib_1.__assign({}, Object.fromEntries(tslib_1.__spread(indexFields.values()).map(function (name) { return [ name, { name: name, type: 'ID', isArray: false, }, ]; }))), model.fields); }); 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, tslib_1.__values(Array.from(modelAssociations.keys()))), _d = _c.next(); !_d.done; _d = _c.next()) { var modelName = _d.value; var parents = modelAssociations.get(modelName); if (parents === null || parents === void 0 ? void 0 : 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; }; exports.initSchema = initSchema; /** * Throws an exception if the schema has *not* been initialized * by `initSchema()`. * * **To be called before trying to access schema.** * * Currently this only needs to be called in `start()` and `clear()` because * all other functions will call start first. */ var checkSchemaInitialized = function () { if (schema === undefined) { var message = 'Schema is not initialized. DataStore will not function as expected. This could happen if you have multiple versions of DataStore installed. Please see https://docs.amplify.aws/lib/troubleshooting/upgrading/q/platform/js/#check-for-duplicate-versions'; logger.error(message); throw new Error(message); } }; /** * Throws an exception if the schema is using a codegen version that is not supported. * * Set the supported version by setting majorVersion and minorVersion * This functions similar to ^ version range. * The tested codegenVersion major version must exactly match the set majorVersion * The tested codegenVersion minor version must be gt or equal to the set minorVersion * Example: For a min supported version of 5.4.0 set majorVersion = 5 and minorVersion = 4 * * This regex will not work when setting a supported range with minor version * of 2 or more digits. * i.e. minorVersion = 10 will not work * The regex will work for testing a codegenVersion with multi digit minor * versions as long as the minimum minorVersion is single digit. * i.e. codegenVersion = 5.30.1, majorVersion = 5, minorVersion = 4 PASSES * * @param codegenVersion schema codegenVersion */ var checkSchemaCodegenVersion = function (codegenVersion) { var majorVersion = 3; var minorVersion = 2; var isValid = false; try { var versionParts = codegenVersion.split('.'); var _a = tslib_1.__read(versionParts, 4), major = _a[0], minor = _a[1], patch = _a[2], patchrevision = _a[3]; isValid = Number(major) === majorVersion && Number(minor) >= minorVersion; } catch (err) { console.log("Error parsing codegen version: " + codegenVersion + "\n" + err); } if (!isValid) { var message = "Models were generated with an unsupported version of codegen. Codegen artifacts are from " + (codegenVersion || 'an unknown version') + ", whereas ^" + majorVersion + "." + minorVersion + ".0 is required. " + "Update to the latest CLI and run 'amplify codegen models'."; logger.error(message); throw new Error(message); } }; var createTypeClasses = function (namespace) { var classes = {}; Object.entries(namespace.models).forEach(function (_a) { var _b = tslib_1.__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 = tslib_1.__read(_a, 2), typeName = _b[0], typeDefinition = _b[1]; var clazz = createNonModelClass(typeDefinition); classes[typeName] = clazz; }); return classes; }; /** * Collection of instantiated models to allow storage of metadata apart from * the model visible to the consuming app -- in case the app doesn't have * metadata fields (_version, _deleted, etc.) exposed on the model itself. */ 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_1 = fieldDefinition.type, isRequired_1 = fieldDefinition.isRequired, isArrayNullable = fieldDefinition.isArrayNullable, name_1 = fieldDefinition.name, isArray = fieldDefinition.isArray; var timestamps = types_1.isSchemaModelWithAttributes(modelDefinition) ? util_1.getTimestampFields(modelDefinition) : {}; var isTimestampField = !!timestamps[name_1]; if (((!isArray && isRequired_1) || (isArray && !isArrayNullable)) && !isTimestampField && (v === null || v === undefined)) { throw new Error("Field " + name_1 + " is required"); } if (types_1.isSchemaModelWithAttributes(modelDefinition) && !util_1.isIdManaged(modelDefinition)) { var keys = util_1.extractPrimaryKeyFieldNames(modelDefinition); if (keys.includes(k) && v === '') { logger.error(util_1.errorMessages.idEmptyString, { k: k, value: v }); throw new Error(util_1.errorMessages.idEmptyString); } } if (types_1.isGraphQLScalarType(type_1)) { var jsType_1 = types_1.GraphQLScalarType.getJSType(type_1); var validateScalar_1 = types_1.GraphQLScalarType.getValidationFunction(type_1); if (type_1 === '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 (!util_1.isNullOrUndefined(v) && v.some(function (e) { return util_1.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 && !util_1.isNullOrUndefined(v)) { var validationStatus = v.map(function (e) { if (!util_1.isNullOrUndefined(e)) { return validateScalar_1(e); } else if (util_1.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_1 + ", 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 (!util_1.isNullOrUndefined(v) && validateScalar_1 && !validateScalar_1(v) // TODO: why never, TS ... why ... ) { throw new Error("Field " + name_1 + " should be of type " + type_1 + ", validation failed. " + v); } } else if (types_1.isNonModelFieldType(type_1)) { // do not check non model fields if undefined or null if (!util_1.isNullOrUndefined(v)) { var subNonModelDefinition_1 = schema.namespaces.user.nonModels[type_1.nonModel]; var modelValidator_1 = validateModelFields(subNonModelDefinition_1); if (isArray) { var errorTypeText = type_1.nonModel; if (!isRequired_1) { errorTypeText = type_1.nonModel + " | null | undefined"; } if (!Array.isArray(v)) { throw new Error("Field " + name_1 + " should be of type [" + errorTypeText + "], " + typeof v + " received. " + v); } v.forEach(function (item) { if ((util_1.isNullOrUndefined(item) && isRequired_1) || (typeof item !== 'object' && typeof item !== 'undefined')) { throw new Error("All elements in the " + name_1 + " array should be of type " + type_1.nonModel + ", [" + typeof item + "] received. " + item); } if (!util_1.isNullOrUndefined(item)) { Object.keys(subNonModelDefinition_1.fields).forEach(function (subKey) { modelValidator_1(subKey, item[subKey]); }); } }); } else { if (typeof v !== 'object') { throw new Error("Field " + name_1 + " should be of type " + type_1.nonModel + ", " + typeof v + " recieved. " + v); } Object.keys(subNonModelDefinition_1.fields).forEach(function (subKey) { modelValidator_1(subKey, v[subKey]); }); } } } } }; }; 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' || types_1.isNonModelFieldType(type) || types_1.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; }; /** * Records the patches (as if against an empty object) used to initialize * an instance of a Model. This can be used for determining which fields to * send to the cloud durnig a CREATE mutation. */ var initPatches = new WeakMap(); /** * Attempts to apply type-aware, casted field values from a given `init` * object to the given `draft`. * * @param init The initialization object to extract field values from. * @param modelDefinition The definition describing the target object shape. * @param draft The draft to apply field values to. */ var initializeInstance = function (init, modelDefinition, draft) { var modelValidator = validateModelFields(modelDefinition); Object.entries(init).forEach(function (_a) { var _b = tslib_1.__read(_a, 2), k = _b[0], v = _b[1]; var parsedValue = castInstanceType(modelDefinition, k, v); modelValidator(k, parsedValue); draft[k] = parsedValue; }); }; /** * Updates a draft to standardize its customer-defined fields so that they are * consistent with the data as it would look after having been synchronized from * Cloud storage. * * The exceptions to this are: * * 1. Non-schema/Internal [sync] metadata fields. * 2. Cloud-managed fields, which are `null` until set by cloud storage. * * This function should be expanded if/when deviations between canonical Cloud * storage data and locally managed data are found. For now, the known areas * that require normalization are: * * 1. Ensuring all non-metadata fields are *defined*. (I.e., turn `undefined` -> `null`.) * * @param modelDefinition Definition for the draft. Used to discover all fields. * @param draft The instance draft to apply normalizations to. */ var normalize = function (modelDefinition, draft) { var e_4, _a; try { for (var _b = tslib_1.__values(Object.keys(modelDefinition.fields)), _c = _b.next(); !_c.done; _c = _b.next()) { var k = _c.value; if (draft[k] === undefined) draft[k] = null; } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_4) throw e_4.error; } } }; var createModelClass = function (modelDefinition) { var e_5, _a; var clazz = /** @class */ (function () { function Model(init) { // we create a base instance first so we can distinguish which fields were explicitly // set by customer code versus those set by normalization. only those fields // which are explicitly set by customers should be part of create mutations. var patches = []; var baseInstance = immer_1.produce(this, function (draft) { initializeInstance(init, modelDefinition, draft); // model is initialized inside a DataStore component (e.g. by Sync Engine, Storage Engine, etc.) var isInternallyInitialized = instancesMetadata.has(init); var modelInstanceMetadata = isInternallyInitialized ? init : {}; var _id = modelInstanceMetadata.id; if (util_1.isIdManaged(modelDefinition)) { var isInternalModel = _id !== null && _id !== undefined; var id = isInternalModel ? _id : modelDefinition.syncable ? uuid_1.v4() : ulid(); draft.id = id; } else if (util_1.isIdOptionallyManaged(modelDefinition)) { // only auto-populate if the id was not provided draft.id = draft.id || uuid_1.v4(); } if (!isInternallyInitialized) { checkReadOnlyPropertyOnCreate(draft, modelDefinition); } var _version = modelInstanceMetadata._version, _lastChangedAt = modelInstanceMetadata._lastChangedAt, _deleted = modelInstanceMetadata._deleted; if (modelDefinition.syncable) { draft._version = _version; draft._lastChangedAt = _lastChangedAt; draft._deleted = _deleted; } }, function (p) { return (patches = p); }); // now that we have a list of patches that encapsulate the explicit, customer-provided // fields, we can normalize. patches from normalization are ignored, because the changes // are only create to provide a consistent view of the data for fields pre/post sync // where possible. (not all fields can be normalized pre-sync, because they're generally // "cloud managed" fields, like createdAt and updatedAt.) var normalized = immer_1.produce(baseInstance, function (draft) { return normalize(modelDefinition, draft); }); initPatches.set(normalized, patches); return normalized; } 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 = immer_1.produce(source, function (draft) { fn(draft); var keyNames = util_1.extractPrimaryKeyFieldNames(modelDefinition); // Keys are immutable keyNames.forEach(function (key) { if (draft[key] !== source[key]) { logger.warn("copyOf() does not update PK fields. The '" + key + "' update is being ignored.", { source: source }); } draft[key] = source[key]; }); var modelValidator = validateModelFields(modelDefinition); Object.entries(draft).forEach(function (_a) { var _b = tslib_1.__read(_a, 2), k = _b[0], v = _b[1]; var parsedValue = castInstanceType(modelDefinition, k, v); modelValidator(k, parsedValue); }); normalize(modelDefinition, draft); }, function (p) { return (patches = p); }); var hasExistingPatches = modelPatchesMap.has(source); if (patches.length || hasExistingPatches) { if (hasExistingPatches) { var _a = tslib_1.__read(modelPatchesMap.get(source), 2), existingPatches = _a[0], existingSource = _a[1]; var mergedPatches = util_1.mergePatches(existingSource, existingPatches, patches); modelPatchesMap.set(model, [mergedPatches, existingSource]); checkReadOnlyPropertyOnUpdate(mergedPatches, modelDefinition); } else { modelPatchesMap.set(model, [patches, source]); checkReadOnlyPropertyOnUpdate(patches, modelDefinition); } } else { // always register patches when performing a copyOf, even if the // patches list is empty. this allows `save()` to recognize when an // instance is the result of a `copyOf()`. without more significant // refactoring, this is the only way for `save()` to know which // diffs (patches) are relevant for `storage` to use in building // the list of "changed" fields for mutations. modelPatchesMap.set(model, [[], source]); } return attached(model, ModelAttachment.DataStore); }; // "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 = tslib_1.__read(_a, 2), k = _b[0], v = _b[1]; modelValidator(k, v); }); return attached(instance, ModelAttachment.DataStore); }; return Model; }()); clazz[immer_1.immerable] = true; Object.defineProperty(clazz, 'name', { value: modelDefinition.name }); // Add getters/setters for relationship fields. // getter - for lazy loading // setter - for FK management var allModelRelationships = relationship_1.ModelRelationship.allFrom({ builder: clazz, schema: modelDefinition, pkField: util_1.extractPrimaryKeyFieldNames(modelDefinition), }); var _loop_1 = function (relationship) { var field = relationship.field; Object.defineProperty(clazz.prototype, modelDefinition.fields[field].name, { set: function (model) { if (!(typeof model === 'object' || typeof model === 'undefined')) return; // if model is undefined or null, the connection should be removed if (model) { // Avoid validation error when processing AppSync response with nested // selection set. Nested entitites lack version field and can not be validated // TODO: explore a more reliable method to solve this if (model.hasOwnProperty('_version')) { var modelConstructor = Object.getPrototypeOf(model || {}) .constructor; if (!isValidModelConstructor(modelConstructor)) { var msg = "Value passed to " + modelDefinition.name + "." + field + " is not a valid instance of a model"; logger.error(msg, { model: model }); throw new Error(msg); } if (modelConstructor.name.toLowerCase() !== relationship.remoteModelConstructor.name.toLowerCase()) { var msg = "Value passed to " + modelDefinition.name + "." + field + " is not an instance of " + relationship.remoteModelConstructor.name; logger.error(msg, { model: model }); throw new Error(msg); } } } // if the relationship can be managed automagically, set the FK's if (relationship.isComplete) { for (var i = 0; i < relationship.localJoinFields.length; i++) { this[relationship.localJoinFields[i]] = model === null || model === void 0 ? void 0 : model[relationship.remoteJoinFields[i]]; } var instanceMemos = modelInstanceAssociationsMap.has(this) ? modelInstanceAssociationsMap.get(this) : modelInstanceAssociationsMap.set(this, {}).get(this); instanceMemos[field] = model || undefined; } }, get: function () { var _this = this; /** * Bucket for holding related models instances specific to `this` instance. */ var instanceMemos = modelInstanceAssociationsMap.has(this) ? modelInstanceAssociationsMap.get(this) : modelInstanceAssociationsMap.set(this, {}).get(this); // if the memos already has a result for this field, we'll use it. // there is no "cache" invalidation of any kind; memos are permanent to // keep an immutable perception of the instance. if (!instanceMemos.hasOwnProperty(field)) { // before we populate the memo, we need to know where to look for relatives. // today, this only supports DataStore. Models aren't managed elsewhere in Amplify. if (exports.getAttachment(this) === ModelAttachment.DataStore) { // when we fetch the results using a query constructed under the guidance // of the relationship metadata, we DO NOT AWAIT resolution. we want to // drop the promise into the memo's synchronously, eliminating the chance // for a race. var resultPromise = instance.query(relationship.remoteModelConstructor, function (base) { return base.and(function (q) { return relationship.remoteJoinFields.map(function (field, index) { // TODO: anything we can use instead of `any` here? return q[field].eq(_this[relationship.localJoinFields[index]]); }); }); }); // results in hand, how we return them to the caller depends on the relationship type. if (relationship.type === 'HAS_MANY') { // collections should support async iteration, even though we don't // leverage it fully [yet]. instanceMemos[field] = new AsyncCollection(resultPromise); } else { // non-collections should only ever return 1 value *or nothing*. // if we have more than 1 record, something's amiss. it's not our job // pick a result for the customer. it's our job to say "something's wrong." instanceMemos[field] = resultPromise.then(function (rows) { if (rows.length > 1) { // should never happen for a HAS_ONE or BELONGS_TO. var err = new Error("\n\t\t\t\t\t\t\t\t\tData integrity error.\n\t\t\t\t\t\t\t\t\tToo many records found for a HAS_ONE/BELONGS_TO field '" + modelDefinition.name + "." + field + "'\n\t\t\t\t\t\t\t\t"); console.error(err); throw err; } else { return rows[0]; } }); } } else if (exports.getAttachment(this) === ModelAttachment.API) { throw new Error('Lazy loading from API is not yet supported!'); } else { if (relationship.type === 'HAS_MANY') { return new AsyncCollection([]); } else { return Promise.resolve(undefined); } } } return instanceMemos[field]; }, }); }; try { for (var allModelRelationships_1 = tslib_1.__values(allModelRelationships), allModelRelationships_1_1 = allModelRelationships_1.next(); !allModelRelationships_1_1.done; allModelRelationships_1_1 = allModelRelationships_1.next()) { var relationship = allModelRelationships_1_1.value; _loop_1(relationship); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (allModelRelationships_1_1 && !allModelRelationships_1_1.done && (_a = allModelRelationships_1.return)) _a.call(allModelRelationships_1); } finally { if (e_5) throw e_5.error; } } return clazz; }; /** * An eventually loaded related model instance. */ var AsyncItem = /** @class */ (function (_super) { tslib_1.__extends(AsyncItem, _super); function AsyncItem() { return _super !== null && _super.apply(this, arguments) || this; } return AsyncItem; }(Promise)); exports.AsyncItem = AsyncItem; /** * A collection of related model instances. * * This collection can be async-iterated or turned directly into an array using `toArray()`. */ var AsyncCollection = /** @class */ (function () { function AsyncCollection(values) { this.values = values; } /** * Facilitates async iteration. * * ```ts * for await (const item of collection) { * handle(item) * } * ``` * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of */ AsyncCollection.prototype[Symbol.asyncIterator] = function () { var _this = this; var values; var index = 0; return { next: function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var result; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (!!values) return [3 /*break*/, 2]; return [4 /*yield*/, this.values]; case 1: values = _a.sent(); _a.label = 2; case 2: if (index < values.length) { result = { value: values[index], done: false, }; index++; return [2 /*return*/, result]; } return [2 /*return*/, { value: null, done: true, }]; } }); }); }, }; }; /** * Turns the collection into an array, up to the amount specified in `max` param. * * ```ts * const all = await collection.toArray(); * const first100 = await collection.toArray({max: 100}); * ``` */ AsyncCollection.prototype.toArray = function (_a) { var _b = (_a === void 0 ? {} : _a).max, max = _b === void 0 ? Number.MAX_SAFE_INTEGER : _b; var e_6, _c; return tslib_1.__awaiter(this, void 0, void 0, function () { var output, i, _d, _e, element, e_6_1; return tslib_1.__generator(this, function (_f) { switch (_f.label) { case 0: output = []; i = 0; _f.label = 1; case 1: _f.trys.push([1, 6, 7, 12]); _d = tslib_1.__asyncValues(this); _f.label = 2; case 2: return [4 /*yield*/, _d.next()]; case 3: if (!(_e = _f.sent(), !_e.done)) return [3 /*break*/, 5]; element = _e.value; if (i < max) { output.push(element); i++; } else { return [3 /*break*/, 5]; } _f.label = 4; case 4: return [3 /*break*/, 2]; case 5: return [3 /*break*/, 12]; case 6: e_6_1 = _f.sent(); e_6 = { error: e_6_1 }; return [3 /*break*/, 12]; case 7: _f.trys.push([7, , 10, 11]); if (!(_e && !_e.done && (_c = _d.return))) return [3 /*break*/, 9]; return [4 /*yield*/, _c.call(_d)]; case 8: _f.sent(); _f.label = 9; case 9: return [3 /*break*/, 11]; case 10: if (e_6) throw e_6.error; return [7 /*endfinally*/]; case 11: return [7 /*endfinally*/]; case 12: return [2 /*return*/, output]; } }); }); }; return AsyncCollection; }()); exports.AsyncCollection = AsyncCollection; 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 = tslib_1.__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 = immer_1.produce(this, function (draft) { initializeInstance(init, typeDefinition, draft); }); return instance; } return Model; }()); clazz[immer_1.immerable] = true; Object.defineProperty(clazz, 'name', { value: typeDefinition.name }); util_1.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, tslib_1.__assign(tslib_1.__assign({}, localModel), { _version: _version })); } function defaultErrorHandler(error) { logger.warn(error); } function getModelConstructorByModelName(namespaceName, modelName) { var result; switch (namespaceName) { case util_1.DATASTORE: result = dataStoreClasses[modelName]; break; case util_1.USER: result = userClasses[modelName]; break; case util_1.SYNC: result = exports.syncClasses[modelName]; break; case util_1.STORAGE: result = storageClasses[modelName]; break; default: throw new Error("Invalid namespace: " + namespaceName); } 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); } } /** * Queries the DataStore metadata tables to see if they are the expected * version. If not, clobbers the whole DB. If so, leaves them alone. * Otherwise, simply writes the schema version. * * SIDE EFFECT: * 1. Creates a transaction * 1. Updates data. * * @param storage Storage adapter containing the metadata. * @param version The expected schema version. */ function checkSchemaVersion(storage, version) { return tslib_1.__awaiter(this, void 0, void 0, function () { var Setting, modelDefinition; var _this = this; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: Setting = dataStoreClasses.Setting; modelDefinition = schema.namespaces[util_1.DATASTORE].models.Setting; return [4 /*yield*/, storage.runExclusive(function (s) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var _a, schemaVersionSetting, storedValue; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, s.query(Setting, predicates_1.ModelPredicateCreator.createFromAST(modelDefinition, { and: { key: { eq: SETTING_SCHEMA_VERSION } }, }), { page: 0, limit: 1 })]; case 1: _a = tslib_1.__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: util_1.DATASTORE, relationships: {}, enums: {}, nonModels: {}, models: { Setting: { name: 'Setting', pluralName: 'Settings', syncable: false, fields: { id: { name: 'id', type: 'ID', isRequired: true,