From 19398292130ed04f5779e8354fef07fa62c8e410 Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Tue, 24 Aug 2021 12:19:05 -0400 Subject: [PATCH] fix(@aws-amplify/datastore): check read-only at instance level (#8794) --- packages/datastore/src/datastore/datastore.ts | 94 +++++++++---------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/packages/datastore/src/datastore/datastore.ts b/packages/datastore/src/datastore/datastore.ts index 94e21b58205..989433c332f 100644 --- a/packages/datastore/src/datastore/datastore.ts +++ b/packages/datastore/src/datastore/datastore.ts @@ -49,7 +49,6 @@ import { ErrorHandler, SyncExpression, AuthModeStrategyType, - ModelFields, } from '../types'; import { DATASTORE, @@ -374,13 +373,18 @@ const createModelClass = ( _deleted, } = modelInstanceMetadata; - const id = - // instancesIds is set by modelInstanceCreator, it is accessible only internally - _id !== null && _id !== undefined - ? _id - : modelDefinition.syncable - ? uuid4() - : ulid(); + // instancesIds are set by modelInstanceCreator, it is accessible only internally + const isInternal = _id !== null && _id !== undefined; + + const id = isInternal + ? _id + : modelDefinition.syncable + ? uuid4() + : ulid(); + + if (!isInternal) { + checkReadOnlyPropertyOnCreate(draft, modelDefinition); + } draft.id = id; @@ -419,6 +423,7 @@ const createModelClass = ( if (patches.length) { modelPatchesMap.set(model, [patches, source]); + checkReadOnlyPropertyOnUpdate(patches, modelDefinition); } return model; @@ -449,6 +454,36 @@ const createModelClass = ( return clazz; }; +const checkReadOnlyPropertyOnCreate = ( + draft: T, + modelDefinition: SchemaModel +) => { + const modelKeys = Object.keys(draft); + const { fields } = modelDefinition; + + modelKeys.forEach(key => { + if (fields[key] && fields[key].isReadOnly) { + throw new Error(`${key} is read-only.`); + } + }); +}; + +const checkReadOnlyPropertyOnUpdate = ( + patches: Patch[], + modelDefinition: SchemaModel +) => { + const patchArray = patches.map(p => [p.path[0], p.value]); + const { fields } = modelDefinition; + + patchArray.forEach(([key, val]) => { + if (!val || !fields[key]) return; + + if (fields[key].isReadOnly) { + throw new Error(`${key} is read-only.`); + } + }); +}; + const createNonModelClass = (typeDefinition: SchemaNonModel) => { const clazz = >(class Model { constructor(init: ModelInit) { @@ -815,9 +850,6 @@ class DataStore { const modelDefinition = getModelDefinition(modelConstructor); - // ensuring "read-only" data isn't being overwritten - this.checkReadOnlyProperty(modelDefinition.fields, model, patchesTuple); - const producedCondition = ModelPredicateCreator.createFromExisting( modelDefinition, condition @@ -835,46 +867,6 @@ class DataStore { return savedModel; }; - private checkReadOnlyProperty( - fields: ModelFields, - model: Record, - patchesTuple: [ - Patch[], - Readonly< - { - id: string; - } & Record - > - ] - ) { - if (!patchesTuple) { - // saving a new model instance - const modelKeys = Object.keys(model); - modelKeys.forEach(key => { - if (fields[key] && fields[key].isReadOnly) { - throw new Error(`${key} is read-only.`); - } - }); - } else { - // * Updating an existing instance via 'patchesTuple' - // patchesTuple[0] is an object that contains the info we need - // like the 'path' (mapped to the model's key) and the 'value' of the patch - const patchArray = patchesTuple[0].map(p => [p.path[0], p.value]); - patchArray.forEach(patch => { - const [key, val] = [...patch]; - - // the value of a read-only field should be undefined - if so, no need to do the following check - if (!val || !fields[key]) return; - - // if the value is NOT undefined, we have to check the 'isReadOnly' property - // and throw an error to avoid persisting a mutation - if (fields[key].isReadOnly) { - throw new Error(`${key} is read-only.`); - } - }); - } - } - setConflictHandler = (config: DataStoreConfig): ConflictHandler => { const { DataStore: configDataStore } = config;