From 4f6c8de601c23d8f63728cba31d3e4705d0740f2 Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:02:46 -0500 Subject: [PATCH] fix(@aws-amplify/datastore): consec updates with timestamps --- packages/datastore/__tests__/outbox.test.ts | 9 ++- packages/datastore/src/sync/outbox.ts | 75 ++++++++++++++++----- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/packages/datastore/__tests__/outbox.test.ts b/packages/datastore/__tests__/outbox.test.ts index 9636553ddf5..d51363bb1e7 100644 --- a/packages/datastore/__tests__/outbox.test.ts +++ b/packages/datastore/__tests__/outbox.test.ts @@ -142,6 +142,8 @@ describe('Outbox tests', () => { _version: (updatedModel1 as any)._version + 1, // increment version like we would expect coming back from AppSync _lastChangedAt: Date.now(), _deleted: false, + createdAt: '2021-11-30T20:51:00.250Z', + updatedAt: '2021-11-30T20:52:00.250Z', }; await Storage.runExclusive(async s => { @@ -166,6 +168,8 @@ describe('Outbox tests', () => { _version: inProgressData._version + 1, // increment version like we would expect coming back from AppSync _lastChangedAt: Date.now(), _deleted: false, + createdAt: '2021-11-30T20:51:00.250Z', + updatedAt: '2021-11-30T20:52:00.250Z', }; await processMutationResponse( @@ -320,9 +324,8 @@ async function instantiateOutbox(): Promise { Storage = DataStore.storage; anyStorage = Storage; - const namespaceResolver = anyStorage.storage.namespaceResolver.bind( - anyStorage - ); + const namespaceResolver = + anyStorage.storage.namespaceResolver.bind(anyStorage); ({ modelInstanceCreator } = anyStorage.storage); diff --git a/packages/datastore/src/sync/outbox.ts b/packages/datastore/src/sync/outbox.ts index 52205ea58df..f5167595591 100644 --- a/packages/datastore/src/sync/outbox.ts +++ b/packages/datastore/src/sync/outbox.ts @@ -12,7 +12,7 @@ import { PersistentModelConstructor, QueryOne, } from '../types'; -import { SYNC, valuesEqual } from '../util'; +import { USER, SYNC, valuesEqual } from '../util'; import { TransformerMutationType } from './utils'; // TODO: Persist deleted ids @@ -31,9 +31,8 @@ class MutationEventOutbox { mutationEvent: MutationEvent ): Promise { storage.runExclusive(async s => { - const mutationEventModelDefinition = this.schema.namespaces[SYNC].models[ - 'MutationEvent' - ]; + const mutationEventModelDefinition = + this.schema.namespaces[SYNC].models['MutationEvent']; const predicate = ModelPredicateCreator.createFromExisting( mutationEventModelDefinition, @@ -124,8 +123,8 @@ class MutationEventOutbox { storage: StorageFacade, model: T ): Promise { - const mutationEventModelDefinition = this.schema.namespaces[SYNC].models - .MutationEvent; + const mutationEventModelDefinition = + this.schema.namespaces[SYNC].models.MutationEvent; const mutationEvents = await storage.query( this.MutationEvent, @@ -161,7 +160,9 @@ class MutationEventOutbox { return; } - const { _version, _lastChangedAt, _deleted, ...incomingData } = record; + const { _version, _lastChangedAt, _deleted, ..._incomingData } = record; + const incomingData = this.removeTimestampFields(head.model, _incomingData); + const data = JSON.parse(head.data); if (!data) { @@ -172,8 +173,9 @@ class MutationEventOutbox { _version: __version, _lastChangedAt: __lastChangedAt, _deleted: __deleted, - ...outgoingData + ..._outgoingData } = data; + const outgoingData = this.removeTimestampFields(head.model, _outgoingData); // Don't sync the version when the data in the response does not match the data // in the request, i.e., when there's a handled conflict @@ -181,9 +183,8 @@ class MutationEventOutbox { return; } - const mutationEventModelDefinition = this.schema.namespaces[SYNC].models[ - 'MutationEvent' - ]; + const mutationEventModelDefinition = + this.schema.namespaces[SYNC].models['MutationEvent']; const predicate = ModelPredicateCreator.createFromExisting( mutationEventModelDefinition, @@ -222,13 +223,8 @@ class MutationEventOutbox { previous: MutationEvent, current: MutationEvent ): MutationEvent { - const { - _version, - id, - _lastChangedAt, - _deleted, - ...previousData - } = JSON.parse(previous.data); + const { _version, id, _lastChangedAt, _deleted, ...previousData } = + JSON.parse(previous.data); const { id: __id, @@ -252,6 +248,49 @@ class MutationEventOutbox { data, }); } + + /* + if a model is using custom timestamp fields + the custom field names will be stored in the model attributes + + e.g. + "attributes": [ + { + "type": "model", + "properties": { + "timestamps": { + "createdAt": "createdOn", + "updatedAt": "updatedOn" + } + } + } + ] + */ + private removeTimestampFields( + model: string, + record: PersistentModel + ): PersistentModel { + const CREATED_AT_DEFAULT_KEY = 'createdAt'; + const UPDATED_AT_DEFAULT_KEY = 'updatedAt'; + + let createdTimestampKey = CREATED_AT_DEFAULT_KEY; + let updatedTimestampKey = UPDATED_AT_DEFAULT_KEY; + + const modelAttributes = this.schema.namespaces[USER].models[ + model + ].attributes?.find(attr => attr.type === 'model'); + const timestampFieldsMap = modelAttributes?.properties?.timestamps; + + if (timestampFieldsMap) { + createdTimestampKey = timestampFieldsMap[CREATED_AT_DEFAULT_KEY]; + updatedTimestampKey = timestampFieldsMap[UPDATED_AT_DEFAULT_KEY]; + } + + delete (record as Record)[createdTimestampKey]; + delete (record as Record)[updatedTimestampKey]; + + return record; + } } export { MutationEventOutbox };