Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 311a5f7e18 SSE optimistic update crashes on custom fields not yet in stale metadata cache
https://sonarly.com/issue/14352?type=bug

When a user creates custom fields on an object and then views that object's records, incoming SSE database events include the new fields, but the frontend metadata cache hasn't refreshed yet, causing a crash in the optimistic record computation.

Fix: **What changed:** In `computeOptimisticRecordFromInput.ts`, replaced the `throw new Error(...)` with `console.warn(...)` when unknown fields are detected in the record input (line 73-77).

**Why:** The function validates that all fields in `recordInput` exist in `objectMetadataItem.fields`. This validation was originally written for mutation-triggered optimistic updates where the frontend controls the input. When SSE real-time events were added (commit `d51c988a9e`, Jan 2026), this function started receiving server-controlled input that may include fields not yet known to the frontend's metadata cache — specifically when a user creates custom fields and immediately views records before the metadata refreshes.

The function's main for-loop (line 80) iterates only over `objectMetadataItem.fields`, so unknown fields in `recordInput` are already naturally skipped. The throw was a defensive assertion that became incorrect when the caller contract changed. Converting it to a warning preserves observability while allowing graceful degradation.

**Test update:** Updated the existing test from asserting a throw to asserting a `console.warn` call and verifying that known fields (like `city: 'Paris'`) are still correctly processed while unknown fields are skipped.
2026-03-13 10:59:58 +00:00
2 changed files with 27 additions and 19 deletions
@@ -252,26 +252,34 @@ describe('computeOptimisticRecordFromInput', () => {
});
});
it('should throw an error if recordInput contains fields unrelated to the current objectMetadata', () => {
it('should warn and skip unknown fields if recordInput contains fields unrelated to the current objectMetadata', () => {
const cache = new InMemoryCache();
const personObjectMetadataItem = getMockObjectMetadataItemOrThrow('person');
const consoleWarnSpy = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});
expect(() =>
computeOptimisticRecordFromInput({
currentWorkspaceMember,
objectMetadataItems: generatedMockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
recordInput: {
unknwon: 'unknown',
foo: 'foo',
bar: 'bar',
city: 'Paris',
},
cache,
objectPermissionsByObjectMetadataId: {},
}),
).toThrowErrorMatchingInlineSnapshot(
`"Should never occur, encountered unknown fields unknwon, foo, bar in objectMetadataItem person"`,
const result = computeOptimisticRecordFromInput({
currentWorkspaceMember,
objectMetadataItems: generatedMockObjectMetadataItems,
objectMetadataItem: personObjectMetadataItem,
recordInput: {
unknwon: 'unknown',
foo: 'foo',
bar: 'bar',
city: 'Paris',
},
cache,
objectPermissionsByObjectMetadataId: {},
});
expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('encountered unknown fields'),
);
expect(result).toEqual({
city: 'Paris',
});
consoleWarnSpy.mockRestore();
});
});
@@ -71,8 +71,8 @@ export const computeOptimisticRecordFromInput = ({
},
);
if (unknownRecordInputFields.length > 0) {
throw new Error(
`Should never occur, encountered unknown fields ${unknownRecordInputFields.join(', ')} in objectMetadataItem ${objectMetadataItem.nameSingular}`,
console.warn(
`computeOptimisticRecordFromInput: encountered unknown fields ${unknownRecordInputFields.join(', ')} in objectMetadataItem ${objectMetadataItem.nameSingular}. Skipping unknown fields.`,
);
}