Files
twenty/packages/twenty-front/src/modules/sse-db-event/hooks/useDispatchMetadataEventsFromSseToBrowserEvents.ts
T
Raphaël Bosi 2455c859b4 Add SSE for metadata and plug front components (#17998)
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.

## Backend

- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`

## Frontend

- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
2026-02-18 11:26:20 +00:00

55 lines
1.8 KiB
TypeScript

import { dispatchMetadataOperationBrowserEvent } from '@/browser-event/utils/dispatchMetadataOperationBrowserEvent';
import { turnSseMetadataEventsToMetadataOperationBrowserEvents } from '@/sse-db-event/utils/turnSseMetadataEventsToMetadataOperationBrowserEvents';
import { useCallback } from 'react';
import {
type AllMetadataName,
type MetadataEvent,
type MetadataEventWithQueryIds,
} from '~/generated-metadata/graphql';
const groupSseMetadataEventsByMetadataName = (
sseMetadataEvents: MetadataEvent[],
): Map<AllMetadataName, MetadataEvent[]> => {
const eventsByMetadataName = new Map<AllMetadataName, MetadataEvent[]>();
for (const event of sseMetadataEvents) {
const metadataName = event.metadataName as AllMetadataName;
const existing = eventsByMetadataName.get(metadataName) ?? [];
eventsByMetadataName.set(metadataName, [...existing, event]);
}
return eventsByMetadataName;
};
export const useDispatchMetadataEventsFromSseToBrowserEvents = <
T extends Record<string, unknown>,
>() => {
const dispatchMetadataEventsFromSseToBrowserEvents = useCallback(
(metadataEventsWithQueryIds: MetadataEventWithQueryIds[]) => {
const sseMetadataEvents = metadataEventsWithQueryIds.map(
(item) => item.metadataEvent,
);
const eventsByMetadataName =
groupSseMetadataEventsByMetadataName(sseMetadataEvents);
for (const [metadataName, events] of eventsByMetadataName) {
const metadataOperationBrowserEvents =
turnSseMetadataEventsToMetadataOperationBrowserEvents<T>({
metadataName,
sseMetadataEvents: events,
});
for (const browserEvent of metadataOperationBrowserEvents) {
dispatchMetadataOperationBrowserEvent(browserEvent);
}
}
},
[],
);
return { dispatchMetadataEventsFromSseToBrowserEvents };
};