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:
+28
@@ -0,0 +1,28 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
export const METADATA_EVENTS_TO_EMIT = {
|
||||
frontComponent: true,
|
||||
objectMetadata: true,
|
||||
fieldMetadata: true,
|
||||
view: true,
|
||||
viewField: true,
|
||||
viewFieldGroup: true,
|
||||
viewGroup: true,
|
||||
viewFilter: true,
|
||||
viewFilterGroup: true,
|
||||
role: true,
|
||||
roleTarget: true,
|
||||
agent: true,
|
||||
skill: true,
|
||||
pageLayout: true,
|
||||
pageLayoutWidget: true,
|
||||
pageLayoutTab: true,
|
||||
commandMenuItem: true,
|
||||
navigationMenuItem: true,
|
||||
rowLevelPermissionPredicate: true,
|
||||
rowLevelPermissionPredicateGroup: true,
|
||||
index: true,
|
||||
logicFunction: true,
|
||||
|
||||
webhook: false,
|
||||
} as const satisfies { [P in AllMetadataName]: boolean };
|
||||
+9
@@ -1,6 +1,7 @@
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type AllFlatWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common';
|
||||
import { METADATA_EVENTS_TO_EMIT } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant';
|
||||
import {
|
||||
type CreateMetadataEvent,
|
||||
type MetadataEvent,
|
||||
@@ -9,6 +10,14 @@ import { flatEntityToScalarFlatEntity } from 'src/engine/workspace-manager/works
|
||||
|
||||
export const deriveMetadataEventsFromCreateAction = (
|
||||
flatAction: AllFlatWorkspaceMigrationAction<'create'>,
|
||||
): MetadataEvent[] => {
|
||||
const events = deriveAllMetadataEventsFromCreateAction(flatAction);
|
||||
|
||||
return events.filter((event) => METADATA_EVENTS_TO_EMIT[event.metadataName]);
|
||||
};
|
||||
|
||||
const deriveAllMetadataEventsFromCreateAction = (
|
||||
flatAction: AllFlatWorkspaceMigrationAction<'create'>,
|
||||
): MetadataEvent[] => {
|
||||
switch (flatAction.metadataName) {
|
||||
case 'fieldMetadata': {
|
||||
|
||||
+13
@@ -5,6 +5,7 @@ import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { type AllFlatWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common';
|
||||
import { METADATA_EVENTS_TO_EMIT } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant';
|
||||
import { type MetadataEvent } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/metadata-event';
|
||||
import { flatEntityToScalarFlatEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/flat-entity-to-scalar-flat-entity.util';
|
||||
|
||||
@@ -16,6 +17,18 @@ export type DeriveMetadataEventsFromDeleteActionArgs = {
|
||||
export const deriveMetadataEventsFromDeleteAction = ({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
}: DeriveMetadataEventsFromDeleteActionArgs): MetadataEvent[] => {
|
||||
const events = deriveAllMetadataEventsFromDeleteAction({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
|
||||
return events.filter((event) => METADATA_EVENTS_TO_EMIT[event.metadataName]);
|
||||
};
|
||||
|
||||
const deriveAllMetadataEventsFromDeleteAction = ({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
}: DeriveMetadataEventsFromDeleteActionArgs): MetadataEvent[] => {
|
||||
switch (flatAction.metadataName) {
|
||||
case 'fieldMetadata':
|
||||
|
||||
+13
@@ -7,6 +7,7 @@ import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-m
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { type MetadataUniversalFlatEntityPropertiesToCompare } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type';
|
||||
import { type AllFlatWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common';
|
||||
import { METADATA_EVENTS_TO_EMIT } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/constants/metadata-event-to-emit.constant';
|
||||
import {
|
||||
type CreateMetadataEvent,
|
||||
type DeleteMetadataEvent,
|
||||
@@ -61,6 +62,18 @@ const buildUpdateMetadataEvent = <TMetadataName extends AllMetadataName>({
|
||||
export const deriveMetadataEventsFromUpdateAction = ({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
}: DeriveMetadataEventsFromUpdateActionArgs): MetadataEvent[] => {
|
||||
const events = deriveAllMetadataEventsFromUpdateAction({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
});
|
||||
|
||||
return events.filter((event) => METADATA_EVENTS_TO_EMIT[event.metadataName]);
|
||||
};
|
||||
|
||||
const deriveAllMetadataEventsFromUpdateAction = ({
|
||||
flatAction,
|
||||
allFlatEntityMaps,
|
||||
}: DeriveMetadataEventsFromUpdateActionArgs): MetadataEvent[] => {
|
||||
switch (flatAction.metadataName) {
|
||||
case 'index': {
|
||||
|
||||
Reference in New Issue
Block a user