From ba3e881ddeba7db7063ec5f5d1a4ed3fc73f8564 Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Tue, 31 Mar 2026 06:31:05 +0000 Subject: [PATCH] fix: replace deep-equal with fast-deep-equal in isDeeplyEqual utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://sonarly.com/issue/19866?type=bug The `deep-equal` v2.2.3 library crashes with `TypeError: iterator must be a function` when comparing record objects received via SSE update events on the companies page, causing an unhandled error during optimistic cache updates. Fix: Replaced `deep-equal` v2.2.3 with `fast-deep-equal` in the `isDeeplyEqual` utility and all direct usages. `deep-equal` has a complex dependency chain (`which-typed-array` → `for-each` → `is-callable`) that crashes with `TypeError: iterator must be a function` when encountering certain object shapes during type introspection. `fast-deep-equal` is a much simpler, faster alternative that handles plain JSON objects (which is all record data consists of) without the complex type-detection logic that causes this crash. Changes: 1. **`isDeeplyEqual.ts`**: Replaced `deep-equal` import with `fast-deep-equal`. Removed the unused `options` parameter (only 2 callers passed `{ strict: true }`, and `fast-deep-equal` always uses strict comparison). 2. **`triggerUpdateRelationsOptimisticEffect.ts`**: Removed the `{ strict: true }` third argument from two `isDeeplyEqual` calls (no longer needed as `fast-deep-equal` is always strict). 3. **`useInitializeQueryParamState.ts`**: Replaced direct `deep-equal` import with `fast-deep-equal`. --- .../utils/triggerUpdateRelationsOptimisticEffect.ts | 8 +++----- .../src/modules/app/hooks/useInitializeQueryParamState.ts | 4 ++-- packages/twenty-front/src/utils/isDeeplyEqual.ts | 5 ++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts index 24bdee2ee2f..2e09d39b01a 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -162,7 +162,6 @@ const triggerUpdateRelationOptimisticEffect = ({ const noDiff = isDeeplyEqual( currentFieldValueOnSourceRecord, updatedFieldValueOnSourceRecord, - { strict: true }, ); if (noDiff && !isDeletion) { return; @@ -326,10 +325,9 @@ const triggerUpdateMorphRelationOptimisticEffect = ({ | null = updatedSourceRecord?.[gqlFieldMorphRelation]; const noDiff = isDeeplyEqual( - currentFieldValueOnSourceRecord, - updatedFieldValueOnSourceRecord, - { strict: true }, - ); + currentFieldValueOnSourceRecord, + updatedFieldValueOnSourceRecord, + ); if (noDiff && !isDeletion) { return; } diff --git a/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts b/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts index 2bc96a5f5dd..035b2cd414b 100644 --- a/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts +++ b/packages/twenty-front/src/modules/app/hooks/useInitializeQueryParamState.ts @@ -5,7 +5,7 @@ import { returnToPathState } from '@/auth/states/returnToPathState'; import { type BillingCheckoutSession } from '@/auth/types/billingCheckoutSession.type'; import { isValidReturnToPath } from '@/auth/utils/isValidReturnToPath'; import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/settings/billing/constants/BillingCheckoutSessionDefaultValue'; -import deepEqual from 'deep-equal'; +import fastDeepEqual from 'fast-deep-equal'; import { useStore } from 'jotai'; export const useInitializeQueryParamState = () => { @@ -26,7 +26,7 @@ export const useInitializeQueryParamState = () => { 'plan' in parsedValue && 'interval' in parsedValue && 'requirePaymentMethod' in parsedValue && - !deepEqual(billingCheckoutSession, parsedValue) + !fastDeepEqual(billingCheckoutSession, parsedValue) ) { store.set( billingCheckoutSessionState.atom, diff --git a/packages/twenty-front/src/utils/isDeeplyEqual.ts b/packages/twenty-front/src/utils/isDeeplyEqual.ts index 62f10026352..e43b4c37a77 100644 --- a/packages/twenty-front/src/utils/isDeeplyEqual.ts +++ b/packages/twenty-front/src/utils/isDeeplyEqual.ts @@ -1,4 +1,3 @@ -import deepEqual from 'deep-equal'; +import fastDeepEqual from 'fast-deep-equal'; -export const isDeeplyEqual = (a: T, b: T, options?: { strict: boolean }) => - deepEqual(a, b, options); +export const isDeeplyEqual = (a: T, b: T) => fastDeepEqual(a, b);