Skip to content

Conversation

@moothz
Copy link
Contributor

@moothz moothz commented Oct 30, 2025

📋 Description

O objetivo principal destas modificações é corrigir o problema das mensagens não serem enviadas para alguns grupos, principalmente os que tem o número do criador no jid (ex.: '[email protected]').

A interrupção no fluxo do request é causada por um erro ao tentar criar um novo registro na base de dados com um remoteJid já existente, isto acontece pois os registros são buscados utilizando apenas o jidOptions, que às vezes não tem o atual remoteJid na lista.

O erro em questão:

Invalid `prismaRepository.isOnWhatsapp.create()` invocation in
evolution-api/src/utils/onWhatsappCache.ts:130:45
n
  127     },
  128   });
  129 } else {
 130   await prismaRepository.isOnWhatsapp.create(
Unique constraint failed on the fields: (`remoteJid`)

Os dados diretamente na base de dados, mostrando que o remoteJid não estava no jidOptions:

SELECT * FROM "IsOnWhatsapp" WHERE "remoteJid" = '[email protected]';
            id             |          remoteJid           |                        jidOptions                         |        createdAt        |       updatedAt        | lid
---------------------------+------------------------------+-----------------------------------------------------------+-------------------------+------------------------+-----
 cm...ok525sz | [email protected] | [email protected],[email protected] | 2025-10-19 13:40:08.508 | 2025-10-21 15:49:53.21 |

A lógica de busca foi alterada para prevenir erros de 'Unique constraint failed'.
Para corrigir isso, a busca no banco agora usa OR para verificar tanto pelo jidOptions quanto pelo remoteJid em uma única query.

Além dessa correção, outras otimizações foram implementadas:

  1. Updates Desnecessários: A função agora compara os dados novos com os dados do 'existingRecord'. O update só é executado se houver de fato uma mudança, reduzindo escritas desnecessárias no banco.
  2. Processamento Paralelo: A iteração sequencial (for...of) foi substituída por Promise.allSettled, permitindo que todos os itens do array data sejam processados em paralelo.
  3. Consistência de Dados: Os JIDs em jidOptions agora são ordenados antes de serem salvos ou comparados. Isso garante que a verificação de "mudança" seja precisa, independentemente da ordem de inserção.
  4. Refactor: A lógica de unificação de JIDs foi simplificada para usar Set e um helper normalizeJid foi criado para limpar o código. O código foi envolto em um try-catch para evitar interrupção do fluxo de envio de mensagens, visto que o cache não é crítico.

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

TODO: Investigar a causa raiz de o remoteJid às vezes não estar no jidOptions no momento da busca inicial. Também notei na base de dados que alguns números estão vindo com o "@g.us" duplicado.

Summary by Sourcery

Fix cache save errors for WhatsApp group messages and optimize saveOnWhatsappCache

Bug Fixes:

  • Prevent unique constraint failure by querying cache entries against both jidOptions and remoteJid

Enhancements:

  • Normalize and sort JIDs to ensure consistent cache entries
  • Process cache updates in parallel using Promise.allSettled
  • Compare new and existing records to skip unnecessary database writes

Chores:

  • Extract normalizeJid helper and encapsulate processing in try-catch for resilience
  • Exit early when caching is disabled via configuration

…B writes

Refactors the cache-saving logic to prevent `Unique constraint failed` errors. This issue occurs when an item's `remoteJid` is not yet included in the `jidOptions` of the existing record.

The database query now uses an `OR` condition to find a matching record by either `jidOptions` (using `contains`) or by the `remoteJid` itself in a single query.

Additionally, this commit introduces several performance optimizations:

1.  **Skip Unnecessary Updates**: The function now performs a deep comparison between the new payload and the `existingRecord`. An `update` operation is only executed if the data has actually changed, reducing unnecessary database writes.
2.  **Parallel Processing**: The sequential `for...of` loop has been replaced with `Promise.allSettled`. This allows all items in the `data` array to be processed concurrently, significantly speeding up execution for batch inputs.
3.  **Data Consistency**: The JIDs in `jidOptions` are now sorted alphabetically before being joined into a string. This ensures that the change-detection logic is accurate, regardless of the order in which JIDs were discovered.
4.  **Refactor**: Simplified JID unification logic using a `Set` and introduced a `normalizeJid` helper function for cleaner code.

TODO: Investigate the root cause of why `remoteJid` is sometimes not present in `jidOptions` upon initial discovery.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 30, 2025

Reviewer's Guide

Refactors the saveOnWhatsappCache function to prevent unique constraint errors by querying both jidOptions and remoteJid, optimize database writes through change detection, process items concurrently for performance, and improve JID normalization and error resilience.

Sequence diagram for the updated saveOnWhatsappCache process

sequenceDiagram
participant Caller
participant "saveOnWhatsappCache()"
participant "prismaRepository.isOnWhatsapp"
participant Logger

Caller->>"saveOnWhatsappCache()": Call with data array
"saveOnWhatsappCache()"->>Logger: Log start and config check
alt Parallel processing
    loop For each item (concurrent)
        "saveOnWhatsappCache()"->>Logger: Log normalization/skipped item
        "saveOnWhatsappCache()"->>"prismaRepository.isOnWhatsapp": findFirst({ OR: [jidOptions contains jid, remoteJid] })
        "saveOnWhatsappCache()"->>Logger: Log result of findFirst
        alt Record exists
            "saveOnWhatsappCache()"->>Logger: Log change detection
            alt Data unchanged
                "saveOnWhatsappCache()"->>Logger: Log skip update
            else Data changed
                "saveOnWhatsappCache()"->>Logger: Log update
                "saveOnWhatsappCache()"->>"prismaRepository.isOnWhatsapp": update({ id, data })
            end
        else No record
            "saveOnWhatsappCache()"->>Logger: Log create
            "saveOnWhatsappCache()"->>"prismaRepository.isOnWhatsapp": create({ data })
        end
        "saveOnWhatsappCache()"->>Logger: Log errors if any
    end
end
"saveOnWhatsappCache()"->>Caller: Return after all settled
Loading

Entity relationship diagram for IsOnWhatsapp table changes

erDiagram
    IsOnWhatsapp {
      string id
      string remoteJid
      string jidOptions
      string lid
      datetime createdAt
      datetime updatedAt
    }
    %% Note: The query now checks both remoteJid and jidOptions for existence
    %% No new tables, but logic for querying IsOnWhatsapp is updated
Loading

Class diagram for updated saveOnWhatsappCache logic

classDiagram
class ISaveOnWhatsappCacheParams {
  string remoteJid
  string remoteJidAlt
  string lid
}
class saveOnWhatsappCache {
  +async saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[])
}
class normalizeJid {
  +normalizeJid(jid: string | null | undefined): string | null
}
class prismaRepository_isOnWhatsapp {
  +findFirst(where)
  +update(where, data)
  +create(data)
}
class Logger {
  +verbose(...)
  +warn(...)
  +error(...)
}
ISaveOnWhatsappCacheParams <.. saveOnWhatsappCache : uses
saveOnWhatsappCache o-- normalizeJid : uses
saveOnWhatsappCache o-- prismaRepository_isOnWhatsapp : uses
saveOnWhatsappCache o-- Logger : uses
Loading

File-Level Changes

Change Details Files
Prevent unique constraint failures by expanding the database lookup
  • Added OR condition in findFirst to search by both jidOptions and remoteJid
  • Ensured current remoteJid is always included in query filters
  • Documented rationale for the expanded search logic
src/utils/onWhatsappCache.ts
Skip unnecessary updates by comparing payloads to existing records
  • Built sorted jidOptions string for accurate comparison
  • Compared new payload to existingRecord before update
  • Logged and returned early when data is unchanged
src/utils/onWhatsappCache.ts
Process cache entries in parallel to improve performance
  • Replaced sequential for...of with data.map and async functions
  • Used Promise.allSettled to await all operations concurrently
  • Implemented early return when feature flag is disabled
src/utils/onWhatsappCache.ts
Normalize and unify JID handling for consistency
  • Introduced normalizeJid helper to strip leading '+'
  • Aggregated JIDs into a Set to enforce uniqueness
  • Sorted JID list before joining into a string
src/utils/onWhatsappCache.ts
Enhance error handling and code resilience
  • Wrapped each item’s logic in try-catch to prevent flow interruption
  • Logged per-item errors without aborting overall process
  • Cleaned up legacy branches and removed unused code
src/utils/onWhatsappCache.ts

Possibly linked issues

  • #[BUG] Erro de Lógica/DB com 'create()' em onWhatsappCache.ts e Artefato de Build Corrompido - Versão Evo-2.3.6: The PR fixes the 'Unique constraint failed' error in onWhatsappCache.ts by improving record lookup, addressing the core issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@moothz moothz changed the title fix: Erro enviar mensagem para grupos (remoteJid failed constraint) fix: Erro ao enviar mensagem para grupos (remoteJid failed constraint) Oct 30, 2025
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Consider limiting the concurrency in Promise.allSettled (e.g. via a p-limit or batching) for large data arrays to prevent overwhelming your database connections.
  • Using Prisma’s contains on the comma-separated jidOptions string may produce false positives; for more reliable lookups consider storing jidOptions as an array field or using an exact-match filter.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider limiting the concurrency in Promise.allSettled (e.g. via a p-limit or batching) for large data arrays to prevent overwhelming your database connections.
- Using Prisma’s contains on the comma-separated jidOptions string may produce false positives; for more reliable lookups consider storing jidOptions as an array field or using an exact-match filter.

## Individual Comments

### Comment 1
<location> `src/utils/onWhatsappCache.ts:99-108` </location>
<code_context>
+      // 2. Unifica todos os JIDs usando um Set para garantir valores únicos
+      const finalJidOptions = new Set(expandedJids);

-      if (existingRecord?.jidOptions) {
-        const existingJids = existingRecord.jidOptions.split(',');
-        // TODO: Adicionar JIDs existentes que não estão na lista atual
-        existingJids.forEach((jid) => {
-          if (!finalJidOptions.includes(jid)) {
-            finalJidOptions.push(jid);
-          }
-        });
+      if (lidAltJid) {
+        finalJidOptions.add(lidAltJid);
       }
-
-      // TODO: Se tiver remoteJidAlt com @lid novo, adicionar
-      if (altJid && !finalJidOptions.includes(altJid)) {
-        finalJidOptions.push(altJid);
+      
+      if (existingRecord?.jidOptions) {
+        existingRecord.jidOptions.split(',').forEach(jid => finalJidOptions.add(jid));
       }

</code_context>

<issue_to_address>
**suggestion (bug_risk):** Merging existing JIDs may introduce legacy or malformed entries.

Validate each JID's format before adding to finalJidOptions to prevent legacy or malformed entries from being included.

Suggested implementation:

```typescript
      // 2. Unifica todos os JIDs usando um Set para garantir valores únicos e válidos
      function isValidJid(jid: string | null | undefined): boolean {
        // Exemplo de validação: JID deve conter '@' e não ser vazio
        return typeof jid === 'string' && jid.includes('@') && jid.trim().length > 0;
      }

      const finalJidOptions = new Set(
        expandedJids.filter(jid => isValidJid(jid))
      );

```

```typescript
      if (lidAltJid && isValidJid(lidAltJid)) {
        finalJidOptions.add(lidAltJid);
      }

```

```typescript
      if (existingRecord?.jidOptions) {
        existingRecord.jidOptions
          .split(',')
          .filter(jid => isValidJid(jid))
          .forEach(jid => finalJidOptions.add(jid));
      }

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Descartei as mudanças nos arquivos que não me pertencem, dá uma folga aí, botinho
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant