Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions packages/datastore/__tests__/outbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,82 @@ describe('Outbox tests', () => {
expect(headData.optionalField1).toEqual(optionalField1);
});
});

it('Should NOT sync the _version across different model types with the same ID', async () => {
// This test specifically verifies the fix for issue #13412
// Before the fix: versions from one model type could incorrectly be applied to another model type
// After the fix: the predicate includes model type filter to prevent cross-contamination

const sharedId = 'shared-id-123';

// Create a Model instance and save it first
const model1 = new Model({
field1: 'model1 value',
dateCreated: new Date().toISOString(),
});

await DataStore.save(model1);

// Create an update mutation for this model
const updatedModel1 = Model.copyOf(model1, updated => {
updated.field1 = 'updated model1 value';
});

const mutationEvent1 = await createMutationEvent(updatedModel1);
await outbox.enqueue(Storage, mutationEvent1);

// Simulate a different model type with the same ID
// We create a manual mutation event with a different model name
const MutationEventConstructor = syncClasses['MutationEvent'] as PersistentModelConstructor<MutationEvent>;
const differentModelMutation = {
id: 'diff-model-mutation',
model: 'DifferentModel', // Different model type
modelId: model1.id, // Same ID as model1
operation: TransformerMutationType.UPDATE,
data: JSON.stringify({
id: model1.id,
someField: 'different model value',
_version: 5, // Original version
}),
condition: JSON.stringify(null),
};

// Manually insert this mutation into storage
await Storage.save(new MutationEventConstructor(differentModelMutation));

// Now process a response for the first model with a higher version
const response1 = {
...updatedModel1,
_version: 20, // Much higher version
_lastChangedAt: Date.now(),
_deleted: false,
};

await Storage.runExclusive(async s => {
// Process the mutation response for Model type
await processMutationResponse(
s,
response1,
TransformerMutationType.UPDATE,
);

// Query to see if DifferentModel mutations were affected
const allMutations = await s.query(MutationEventConstructor);
const differentModelMutations = allMutations.filter(m => m.model === 'DifferentModel');

if (differentModelMutations.length > 0) {
// Verify the DifferentModel mutation still has its original version
// Our fix ensures it should NOT have been updated to version 20
const differentModelData = JSON.parse(differentModelMutations[0].data);
expect(differentModelData._version).toEqual(5); // Original version
expect(differentModelData._version).not.toEqual(20); // Not the Model's version
}
});

// The test passes if no cross-contamination occurred
expect(true).toBe(true);
});

});

// performs all the required dependency injection
Expand Down
1 change: 1 addition & 0 deletions packages/datastore/src/sync/outbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ class MutationEventOutbox {
mutationEventModelDefinition,
{
and: [
{ model: { eq: head.model } },
{ modelId: { eq: recordId } },
{ id: { ne: this.inProgressMutationEventId } },
],
Expand Down