---
title: "Синхронізація discovery після мутацій"
description: "Як Ring підтримує актуальність списків можливостей і сутностей після кожної зміни — кеш, SSR та realtime для засновників та інтеграторів"
locale: "uk"
---
# Синхронізація discovery після мутацій

> **Info**
> Фільтруйте цю сторінку у бічній панелі документації за **Founder** / **Developer**. Засновники дізнаються, *чому* списки залишаються актуальними; розробники — про модулі, події та точки розширення.

Коли хтось створює, редагує або видаляє **можливість** чи **сутність** у клонах Ring із PostgreSQL як основною БД, користувачі очікують, що маркетплейс і каталог оновляться **негайно** — без нічного завдання переіндексації. Ring координує три легкі кроки під час кожної мутації.

## Модель актуальності у три кроки

| Крок | Що бачать користувачі | Що відбувається всередині |
|------|------------------------|----------------------------|
| **1. Скидання кешу** | Сторінки списків перестають показувати застарілі картки | `revalidateTag` очищає кеші списків, розподілені за ролями |
| **2. Оновлення сторінки** | На наступній навігації сторінки деталей і хаби показують нові дані | `revalidatePath` інвалідовує SSR App Router |
| **3. Realtime-сигнал** | Відкриті вкладки оновлюються без ручного перезавантаження | Tunnel публікує події `opportunity:*` / `entity:*`. `/opportunities` підписується **безшумно** (`useRealtimeOpportunities`); старої панелі «Live Updates Active / via websocket» у списку більше немає. На сторінці деталей (`opportunity-details.tsx`) ця смуга все ще відображається. |

> **Tip**
> У клонах із PostgreSQL як основною БД **рядок у базі даних є пошуковим індексом**. Окремої таблиці Elasticsearch для перебудови немає — discovery-запити читають JSONB безпосередньо ([модель даних](./data-model)).

### For founders

## Чому це важливо для засновників

**Каталог** (сутності) і **маркетплейс потреб** (можливості) вашого клона — основний цикл, навколо якого монетизується більшість кілець. Застарілі списки швидше підривають довіру, ніж відсутня функція.

### Типові сценарії

  
- **[Дошка вакансій](/docs/features/opportunities.md)** — Учасник публікує контракт — схвалені оголошення з’являються на `/opportunities`, а підписники отримують сповіщення Tunnel.

  
- **[Каталог постачальників](/docs/features/entities.md)** — Верифікована сутність оновлює свою вітрину — сторінки профілів і публічний каталог оновлюються без втручання оператора.

  
- **[Подальша дія AI-матчера](/docs/features/ai-matcher.md)** — Нові можливості запускають матчер-пайплайни; актуальні теги кешу гарантують, що картки збігів посилаються на поточні поля бюджету й дедлайну.

  
- **[Процес модерації](/docs/features/admin.md)** — Процеси схвалення/відхилення адміністратора викликають ту саму синхронізацію — зміни статусу поводяться як оновлення для підписників каналу.

### Очікування від оператора

- **Для стандартного CRUD не потрібна ручна CLI-переіндексація** — якщо списки виглядають застарілими, спочатку перевірте транспорт Tunnel і `DB_BACKEND_MODE`.
- **Конфіденційні рівні** використовують спільні теги кешу списків; повторна валідація шляхів мінімізована для вузьких аудиторій (див. таблицю для розробників нижче).
- **Карти / графові подання** (Ringdom Maps) наразі є окремим сховищем — CRUD сутностей автоматично не оновлює вузли карт.

### For developers

## Архітектура

```mermaid
sequenceDiagram
    participant SA as Server Action / Service
    participant DB as DatabaseService (Postgres)
    participant Cache as invalidate*Cache
    participant RSC as revalidatePath
    participant T as Tunnel publishToChannel

    SA->>DB: create / update / delete
    DB-->>SA: success
    SA->>Cache: revalidateTag (role keys)
    SA->>RSC: hub + detail paths
    SA->>T: syncDiscovery(channel, id, event)
```

### Спільний помічник Tunnel

`lib/discovery/sync-discovery.ts` — лише realtime-розповсюдження (кеш і шляхи знаходяться в доменних обгортках):

{`export async function syncDiscovery(params: {
  channel: 'opportunities' | 'entities'
  resourceId: string
  event: 'created' | 'updated' | 'deleted' | 'status_changed'
}): Promise {
  const tunnelEvent = resolveDiscoveryTunnelEvent(params.channel, params.event)
  // status_changed → :updated on the wire
  await publishToChannel(params.channel, tunnelEvent, {
    id: params.resourceId,
    event: params.event,
  })
}`}

