Skip to content

Commit

Permalink
fix(@aws-amplify/datastore): check read-only at instance level (aws-a…
Browse files Browse the repository at this point in the history
  • Loading branch information
iartemiev authored and Marius committed Sep 21, 2021
1 parent c3f4f0e commit 1939829
Showing 1 changed file with 43 additions and 51 deletions.
94 changes: 43 additions & 51 deletions packages/datastore/src/datastore/datastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import {
ErrorHandler,
SyncExpression,
AuthModeStrategyType,
ModelFields,
} from '../types';
import {
DATASTORE,
Expand Down Expand Up @@ -374,13 +373,18 @@ const createModelClass = <T extends PersistentModel>(
_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;

Expand Down Expand Up @@ -419,6 +423,7 @@ const createModelClass = <T extends PersistentModel>(

if (patches.length) {
modelPatchesMap.set(model, [patches, source]);
checkReadOnlyPropertyOnUpdate(patches, modelDefinition);
}

return model;
Expand Down Expand Up @@ -449,6 +454,36 @@ const createModelClass = <T extends PersistentModel>(
return clazz;
};

const checkReadOnlyPropertyOnCreate = <T extends PersistentModel>(
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 = <T>(typeDefinition: SchemaNonModel) => {
const clazz = <NonModelTypeConstructor<T>>(<unknown>class Model {
constructor(init: ModelInit<T>) {
Expand Down Expand Up @@ -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
Expand All @@ -835,46 +867,6 @@ class DataStore {
return savedModel;
};

private checkReadOnlyProperty(
fields: ModelFields,
model: Record<string, any>,
patchesTuple: [
Patch[],
Readonly<
{
id: string;
} & Record<string, any>
>
]
) {
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;

Expand Down

0 comments on commit 1939829

Please sign in to comment.