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
This commit is contained in:
Raphaël Bosi
2026-02-18 12:26:20 +01:00
committed by GitHub
parent e3753bf822
commit 2455c859b4
76 changed files with 1636 additions and 173 deletions
@@ -166,7 +166,9 @@ export {
export type { StringPropertyKeys } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties';
export { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties';
export { trimAndRemoveDuplicatedWhitespacesFromString } from './trim-and-remove-duplicated-whitespaces-from-string';
export { isMetadataGqlOperationSignature } from './typeguard/isMetadataGqlOperationSignature';
export { isPlainObject } from './typeguard/isPlainObject';
export { isRecordGqlOperationSignature } from './typeguard/isRecordGqlOperationSignature';
export { throwIfNotDefined } from './typeguard/throwIfNotDefined';
export { absoluteUrlSchema } from './url/absoluteUrlSchema';
export { buildSignedPath } from './url/buildSignedPath';
@@ -0,0 +1,9 @@
import { type MetadataGqlOperationSignature } from '../../types/MetadataGqlOperationSignature';
import { type RecordGqlOperationSignature } from '../../types/RecordGqlOperationSignature';
export const isMetadataGqlOperationSignature = (
operationSignature:
| RecordGqlOperationSignature
| MetadataGqlOperationSignature,
): operationSignature is MetadataGqlOperationSignature =>
'metadataName' in operationSignature;
@@ -0,0 +1,9 @@
import { type MetadataGqlOperationSignature } from '../../types/MetadataGqlOperationSignature';
import { type RecordGqlOperationSignature } from '../../types/RecordGqlOperationSignature';
export const isRecordGqlOperationSignature = (
operationSignature:
| RecordGqlOperationSignature
| MetadataGqlOperationSignature,
): operationSignature is RecordGqlOperationSignature =>
'objectNameSingular' in operationSignature;