| Канал | Події Tunnel |
|---------|---------------|
| `opportunities` | `opportunity:created`, `opportunity:updated`, `opportunity:deleted` |
| `entities` | `entity:created`, `entity:updated`, `entity:deleted` |

### Доменні обгортки

| Ресурс | Модуль | Викликається з |
|----------|--------|--------------|
| Opportunities | `features/opportunities/lib/opportunity-mutation-sync.ts` | `create-opportunity`, `update-opportunity`, `delete-opportunity`, `auto-approval-service` |
| Entities | `features/entities/lib/entity-mutation-sync.ts` | `create-entity`, `update-entity`, `delete-entity`, хуки модерації та KYC |

### Після будь-якої мутації можливості

{`import { syncOpportunityDiscovery } from '@/features/opportunities/lib/opportunity-mutation-sync'

await syncOpportunityDiscovery({
  opportunityId: id,
  event: 'created', // | 'updated' | 'deleted' | 'status_changed'
})`}

### Після будь-якої мутації сутності

{`import { syncEntityDiscovery } from '@/features/entities/lib/entity-mutation-sync'

await syncEntityDiscovery({
  entityId: id,
  event: 'updated',
})`}

### Підписка на клієнті

Підключайте слухачі топіків через **`useTunnelChannel`** — production-хуки розбирають payload `syncDiscovery` за допомогою `lib/discovery/parse-discovery-tunnel-message.ts` (`{ id, event }` і `message.event`, наприклад `entity:created`).

| Канал | Хук | Підключення до UI |
|------|------|-----------|
| `opportunities` | `hooks/use-realtime-opportunities.ts` | Browse: `opportunities.tsx` (безшумна підписка). Деталі: `opportunity-details.tsx` (панель live-оновлень усе ще відображається). Сніпети створення містять `creator { id, name, avatar }`, тому картки, додані realtime, не показують «Private User». |
| `entities` | `hooks/use-realtime-entities.ts` | `features/entities/components/entities.tsx` — видалення локально вирізає елемент; створення/оновлення робить м’яке перезавантаження cursor feed |

Див. [Realtime-транспорт](./real-time) і [протокол Tunnel](../features/tunnel-protocol).

### Шляхи, що повторно валідовуються

**Можливості**

- `/[locale]/opportunities`
- `/[locale]/opportunities/[id]`
- `/[locale]/opportunities/my`
- `/opportunities` (під час створення / зміни статусу)

**Сутності**

- `/[locale]/entities`
- `/[locale]/entities/[id]`
- `/[locale]/entities/my`
- `/entities` (під час створення / зміни статусу)

### Шляхи, навмисно виключені

| Шлях | Причина |
|------|---------|
| `/[locale]/entities/add` | Одноразова форма; після створення виконується перенаправлення |
| `/[locale]/entities/status/...` | Платіжні callback-и — не стосуються CRUD discovery |
| `/[locale]/confidential/entities` | Інвалідації тегів достатньо; це уникає зайвого оновлення шляхів |
| Вузли Ringdom Maps | Окреме графове сховище — не підключене до кешів списків сутностей |

### Відображення рядків

Читання перетворюють рядки `DatabaseService` через:

- `features/opportunities/lib/opportunity-db-mapper.ts`
- `features/entities/lib/entity-db-mapper.ts`

Застарілі конвертери Firestore у `lib/converters/*-converter.ts` застосовуються лише коли `DB_BACKEND_MODE=firebase-full`.

## Пов’язана документація

  
- [features/tunnel-protocol](/docs/features/tunnel-protocol.md) — Передумова: SSOT підписки на канал і відмінність між publishToChannel та publishToUserTunnel.

  
- [architecture/real-time](/docs/architecture/real-time.md) — Глибше занурення: брокер TunnelHub і матриця публікації/підписки функцій.

  
- [architecture/data-model](/docs/architecture/data-model.md) — Залежність: колекції JSONB, синхронізовані цим пайплайном.

  
- [features/opportunities](/docs/features/opportunities.md) — Той самий процес: стрічка перегляду гідратується зі спискових API та безшумних вставок Tunnel.

  
- [api/opportunities](/docs/api/opportunities.md) — Той самий процес: REST-поверхня та хуки після мутацій для можливостей.

  
- [api/entities](/docs/api/entities.md) — Той самий процес: REST-поверхня та хуки після мутацій для сутностей.
