From 5526d2e5d0ec695cb174f7515eefef98fe24f25c Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Thu, 19 Mar 2026 14:24:38 +0100 Subject: [PATCH] Separate metadata and object record publisher (#18740) - Separate both publishers - Renaming - Removal of the old SSE endpoint --------- Co-authored-by: Charles Bochet --- .../src/generated-metadata/graphql.ts | 21 - .../clients/generated/metadata/schema.graphql | 201 ++- .../src/clients/generated/metadata/schema.ts | 545 +++---- .../src/clients/generated/metadata/types.ts | 1447 ++++++++--------- .../enums/database-event-action.ts | 7 + .../listeners/entity-events-to-db.listener.ts | 6 +- .../engine/core-modules/core-engine.module.ts | 2 - .../metadata-event-emitter.module.ts | 14 - .../call-webhook-jobs-for-metadata.job.ts | 2 +- .../subscriptions/dtos/on-db-event.dto.ts | 29 - .../subscriptions/dtos/on-db-event.input.ts | 16 - .../enums/subscription-channel.enum.ts | 1 - .../event-stream-exception.filter.ts} | 4 +- .../event-stream.resolver.ts} | 50 +- .../metadata-event}/metadata-event-emitter.ts | 4 +- .../metadata-event-publisher.ts | 250 +++ .../metadata-events-to-db.listener.ts | 8 +- .../types/metadata-event-batch.type.ts | 0 .../utils/compute-metadata-event-name.util.ts | 0 ...ield-metadata-event-with-relations.util.ts | 0 .../object-record-event-publisher.spec.ts} | 21 +- .../object-record-event-publisher.ts} | 309 +--- .../subscriptions/subscriptions.module.ts | 36 +- .../get-channel-id-from-event-stream-id.ts | 0 .../wrap-async-iterator-with-lifecycle.ts | 0 ...tadata-record-matching-filter.util.spec.ts | 512 ------ ...is-metadata-record-matching-filter.util.ts | 198 --- .../workspace-event-emitter.module.ts | 26 +- ...igration-validate-build-and-run-service.ts | 2 +- 29 files changed, 1398 insertions(+), 2313 deletions(-) delete mode 100644 packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.module.ts delete mode 100644 packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.dto.ts delete mode 100644 packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.input.ts rename packages/twenty-server/src/engine/{workspace-event-emitter/workspace-event-emitter-exception.filter.ts => subscriptions/event-stream-exception.filter.ts} (91%) rename packages/twenty-server/src/engine/{workspace-event-emitter/workspace-event-emitter.resolver.ts => subscriptions/event-stream.resolver.ts} (80%) rename packages/twenty-server/src/engine/{metadata-event-emitter => subscriptions/metadata-event}/metadata-event-emitter.ts (93%) create mode 100644 packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.ts rename packages/twenty-server/src/engine/{metadata-event-emitter/listeners => subscriptions/metadata-event}/metadata-events-to-db.listener.ts (88%) rename packages/twenty-server/src/engine/{metadata-event-emitter => subscriptions/metadata-event}/types/metadata-event-batch.type.ts (100%) rename packages/twenty-server/src/engine/{metadata-event-emitter => subscriptions/metadata-event}/utils/compute-metadata-event-name.util.ts (100%) rename packages/twenty-server/src/engine/{workspace-event-emitter => subscriptions/metadata-event}/utils/enrich-field-metadata-event-with-relations.util.ts (100%) rename packages/twenty-server/src/engine/{workspace-event-emitter/__tests__/workspace-event-emitter.service.spec.ts => subscriptions/object-record-event/__tests__/object-record-event-publisher.spec.ts} (98%) rename packages/twenty-server/src/engine/{workspace-event-emitter/workspace-event-emitter.service.ts => subscriptions/object-record-event/object-record-event-publisher.ts} (66%) rename packages/twenty-server/src/engine/{workspace-event-emitter => subscriptions}/utils/get-channel-id-from-event-stream-id.ts (100%) rename packages/twenty-server/src/engine/{workspace-event-emitter => subscriptions}/utils/wrap-async-iterator-with-lifecycle.ts (100%) delete mode 100644 packages/twenty-server/src/engine/workspace-event-emitter/utils/__tests__/is-metadata-record-matching-filter.util.spec.ts delete mode 100644 packages/twenty-server/src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index ba2ba4dd1eb..946d57e0536 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -3687,21 +3687,6 @@ export type ObjectStandardOverrides = { translations?: Maybe; }; -export type OnDbEvent = { - __typename?: 'OnDbEvent'; - action: DatabaseEventAction; - eventDate: Scalars['DateTime']; - objectNameSingular: Scalars['String']; - record: Scalars['JSON']; - updatedFields?: Maybe>; -}; - -export type OnDbEventInput = { - action?: InputMaybe; - objectNameSingular?: InputMaybe; - recordId?: InputMaybe; -}; - /** Onboarding status */ export enum OnboardingStatus { BOOK_ONBOARDING = 'BOOK_ONBOARDING', @@ -4738,7 +4723,6 @@ export type StandardOverrides = { export type Subscription = { __typename?: 'Subscription'; logicFunctionLogs: LogicFunctionLogs; - onDbEvent: OnDbEvent; onEventSubscription?: Maybe; }; @@ -4748,11 +4732,6 @@ export type SubscriptionLogicFunctionLogsArgs = { }; -export type SubscriptionOnDbEventArgs = { - input: OnDbEventInput; -}; - - export type SubscriptionOnEventSubscriptionArgs = { eventStreamId: Scalars['String']; }; diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql index 0bbed14d684..e8168ef17ae 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.graphql @@ -1236,107 +1236,6 @@ enum PageLayoutType { DASHBOARD } -type FileWithSignedUrl { - id: UUID! - path: String! - size: Float! - createdAt: DateTime! - url: String! -} - -type RecordIdentifier { - id: UUID! - labelIdentifier: String! - imageIdentifier: String -} - -type NavigationMenuItem { - id: UUID! - userWorkspaceId: UUID - targetRecordId: UUID - targetObjectMetadataId: UUID - viewId: UUID - type: NavigationMenuItemType! - name: String - link: String - icon: String - color: String - folderId: UUID - position: Float! - applicationId: UUID - createdAt: DateTime! - updatedAt: DateTime! - targetRecordIdentifier: RecordIdentifier -} - -enum NavigationMenuItemType { - VIEW - FOLDER - LINK - OBJECT - RECORD -} - -type ObjectRecordEventProperties { - updatedFields: [String!] - before: JSON - after: JSON - diff: JSON -} - -type MetadataEvent { - type: MetadataEventAction! - metadataName: String! - recordId: String! - properties: ObjectRecordEventProperties! - updatedCollectionHash: String -} - -"""Metadata Event Action""" -enum MetadataEventAction { - CREATED - UPDATED - DELETED -} - -type ObjectRecordEvent { - action: DatabaseEventAction! - objectNameSingular: String! - recordId: String! - userId: String - workspaceMemberId: String - properties: ObjectRecordEventProperties! -} - -"""Database Event Action""" -enum DatabaseEventAction { - CREATED - UPDATED - DELETED - DESTROYED - RESTORED - UPSERTED -} - -type ObjectRecordEventWithQueryIds { - queryIds: [String!]! - objectRecordEvent: ObjectRecordEvent! -} - -type EventSubscription { - eventStreamId: String! - objectRecordEventsWithQueryIds: [ObjectRecordEventWithQueryIds!]! - metadataEvents: [MetadataEvent!]! -} - -type OnDbEvent { - action: DatabaseEventAction! - objectNameSingular: String! - eventDate: DateTime! - record: JSON! - updatedFields: [String!] -} - type Analytics { """Boolean that confirms query was dispatched""" success: Boolean! @@ -1523,6 +1422,14 @@ type ApprovedAccessDomain { createdAt: DateTime! } +type FileWithSignedUrl { + id: UUID! + path: String! + size: Float! + createdAt: DateTime! + url: String! +} + type WorkspaceInvitation { id: UUID! email: String! @@ -1958,6 +1865,91 @@ type WorkspaceInviteHashValid { isValid: Boolean! } +type RecordIdentifier { + id: UUID! + labelIdentifier: String! + imageIdentifier: String +} + +type NavigationMenuItem { + id: UUID! + userWorkspaceId: UUID + targetRecordId: UUID + targetObjectMetadataId: UUID + viewId: UUID + type: NavigationMenuItemType! + name: String + link: String + icon: String + color: String + folderId: UUID + position: Float! + applicationId: UUID + createdAt: DateTime! + updatedAt: DateTime! + targetRecordIdentifier: RecordIdentifier +} + +enum NavigationMenuItemType { + VIEW + FOLDER + LINK + OBJECT + RECORD +} + +type ObjectRecordEventProperties { + updatedFields: [String!] + before: JSON + after: JSON + diff: JSON +} + +type MetadataEvent { + type: MetadataEventAction! + metadataName: String! + recordId: String! + properties: ObjectRecordEventProperties! + updatedCollectionHash: String +} + +"""Metadata Event Action""" +enum MetadataEventAction { + CREATED + UPDATED + DELETED +} + +type ObjectRecordEvent { + action: DatabaseEventAction! + objectNameSingular: String! + recordId: String! + userId: String + workspaceMemberId: String + properties: ObjectRecordEventProperties! +} + +"""Database Event Action""" +enum DatabaseEventAction { + CREATED + UPDATED + DELETED + DESTROYED + RESTORED + UPSERTED +} + +type ObjectRecordEventWithQueryIds { + queryIds: [String!]! + objectRecordEvent: ObjectRecordEvent! +} + +type EventSubscription { + eventStreamId: String! + objectRecordEventsWithQueryIds: [ObjectRecordEventWithQueryIds!]! + metadataEvents: [MetadataEvent!]! +} + type LogicFunctionExecutionResult { """Execution result in JSON format""" data: JSON @@ -4316,17 +4308,10 @@ enum FileFolder { } type Subscription { - onDbEvent(input: OnDbEventInput!): OnDbEvent! onEventSubscription(eventStreamId: String!): EventSubscription logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs! } -input OnDbEventInput { - action: DatabaseEventAction - objectNameSingular: String - recordId: UUID -} - input LogicFunctionLogsInput { applicationId: UUID applicationUniversalIdentifier: UUID diff --git a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts index 966ca9ebbea..e1e4c6b7f30 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/schema.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/schema.ts @@ -963,101 +963,6 @@ export interface PageLayout { export type PageLayoutType = 'RECORD_INDEX' | 'RECORD_PAGE' | 'DASHBOARD' -export interface FileWithSignedUrl { - id: Scalars['UUID'] - path: Scalars['String'] - size: Scalars['Float'] - createdAt: Scalars['DateTime'] - url: Scalars['String'] - __typename: 'FileWithSignedUrl' -} - -export interface RecordIdentifier { - id: Scalars['UUID'] - labelIdentifier: Scalars['String'] - imageIdentifier?: Scalars['String'] - __typename: 'RecordIdentifier' -} - -export interface NavigationMenuItem { - id: Scalars['UUID'] - userWorkspaceId?: Scalars['UUID'] - targetRecordId?: Scalars['UUID'] - targetObjectMetadataId?: Scalars['UUID'] - viewId?: Scalars['UUID'] - type: NavigationMenuItemType - name?: Scalars['String'] - link?: Scalars['String'] - icon?: Scalars['String'] - color?: Scalars['String'] - folderId?: Scalars['UUID'] - position: Scalars['Float'] - applicationId?: Scalars['UUID'] - createdAt: Scalars['DateTime'] - updatedAt: Scalars['DateTime'] - targetRecordIdentifier?: RecordIdentifier - __typename: 'NavigationMenuItem' -} - -export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD' - -export interface ObjectRecordEventProperties { - updatedFields?: Scalars['String'][] - before?: Scalars['JSON'] - after?: Scalars['JSON'] - diff?: Scalars['JSON'] - __typename: 'ObjectRecordEventProperties' -} - -export interface MetadataEvent { - type: MetadataEventAction - metadataName: Scalars['String'] - recordId: Scalars['String'] - properties: ObjectRecordEventProperties - updatedCollectionHash?: Scalars['String'] - __typename: 'MetadataEvent' -} - - -/** Metadata Event Action */ -export type MetadataEventAction = 'CREATED' | 'UPDATED' | 'DELETED' - -export interface ObjectRecordEvent { - action: DatabaseEventAction - objectNameSingular: Scalars['String'] - recordId: Scalars['String'] - userId?: Scalars['String'] - workspaceMemberId?: Scalars['String'] - properties: ObjectRecordEventProperties - __typename: 'ObjectRecordEvent' -} - - -/** Database Event Action */ -export type DatabaseEventAction = 'CREATED' | 'UPDATED' | 'DELETED' | 'DESTROYED' | 'RESTORED' | 'UPSERTED' - -export interface ObjectRecordEventWithQueryIds { - queryIds: Scalars['String'][] - objectRecordEvent: ObjectRecordEvent - __typename: 'ObjectRecordEventWithQueryIds' -} - -export interface EventSubscription { - eventStreamId: Scalars['String'] - objectRecordEventsWithQueryIds: ObjectRecordEventWithQueryIds[] - metadataEvents: MetadataEvent[] - __typename: 'EventSubscription' -} - -export interface OnDbEvent { - action: DatabaseEventAction - objectNameSingular: Scalars['String'] - eventDate: Scalars['DateTime'] - record: Scalars['JSON'] - updatedFields?: Scalars['String'][] - __typename: 'OnDbEvent' -} - export interface Analytics { /** Boolean that confirms query was dispatched */ success: Scalars['Boolean'] @@ -1241,6 +1146,15 @@ export interface ApprovedAccessDomain { __typename: 'ApprovedAccessDomain' } +export interface FileWithSignedUrl { + id: Scalars['UUID'] + path: Scalars['String'] + size: Scalars['Float'] + createdAt: Scalars['DateTime'] + url: Scalars['String'] + __typename: 'FileWithSignedUrl' +} + export interface WorkspaceInvitation { id: Scalars['UUID'] email: Scalars['String'] @@ -1687,6 +1601,83 @@ export interface WorkspaceInviteHashValid { __typename: 'WorkspaceInviteHashValid' } +export interface RecordIdentifier { + id: Scalars['UUID'] + labelIdentifier: Scalars['String'] + imageIdentifier?: Scalars['String'] + __typename: 'RecordIdentifier' +} + +export interface NavigationMenuItem { + id: Scalars['UUID'] + userWorkspaceId?: Scalars['UUID'] + targetRecordId?: Scalars['UUID'] + targetObjectMetadataId?: Scalars['UUID'] + viewId?: Scalars['UUID'] + type: NavigationMenuItemType + name?: Scalars['String'] + link?: Scalars['String'] + icon?: Scalars['String'] + color?: Scalars['String'] + folderId?: Scalars['UUID'] + position: Scalars['Float'] + applicationId?: Scalars['UUID'] + createdAt: Scalars['DateTime'] + updatedAt: Scalars['DateTime'] + targetRecordIdentifier?: RecordIdentifier + __typename: 'NavigationMenuItem' +} + +export type NavigationMenuItemType = 'VIEW' | 'FOLDER' | 'LINK' | 'OBJECT' | 'RECORD' + +export interface ObjectRecordEventProperties { + updatedFields?: Scalars['String'][] + before?: Scalars['JSON'] + after?: Scalars['JSON'] + diff?: Scalars['JSON'] + __typename: 'ObjectRecordEventProperties' +} + +export interface MetadataEvent { + type: MetadataEventAction + metadataName: Scalars['String'] + recordId: Scalars['String'] + properties: ObjectRecordEventProperties + updatedCollectionHash?: Scalars['String'] + __typename: 'MetadataEvent' +} + + +/** Metadata Event Action */ +export type MetadataEventAction = 'CREATED' | 'UPDATED' | 'DELETED' + +export interface ObjectRecordEvent { + action: DatabaseEventAction + objectNameSingular: Scalars['String'] + recordId: Scalars['String'] + userId?: Scalars['String'] + workspaceMemberId?: Scalars['String'] + properties: ObjectRecordEventProperties + __typename: 'ObjectRecordEvent' +} + + +/** Database Event Action */ +export type DatabaseEventAction = 'CREATED' | 'UPDATED' | 'DELETED' | 'DESTROYED' | 'RESTORED' | 'UPSERTED' + +export interface ObjectRecordEventWithQueryIds { + queryIds: Scalars['String'][] + objectRecordEvent: ObjectRecordEvent + __typename: 'ObjectRecordEventWithQueryIds' +} + +export interface EventSubscription { + eventStreamId: Scalars['String'] + objectRecordEventsWithQueryIds: ObjectRecordEventWithQueryIds[] + metadataEvents: MetadataEvent[] + __typename: 'EventSubscription' +} + export interface LogicFunctionExecutionResult { /** Execution result in JSON format */ data?: Scalars['JSON'] @@ -2881,7 +2872,6 @@ export type WorkspaceMigrationActionType = 'delete' | 'create' | 'update' export type FileFolder = 'ProfilePicture' | 'WorkspaceLogo' | 'Attachment' | 'PersonPicture' | 'CorePicture' | 'File' | 'AgentChat' | 'BuiltLogicFunction' | 'BuiltFrontComponent' | 'PublicAsset' | 'Source' | 'FilesField' | 'Dependencies' | 'Workflow' | 'AppTarball' export interface Subscription { - onDbEvent: OnDbEvent onEventSubscription?: EventSubscription logicFunctionLogs: LogicFunctionLogs __typename: 'Subscription' @@ -3897,100 +3887,6 @@ export interface PageLayoutGenqlSelection{ __scalar?: boolean | number } -export interface FileWithSignedUrlGenqlSelection{ - id?: boolean | number - path?: boolean | number - size?: boolean | number - createdAt?: boolean | number - url?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface RecordIdentifierGenqlSelection{ - id?: boolean | number - labelIdentifier?: boolean | number - imageIdentifier?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface NavigationMenuItemGenqlSelection{ - id?: boolean | number - userWorkspaceId?: boolean | number - targetRecordId?: boolean | number - targetObjectMetadataId?: boolean | number - viewId?: boolean | number - type?: boolean | number - name?: boolean | number - link?: boolean | number - icon?: boolean | number - color?: boolean | number - folderId?: boolean | number - position?: boolean | number - applicationId?: boolean | number - createdAt?: boolean | number - updatedAt?: boolean | number - targetRecordIdentifier?: RecordIdentifierGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface ObjectRecordEventPropertiesGenqlSelection{ - updatedFields?: boolean | number - before?: boolean | number - after?: boolean | number - diff?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface MetadataEventGenqlSelection{ - type?: boolean | number - metadataName?: boolean | number - recordId?: boolean | number - properties?: ObjectRecordEventPropertiesGenqlSelection - updatedCollectionHash?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface ObjectRecordEventGenqlSelection{ - action?: boolean | number - objectNameSingular?: boolean | number - recordId?: boolean | number - userId?: boolean | number - workspaceMemberId?: boolean | number - properties?: ObjectRecordEventPropertiesGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface ObjectRecordEventWithQueryIdsGenqlSelection{ - queryIds?: boolean | number - objectRecordEvent?: ObjectRecordEventGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface EventSubscriptionGenqlSelection{ - eventStreamId?: boolean | number - objectRecordEventsWithQueryIds?: ObjectRecordEventWithQueryIdsGenqlSelection - metadataEvents?: MetadataEventGenqlSelection - __typename?: boolean | number - __scalar?: boolean | number -} - -export interface OnDbEventGenqlSelection{ - action?: boolean | number - objectNameSingular?: boolean | number - eventDate?: boolean | number - record?: boolean | number - updatedFields?: boolean | number - __typename?: boolean | number - __scalar?: boolean | number -} - export interface AnalyticsGenqlSelection{ /** Boolean that confirms query was dispatched */ success?: boolean | number @@ -4181,6 +4077,16 @@ export interface ApprovedAccessDomainGenqlSelection{ __scalar?: boolean | number } +export interface FileWithSignedUrlGenqlSelection{ + id?: boolean | number + path?: boolean | number + size?: boolean | number + createdAt?: boolean | number + url?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + export interface WorkspaceInvitationGenqlSelection{ id?: boolean | number email?: boolean | number @@ -4676,6 +4582,80 @@ export interface WorkspaceInviteHashValidGenqlSelection{ __scalar?: boolean | number } +export interface RecordIdentifierGenqlSelection{ + id?: boolean | number + labelIdentifier?: boolean | number + imageIdentifier?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface NavigationMenuItemGenqlSelection{ + id?: boolean | number + userWorkspaceId?: boolean | number + targetRecordId?: boolean | number + targetObjectMetadataId?: boolean | number + viewId?: boolean | number + type?: boolean | number + name?: boolean | number + link?: boolean | number + icon?: boolean | number + color?: boolean | number + folderId?: boolean | number + position?: boolean | number + applicationId?: boolean | number + createdAt?: boolean | number + updatedAt?: boolean | number + targetRecordIdentifier?: RecordIdentifierGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface ObjectRecordEventPropertiesGenqlSelection{ + updatedFields?: boolean | number + before?: boolean | number + after?: boolean | number + diff?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface MetadataEventGenqlSelection{ + type?: boolean | number + metadataName?: boolean | number + recordId?: boolean | number + properties?: ObjectRecordEventPropertiesGenqlSelection + updatedCollectionHash?: boolean | number + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface ObjectRecordEventGenqlSelection{ + action?: boolean | number + objectNameSingular?: boolean | number + recordId?: boolean | number + userId?: boolean | number + workspaceMemberId?: boolean | number + properties?: ObjectRecordEventPropertiesGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface ObjectRecordEventWithQueryIdsGenqlSelection{ + queryIds?: boolean | number + objectRecordEvent?: ObjectRecordEventGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + +export interface EventSubscriptionGenqlSelection{ + eventStreamId?: boolean | number + objectRecordEventsWithQueryIds?: ObjectRecordEventWithQueryIdsGenqlSelection + metadataEvents?: MetadataEventGenqlSelection + __typename?: boolean | number + __scalar?: boolean | number +} + export interface LogicFunctionExecutionResultGenqlSelection{ /** Execution result in JSON format */ data?: boolean | number @@ -6266,15 +6246,12 @@ export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActio export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']} export interface SubscriptionGenqlSelection{ - onDbEvent?: (OnDbEventGenqlSelection & { __args: {input: OnDbEventInput} }) onEventSubscription?: (EventSubscriptionGenqlSelection & { __args: {eventStreamId: Scalars['String']} }) logicFunctionLogs?: (LogicFunctionLogsGenqlSelection & { __args: {input: LogicFunctionLogsInput} }) __typename?: boolean | number __scalar?: boolean | number } -export interface OnDbEventInput {action?: (DatabaseEventAction | null),objectNameSingular?: (Scalars['String'] | null),recordId?: (Scalars['UUID'] | null)} - export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null),applicationUniversalIdentifier?: (Scalars['UUID'] | null),name?: (Scalars['String'] | null),id?: (Scalars['UUID'] | null),universalIdentifier?: (Scalars['UUID'] | null)} @@ -6822,78 +6799,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null - const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl'] - export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => { - if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"') - return FileWithSignedUrl_possibleTypes.includes(obj.__typename) - } - - - - const RecordIdentifier_possibleTypes: string[] = ['RecordIdentifier'] - export const isRecordIdentifier = (obj?: { __typename?: any } | null): obj is RecordIdentifier => { - if (!obj?.__typename) throw new Error('__typename is missing in "isRecordIdentifier"') - return RecordIdentifier_possibleTypes.includes(obj.__typename) - } - - - - const NavigationMenuItem_possibleTypes: string[] = ['NavigationMenuItem'] - export const isNavigationMenuItem = (obj?: { __typename?: any } | null): obj is NavigationMenuItem => { - if (!obj?.__typename) throw new Error('__typename is missing in "isNavigationMenuItem"') - return NavigationMenuItem_possibleTypes.includes(obj.__typename) - } - - - - const ObjectRecordEventProperties_possibleTypes: string[] = ['ObjectRecordEventProperties'] - export const isObjectRecordEventProperties = (obj?: { __typename?: any } | null): obj is ObjectRecordEventProperties => { - if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEventProperties"') - return ObjectRecordEventProperties_possibleTypes.includes(obj.__typename) - } - - - - const MetadataEvent_possibleTypes: string[] = ['MetadataEvent'] - export const isMetadataEvent = (obj?: { __typename?: any } | null): obj is MetadataEvent => { - if (!obj?.__typename) throw new Error('__typename is missing in "isMetadataEvent"') - return MetadataEvent_possibleTypes.includes(obj.__typename) - } - - - - const ObjectRecordEvent_possibleTypes: string[] = ['ObjectRecordEvent'] - export const isObjectRecordEvent = (obj?: { __typename?: any } | null): obj is ObjectRecordEvent => { - if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEvent"') - return ObjectRecordEvent_possibleTypes.includes(obj.__typename) - } - - - - const ObjectRecordEventWithQueryIds_possibleTypes: string[] = ['ObjectRecordEventWithQueryIds'] - export const isObjectRecordEventWithQueryIds = (obj?: { __typename?: any } | null): obj is ObjectRecordEventWithQueryIds => { - if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEventWithQueryIds"') - return ObjectRecordEventWithQueryIds_possibleTypes.includes(obj.__typename) - } - - - - const EventSubscription_possibleTypes: string[] = ['EventSubscription'] - export const isEventSubscription = (obj?: { __typename?: any } | null): obj is EventSubscription => { - if (!obj?.__typename) throw new Error('__typename is missing in "isEventSubscription"') - return EventSubscription_possibleTypes.includes(obj.__typename) - } - - - - const OnDbEvent_possibleTypes: string[] = ['OnDbEvent'] - export const isOnDbEvent = (obj?: { __typename?: any } | null): obj is OnDbEvent => { - if (!obj?.__typename) throw new Error('__typename is missing in "isOnDbEvent"') - return OnDbEvent_possibleTypes.includes(obj.__typename) - } - - - const Analytics_possibleTypes: string[] = ['Analytics'] export const isAnalytics = (obj?: { __typename?: any } | null): obj is Analytics => { if (!obj?.__typename) throw new Error('__typename is missing in "isAnalytics"') @@ -7062,6 +6967,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl'] + export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => { + if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"') + return FileWithSignedUrl_possibleTypes.includes(obj.__typename) + } + + + const WorkspaceInvitation_possibleTypes: string[] = ['WorkspaceInvitation'] export const isWorkspaceInvitation = (obj?: { __typename?: any } | null): obj is WorkspaceInvitation => { if (!obj?.__typename) throw new Error('__typename is missing in "isWorkspaceInvitation"') @@ -7550,6 +7463,62 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null + const RecordIdentifier_possibleTypes: string[] = ['RecordIdentifier'] + export const isRecordIdentifier = (obj?: { __typename?: any } | null): obj is RecordIdentifier => { + if (!obj?.__typename) throw new Error('__typename is missing in "isRecordIdentifier"') + return RecordIdentifier_possibleTypes.includes(obj.__typename) + } + + + + const NavigationMenuItem_possibleTypes: string[] = ['NavigationMenuItem'] + export const isNavigationMenuItem = (obj?: { __typename?: any } | null): obj is NavigationMenuItem => { + if (!obj?.__typename) throw new Error('__typename is missing in "isNavigationMenuItem"') + return NavigationMenuItem_possibleTypes.includes(obj.__typename) + } + + + + const ObjectRecordEventProperties_possibleTypes: string[] = ['ObjectRecordEventProperties'] + export const isObjectRecordEventProperties = (obj?: { __typename?: any } | null): obj is ObjectRecordEventProperties => { + if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEventProperties"') + return ObjectRecordEventProperties_possibleTypes.includes(obj.__typename) + } + + + + const MetadataEvent_possibleTypes: string[] = ['MetadataEvent'] + export const isMetadataEvent = (obj?: { __typename?: any } | null): obj is MetadataEvent => { + if (!obj?.__typename) throw new Error('__typename is missing in "isMetadataEvent"') + return MetadataEvent_possibleTypes.includes(obj.__typename) + } + + + + const ObjectRecordEvent_possibleTypes: string[] = ['ObjectRecordEvent'] + export const isObjectRecordEvent = (obj?: { __typename?: any } | null): obj is ObjectRecordEvent => { + if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEvent"') + return ObjectRecordEvent_possibleTypes.includes(obj.__typename) + } + + + + const ObjectRecordEventWithQueryIds_possibleTypes: string[] = ['ObjectRecordEventWithQueryIds'] + export const isObjectRecordEventWithQueryIds = (obj?: { __typename?: any } | null): obj is ObjectRecordEventWithQueryIds => { + if (!obj?.__typename) throw new Error('__typename is missing in "isObjectRecordEventWithQueryIds"') + return ObjectRecordEventWithQueryIds_possibleTypes.includes(obj.__typename) + } + + + + const EventSubscription_possibleTypes: string[] = ['EventSubscription'] + export const isEventSubscription = (obj?: { __typename?: any } | null): obj is EventSubscription => { + if (!obj?.__typename) throw new Error('__typename is missing in "isEventSubscription"') + return EventSubscription_possibleTypes.includes(obj.__typename) + } + + + const LogicFunctionExecutionResult_possibleTypes: string[] = ['LogicFunctionExecutionResult'] export const isLogicFunctionExecutionResult = (obj?: { __typename?: any } | null): obj is LogicFunctionExecutionResult => { if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionExecutionResult"') @@ -8591,29 +8560,6 @@ export const enumPageLayoutType = { DASHBOARD: 'DASHBOARD' as const } -export const enumNavigationMenuItemType = { - VIEW: 'VIEW' as const, - FOLDER: 'FOLDER' as const, - LINK: 'LINK' as const, - OBJECT: 'OBJECT' as const, - RECORD: 'RECORD' as const -} - -export const enumMetadataEventAction = { - CREATED: 'CREATED' as const, - UPDATED: 'UPDATED' as const, - DELETED: 'DELETED' as const -} - -export const enumDatabaseEventAction = { - CREATED: 'CREATED' as const, - UPDATED: 'UPDATED' as const, - DELETED: 'DELETED' as const, - DESTROYED: 'DESTROYED' as const, - RESTORED: 'RESTORED' as const, - UPSERTED: 'UPSERTED' as const -} - export const enumBillingPlanKey = { PRO: 'PRO' as const, ENTERPRISE: 'ENTERPRISE' as const @@ -8692,6 +8638,29 @@ export const enumRelationType = { MANY_TO_ONE: 'MANY_TO_ONE' as const } +export const enumNavigationMenuItemType = { + VIEW: 'VIEW' as const, + FOLDER: 'FOLDER' as const, + LINK: 'LINK' as const, + OBJECT: 'OBJECT' as const, + RECORD: 'RECORD' as const +} + +export const enumMetadataEventAction = { + CREATED: 'CREATED' as const, + UPDATED: 'UPDATED' as const, + DELETED: 'DELETED' as const +} + +export const enumDatabaseEventAction = { + CREATED: 'CREATED' as const, + UPDATED: 'UPDATED' as const, + DELETED: 'DELETED' as const, + DESTROYED: 'DESTROYED' as const, + RESTORED: 'RESTORED' as const, + UPSERTED: 'UPSERTED' as const +} + export const enumLogicFunctionExecutionStatus = { IDLE: 'IDLE' as const, SUCCESS: 'SUCCESS' as const, diff --git a/packages/twenty-sdk/src/clients/generated/metadata/types.ts b/packages/twenty-sdk/src/clients/generated/metadata/types.ts index 56cf7e885d0..3924df046a1 100644 --- a/packages/twenty-sdk/src/clients/generated/metadata/types.ts +++ b/packages/twenty-sdk/src/clients/generated/metadata/types.ts @@ -38,45 +38,45 @@ export default { 94, 95, 112, - 116, + 117, + 118, 119, 121, 129, - 130, - 131, - 133, - 141, - 156, - 157, - 162, - 166, - 190, - 218, + 145, + 146, + 151, + 155, + 179, + 208, + 211, + 213, + 217, + 225, 226, - 227, - 237, + 236, + 244, 245, - 246, - 251, - 254, + 250, + 253, + 259, 260, - 261, - 263, - 268, + 262, + 267, + 272, 273, - 274, - 286, + 285, + 301, 302, - 303, - 326, + 325, + 332, 333, 334, - 335, - 337, + 336, + 348, 349, - 350, - 456, - 457 + 455, + 456 ], "types": { "BillingProductDTO": { @@ -90,13 +90,13 @@ export default { 1 ], "metadata": [ - 128 + 116 ], "on_BillingLicensedProduct": [ - 137 + 125 ], "on_BillingMeteredProduct": [ - 138 + 126 ], "__typename": [ 1 @@ -780,10 +780,10 @@ export default { 3 ], "relation": [ - 189 + 178 ], "morphRelations": [ - 189 + 178 ], "object": [ 46 @@ -842,7 +842,7 @@ export default { 36 ], "objectMetadata": [ - 177, + 166, { "paging": [ 39, @@ -855,7 +855,7 @@ export default { } ], "indexFieldMetadatas": [ - 175, + 164, { "paging": [ 39, @@ -1100,7 +1100,7 @@ export default { 37 ], "fields": [ - 182, + 171, { "paging": [ 39, @@ -1113,7 +1113,7 @@ export default { } ], "indexMetadatas": [ - 180, + 169, { "paging": [ 39, @@ -1691,16 +1691,16 @@ export default { 49 ], "featureFlags": [ - 165 + 154 ], "billingSubscriptions": [ - 140 + 128 ], "currentBillingSubscription": [ - 140 + 128 ], "billingEntitlements": [ - 161 + 150 ], "hasValidEnterpriseKey": [ 6 @@ -1712,7 +1712,7 @@ export default { 6 ], "workspaceUrls": [ - 154 + 143 ], "workspaceCustomApplicationId": [ 1 @@ -1804,7 +1804,7 @@ export default { 20 ], "deletedWorkspaceMembers": [ - 160 + 149 ], "hasPassword": [ 6 @@ -1816,7 +1816,7 @@ export default { 17 ], "availableWorkspaces": [ - 159 + 148 ], "__typename": [ 1 @@ -2566,201 +2566,6 @@ export default { ] }, "PageLayoutType": {}, - "FileWithSignedUrl": { - "id": [ - 3 - ], - "path": [ - 1 - ], - "size": [ - 11 - ], - "createdAt": [ - 4 - ], - "url": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "RecordIdentifier": { - "id": [ - 3 - ], - "labelIdentifier": [ - 1 - ], - "imageIdentifier": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "NavigationMenuItem": { - "id": [ - 3 - ], - "userWorkspaceId": [ - 3 - ], - "targetRecordId": [ - 3 - ], - "targetObjectMetadataId": [ - 3 - ], - "viewId": [ - 3 - ], - "type": [ - 116 - ], - "name": [ - 1 - ], - "link": [ - 1 - ], - "icon": [ - 1 - ], - "color": [ - 1 - ], - "folderId": [ - 3 - ], - "position": [ - 11 - ], - "applicationId": [ - 3 - ], - "createdAt": [ - 4 - ], - "updatedAt": [ - 4 - ], - "targetRecordIdentifier": [ - 114 - ], - "__typename": [ - 1 - ] - }, - "NavigationMenuItemType": {}, - "ObjectRecordEventProperties": { - "updatedFields": [ - 1 - ], - "before": [ - 15 - ], - "after": [ - 15 - ], - "diff": [ - 15 - ], - "__typename": [ - 1 - ] - }, - "MetadataEvent": { - "type": [ - 119 - ], - "metadataName": [ - 1 - ], - "recordId": [ - 1 - ], - "properties": [ - 117 - ], - "updatedCollectionHash": [ - 1 - ], - "__typename": [ - 1 - ] - }, - "MetadataEventAction": {}, - "ObjectRecordEvent": { - "action": [ - 121 - ], - "objectNameSingular": [ - 1 - ], - "recordId": [ - 1 - ], - "userId": [ - 1 - ], - "workspaceMemberId": [ - 1 - ], - "properties": [ - 117 - ], - "__typename": [ - 1 - ] - }, - "DatabaseEventAction": {}, - "ObjectRecordEventWithQueryIds": { - "queryIds": [ - 1 - ], - "objectRecordEvent": [ - 120 - ], - "__typename": [ - 1 - ] - }, - "EventSubscription": { - "eventStreamId": [ - 1 - ], - "objectRecordEventsWithQueryIds": [ - 122 - ], - "metadataEvents": [ - 118 - ], - "__typename": [ - 1 - ] - }, - "OnDbEvent": { - "action": [ - 121 - ], - "objectNameSingular": [ - 1 - ], - "eventDate": [ - 4 - ], - "record": [ - 15 - ], - "updatedFields": [ - 1 - ], - "__typename": [ - 1 - ] - }, "Analytics": { "success": [ 6 @@ -2788,7 +2593,7 @@ export default { 11 ], "items": [ - 126 + 114 ], "__typename": [ 1 @@ -2796,13 +2601,13 @@ export default { }, "BillingProductMetadata": { "planKey": [ - 129 + 117 ], "priceUsageBased": [ - 130 + 118 ], "productKey": [ - 131 + 119 ], "__typename": [ 1 @@ -2813,7 +2618,7 @@ export default { "BillingProductKey": {}, "BillingPriceLicensed": { "recurringInterval": [ - 133 + 121 ], "unitAmount": [ 11 @@ -2822,7 +2627,7 @@ export default { 1 ], "priceUsageType": [ - 130 + 118 ], "__typename": [ 1 @@ -2845,16 +2650,16 @@ export default { }, "BillingPriceMetered": { "tiers": [ - 134 + 122 ], "recurringInterval": [ - 133 + 121 ], "stripePriceId": [ 1 ], "priceUsageType": [ - 130 + 118 ], "__typename": [ 1 @@ -2871,7 +2676,7 @@ export default { 1 ], "metadata": [ - 128 + 116 ], "__typename": [ 1 @@ -2888,10 +2693,10 @@ export default { 1 ], "metadata": [ - 128 + 116 ], "prices": [ - 132 + 120 ], "__typename": [ 1 @@ -2908,10 +2713,10 @@ export default { 1 ], "metadata": [ - 128 + 116 ], "prices": [ - 135 + 123 ], "__typename": [ 1 @@ -2942,13 +2747,13 @@ export default { 3 ], "status": [ - 141 + 129 ], "interval": [ - 133 + 121 ], "billingSubscriptionItems": [ - 139 + 127 ], "currentPeriodEnd": [ 4 @@ -2957,7 +2762,7 @@ export default { 15 ], "phases": [ - 127 + 115 ], "__typename": [ 1 @@ -2966,7 +2771,7 @@ export default { "SubscriptionStatus": {}, "BillingEndTrialPeriod": { "status": [ - 141 + 129 ], "hasPaymentMethod": [ 6 @@ -2980,7 +2785,7 @@ export default { }, "BillingMeteredProductUsage": { "productKey": [ - 131 + 119 ], "periodStart": [ 4 @@ -3009,13 +2814,13 @@ export default { }, "BillingPlan": { "planKey": [ - 129 + 117 ], "licensedProducts": [ - 137 + 125 ], "meteredProducts": [ - 138 + 126 ], "__typename": [ 1 @@ -3031,10 +2836,10 @@ export default { }, "BillingUpdate": { "currentBillingSubscription": [ - 140 + 128 ], "billingSubscriptions": [ - 140 + 128 ], "__typename": [ 1 @@ -3105,6 +2910,26 @@ export default { 1 ] }, + "FileWithSignedUrl": { + "id": [ + 3 + ], + "path": [ + 1 + ], + "size": [ + 11 + ], + "createdAt": [ + 4 + ], + "url": [ + 1 + ], + "__typename": [ + 1 + ] + }, "WorkspaceInvitation": { "id": [ 3 @@ -3130,7 +2955,7 @@ export default { 1 ], "result": [ - 151 + 140 ], "__typename": [ 1 @@ -3157,7 +2982,7 @@ export default { }, "SSOConnection": { "type": [ - 156 + 145 ], "id": [ 3 @@ -3169,7 +2994,7 @@ export default { 1 ], "status": [ - 157 + 146 ], "__typename": [ 1 @@ -3194,13 +3019,13 @@ export default { 1 ], "workspaceUrls": [ - 154 + 143 ], "logo": [ 1 ], "sso": [ - 155 + 144 ], "__typename": [ 1 @@ -3208,10 +3033,10 @@ export default { }, "AvailableWorkspaces": { "availableWorkspacesForSignIn": [ - 158 + 147 ], "availableWorkspacesForSignUp": [ - 158 + 147 ], "__typename": [ 1 @@ -3239,7 +3064,7 @@ export default { }, "BillingEntitlement": { "key": [ - 162 + 151 ], "value": [ 6 @@ -3277,7 +3102,7 @@ export default { 1 ], "records": [ - 163 + 152 ], "__typename": [ 1 @@ -3285,7 +3110,7 @@ export default { }, "FeatureFlag": { "key": [ - 166 + 155 ], "value": [ 6 @@ -3303,10 +3128,10 @@ export default { 1 ], "type": [ - 156 + 145 ], "status": [ - 157 + 146 ], "issuer": [ 1 @@ -3317,7 +3142,7 @@ export default { }, "AuthProviders": { "sso": [ - 167 + 156 ], "google": [ 6 @@ -3354,10 +3179,10 @@ export default { 3 ], "authProviders": [ - 168 + 157 ], "authBypassProviders": [ - 169 + 158 ], "logo": [ 1 @@ -3366,7 +3191,7 @@ export default { 1 ], "workspaceUrls": [ - 154 + 143 ], "__typename": [ 1 @@ -3402,10 +3227,10 @@ export default { }, "IndexConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 171 + 160 ], "__typename": [ 1 @@ -3424,10 +3249,10 @@ export default { }, "IndexIndexFieldMetadatasConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 174 + 163 ], "__typename": [ 1 @@ -3446,10 +3271,10 @@ export default { }, "IndexObjectMetadataConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 176 + 165 ], "__typename": [ 1 @@ -3468,10 +3293,10 @@ export default { }, "ObjectConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 176 + 165 ], "__typename": [ 1 @@ -3479,10 +3304,10 @@ export default { }, "ObjectIndexMetadatasConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 171 + 160 ], "__typename": [ 1 @@ -3501,10 +3326,10 @@ export default { }, "ObjectFieldsConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 181 + 170 ], "__typename": [ 1 @@ -3540,7 +3365,7 @@ export default { 1 ], "versionDistribution": [ - 184 + 173 ], "__typename": [ 1 @@ -3587,7 +3412,7 @@ export default { }, "Relation": { "type": [ - 190 + 179 ], "sourceObjectMetadata": [ 46 @@ -3608,10 +3433,10 @@ export default { "RelationType": {}, "FieldConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 181 + 170 ], "__typename": [ 1 @@ -3630,7 +3455,7 @@ export default { 3 ], "type": [ - 156 + 145 ], "issuer": [ 1 @@ -3639,7 +3464,7 @@ export default { 1 ], "status": [ - 157 + 146 ], "__typename": [ 1 @@ -3658,7 +3483,7 @@ export default { }, "FindAvailableSSOIDP": { "type": [ - 156 + 145 ], "id": [ 3 @@ -3670,10 +3495,10 @@ export default { 1 ], "status": [ - 157 + 146 ], "workspace": [ - 194 + 183 ], "__typename": [ 1 @@ -3684,7 +3509,7 @@ export default { 3 ], "type": [ - 156 + 145 ], "issuer": [ 1 @@ -3693,7 +3518,7 @@ export default { 1 ], "status": [ - 157 + 146 ], "__typename": [ 1 @@ -3744,10 +3569,10 @@ export default { }, "AuthTokenPair": { "accessOrWorkspaceAgnosticToken": [ - 201 + 190 ], "refreshToken": [ - 201 + 190 ], "__typename": [ 1 @@ -3755,10 +3580,10 @@ export default { }, "AvailableWorkspacesAndAccessTokens": { "tokens": [ - 202 + 191 ], "availableWorkspaces": [ - 159 + 148 ], "__typename": [ 1 @@ -3796,7 +3621,7 @@ export default { }, "WorkspaceUrlsAndId": { "workspaceUrls": [ - 154 + 143 ], "id": [ 3 @@ -3807,10 +3632,10 @@ export default { }, "SignUp": { "loginToken": [ - 201 + 190 ], "workspace": [ - 207 + 196 ], "__typename": [ 1 @@ -3818,7 +3643,7 @@ export default { }, "TransientToken": { "transientToken": [ - 201 + 190 ], "__typename": [ 1 @@ -3840,10 +3665,10 @@ export default { }, "VerifyEmailAndGetLoginToken": { "loginToken": [ - 201 + 190 ], "workspaceUrls": [ - 154 + 143 ], "__typename": [ 1 @@ -3859,7 +3684,7 @@ export default { }, "AuthTokens": { "tokens": [ - 202 + 191 ], "__typename": [ 1 @@ -3867,7 +3692,7 @@ export default { }, "LoginToken": { "loginToken": [ - 201 + 190 ], "__typename": [ 1 @@ -3895,6 +3720,161 @@ export default { 1 ] }, + "RecordIdentifier": { + "id": [ + 3 + ], + "labelIdentifier": [ + 1 + ], + "imageIdentifier": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "NavigationMenuItem": { + "id": [ + 3 + ], + "userWorkspaceId": [ + 3 + ], + "targetRecordId": [ + 3 + ], + "targetObjectMetadataId": [ + 3 + ], + "viewId": [ + 3 + ], + "type": [ + 208 + ], + "name": [ + 1 + ], + "link": [ + 1 + ], + "icon": [ + 1 + ], + "color": [ + 1 + ], + "folderId": [ + 3 + ], + "position": [ + 11 + ], + "applicationId": [ + 3 + ], + "createdAt": [ + 4 + ], + "updatedAt": [ + 4 + ], + "targetRecordIdentifier": [ + 206 + ], + "__typename": [ + 1 + ] + }, + "NavigationMenuItemType": {}, + "ObjectRecordEventProperties": { + "updatedFields": [ + 1 + ], + "before": [ + 15 + ], + "after": [ + 15 + ], + "diff": [ + 15 + ], + "__typename": [ + 1 + ] + }, + "MetadataEvent": { + "type": [ + 211 + ], + "metadataName": [ + 1 + ], + "recordId": [ + 1 + ], + "properties": [ + 209 + ], + "updatedCollectionHash": [ + 1 + ], + "__typename": [ + 1 + ] + }, + "MetadataEventAction": {}, + "ObjectRecordEvent": { + "action": [ + 213 + ], + "objectNameSingular": [ + 1 + ], + "recordId": [ + 1 + ], + "userId": [ + 1 + ], + "workspaceMemberId": [ + 1 + ], + "properties": [ + 209 + ], + "__typename": [ + 1 + ] + }, + "DatabaseEventAction": {}, + "ObjectRecordEventWithQueryIds": { + "queryIds": [ + 1 + ], + "objectRecordEvent": [ + 212 + ], + "__typename": [ + 1 + ] + }, + "EventSubscription": { + "eventStreamId": [ + 1 + ], + "objectRecordEventsWithQueryIds": [ + 214 + ], + "metadataEvents": [ + 210 + ], + "__typename": [ + 1 + ] + }, "LogicFunctionExecutionResult": { "data": [ 15 @@ -3906,7 +3886,7 @@ export default { 11 ], "status": [ - 218 + 217 ], "error": [ 15 @@ -4067,10 +4047,10 @@ export default { }, "ApplicationTokenPair": { "applicationAccessToken": [ - 201 + 190 ], "applicationRefreshToken": [ - 201 + 190 ], "__typename": [ 1 @@ -4114,7 +4094,7 @@ export default { 6 ], "applicationTokenPair": [ - 223 + 222 ], "__typename": [ 1 @@ -4131,10 +4111,10 @@ export default { 3 ], "frontComponent": [ - 224 + 223 ], "engineComponentKey": [ - 226 + 225 ], "label": [ 1 @@ -4152,7 +4132,7 @@ export default { 6 ], "availabilityType": [ - 227 + 226 ], "hotKeys": [ 1 @@ -4230,7 +4210,7 @@ export default { 1 ], "parts": [ - 221 + 220 ], "createdAt": [ 4 @@ -4255,7 +4235,7 @@ export default { }, "AISystemPromptPreview": { "sections": [ - 230 + 229 ], "estimatedTokenCount": [ 21 @@ -4266,7 +4246,7 @@ export default { }, "AgentChatThreadEdge": { "node": [ - 228 + 227 ], "cursor": [ 40 @@ -4277,10 +4257,10 @@ export default { }, "AgentChatThreadConnection": { "pageInfo": [ - 172 + 161 ], "edges": [ - 232 + 231 ], "__typename": [ 1 @@ -4317,10 +4297,10 @@ export default { 3 ], "evaluations": [ - 234 + 233 ], "messages": [ - 229 + 228 ], "createdAt": [ 4 @@ -4331,7 +4311,7 @@ export default { }, "CollectionHash": { "collectionName": [ - 237 + 236 ], "hash": [ 1 @@ -4398,13 +4378,13 @@ export default { }, "MinimalMetadata": { "objectMetadataItems": [ - 238 + 237 ], "views": [ - 239 + 238 ], "collectionHashes": [ - 236 + 235 ], "__typename": [ 1 @@ -4472,10 +4452,10 @@ export default { 1 ], "modelFamily": [ - 245 + 244 ], "inferenceProvider": [ - 246 + 245 ], "inputCostPerMillionTokensInCredits": [ 11 @@ -4484,7 +4464,7 @@ export default { 11 ], "nativeCapabilities": [ - 243 + 242 ], "deprecated": [ 6 @@ -4506,10 +4486,10 @@ export default { 1 ], "modelFamily": [ - 245 + 244 ], "inferenceProvider": [ - 246 + 245 ], "isAvailable": [ 6 @@ -4532,7 +4512,7 @@ export default { 6 ], "models": [ - 247 + 246 ], "__typename": [ 1 @@ -4546,7 +4526,7 @@ export default { 1 ], "trialPeriods": [ - 242 + 241 ], "__typename": [ 1 @@ -4554,7 +4534,7 @@ export default { }, "Support": { "supportDriver": [ - 251 + 250 ], "supportFrontChatId": [ 1 @@ -4580,7 +4560,7 @@ export default { }, "Captcha": { "provider": [ - 254 + 253 ], "siteKey": [ 1 @@ -4614,10 +4594,10 @@ export default { }, "PublicFeatureFlag": { "key": [ - 166 + 155 ], "metadata": [ - 256 + 255 ], "__typename": [ 1 @@ -4628,13 +4608,13 @@ export default { 1 ], "authProviders": [ - 168 + 157 ], "billing": [ - 249 + 248 ], "aiModels": [ - 244 + 243 ], "signInPrefilled": [ 6 @@ -4655,25 +4635,25 @@ export default { 6 ], "support": [ - 250 + 249 ], "isAttachmentPreviewEnabled": [ 6 ], "sentry": [ - 252 + 251 ], "captcha": [ - 253 + 252 ], "api": [ - 255 + 254 ], "canManageFeatureFlags": [ 6 ], "publicFeatureFlags": [ - 257 + 256 ], "isMicrosoftMessagingEnabled": [ 6 @@ -4723,13 +4703,13 @@ export default { 6 ], "source": [ - 260 + 259 ], "isEnvOnly": [ 6 ], "type": [ - 261 + 260 ], "options": [ 15 @@ -4742,10 +4722,10 @@ export default { "ConfigVariableType": {}, "ConfigVariablesGroupData": { "variables": [ - 259 + 258 ], "name": [ - 263 + 262 ], "description": [ 1 @@ -4760,7 +4740,7 @@ export default { "ConfigVariablesGroup": {}, "ConfigVariables": { "groups": [ - 262 + 261 ], "__typename": [ 1 @@ -4785,7 +4765,7 @@ export default { 21 ], "results": [ - 265 + 264 ], "__typename": [ 1 @@ -4802,7 +4782,7 @@ export default { 15 ], "state": [ - 268 + 267 ], "timestamp": [ 11 @@ -4852,7 +4832,7 @@ export default { }, "QueueJobsResponse": { "jobs": [ - 267 + 266 ], "count": [ 11 @@ -4864,7 +4844,7 @@ export default { 6 ], "retentionConfig": [ - 269 + 268 ], "__typename": [ 1 @@ -4875,7 +4855,7 @@ export default { 21 ], "results": [ - 265 + 264 ], "__typename": [ 1 @@ -4883,13 +4863,13 @@ export default { }, "SystemHealthService": { "id": [ - 273 + 272 ], "label": [ 1 ], "status": [ - 274 + 273 ], "__typename": [ 1 @@ -4899,7 +4879,7 @@ export default { "AdminPanelHealthServiceStatus": {}, "SystemHealth": { "services": [ - 272 + 271 ], "__typename": [ 1 @@ -4939,13 +4919,13 @@ export default { 11 ], "workspaceUrls": [ - 154 + 143 ], "users": [ - 276 + 275 ], "featureFlags": [ - 165 + 154 ], "__typename": [ 1 @@ -4953,10 +4933,10 @@ export default { }, "UserLookup": { "user": [ - 276 + 275 ], "workspaces": [ - 277 + 276 ], "__typename": [ 1 @@ -4981,7 +4961,7 @@ export default { 1 ], "status": [ - 274 + 273 ], "__typename": [ 1 @@ -4989,7 +4969,7 @@ export default { }, "AdminPanelHealthServiceData": { "id": [ - 273 + 272 ], "label": [ 1 @@ -4998,7 +4978,7 @@ export default { 1 ], "status": [ - 274 + 273 ], "errorMessage": [ 1 @@ -5007,7 +4987,7 @@ export default { 1 ], "queues": [ - 280 + 279 ], "__typename": [ 1 @@ -5029,7 +5009,7 @@ export default { 1 ], "data": [ - 282 + 281 ], "__typename": [ 1 @@ -5072,13 +5052,13 @@ export default { 11 ], "timeRange": [ - 286 + 285 ], "details": [ - 284 + 283 ], "data": [ - 283 + 282 ], "__typename": [ 1 @@ -5087,10 +5067,10 @@ export default { "QueueMetricsTimeRange": {}, "Impersonate": { "loginToken": [ - 201 + 190 ], "workspace": [ - 207 + 196 ], "__typename": [ 1 @@ -5184,7 +5164,7 @@ export default { 1 ], "fields": [ - 291 + 290 ], "__typename": [ 1 @@ -5281,10 +5261,10 @@ export default { 6 ], "objectPermissions": [ - 295 + 294 ], "fieldPermissions": [ - 296 + 295 ], "permissionFlags": [ 1 @@ -5334,19 +5314,19 @@ export default { 1 ], "objects": [ - 292 - ], - "fields": [ 291 ], + "fields": [ + 290 + ], "logicFunctions": [ - 293 + 292 ], "frontComponents": [ - 294 + 293 ], "defaultRole": [ - 297 + 296 ], "sourcePackage": [ 1 @@ -5406,13 +5386,13 @@ export default { 1 ], "driver": [ - 302 + 301 ], "status": [ - 303 + 302 ], "verificationRecords": [ - 300 + 299 ], "verifiedAt": [ 4 @@ -5459,7 +5439,7 @@ export default { 1 ], "location": [ - 305 + 304 ], "__typename": [ 1 @@ -5487,13 +5467,13 @@ export default { }, "ImapSmtpCaldavConnectionParameters": { "IMAP": [ - 307 + 306 ], "SMTP": [ - 307 + 306 ], "CALDAV": [ - 307 + 306 ], "__typename": [ 1 @@ -5513,7 +5493,7 @@ export default { 3 ], "connectionParameters": [ - 308 + 307 ], "__typename": [ 1 @@ -5577,7 +5557,7 @@ export default { 1 ], "series": [ - 313 + 312 ], "xAxisLabel": [ 1 @@ -5626,7 +5606,7 @@ export default { 1 ], "data": [ - 315 + 314 ], "__typename": [ 1 @@ -5634,7 +5614,7 @@ export default { }, "LineChartData": { "series": [ - 316 + 315 ], "xAxisLabel": [ 1 @@ -5671,7 +5651,7 @@ export default { }, "PieChartData": { "data": [ - 318 + 317 ], "showLegend": [ 6 @@ -5754,13 +5734,13 @@ export default { }, "EventLogQueryResult": { "records": [ - 321 + 320 ], "totalCount": [ 21 ], "pageInfo": [ - 322 + 321 ], "__typename": [ 1 @@ -5768,10 +5748,10 @@ export default { }, "Query": { "navigationMenuItems": [ - 115 + 207 ], "navigationMenuItem": [ - 115, + 207, { "id": [ 3, @@ -5839,7 +5819,7 @@ export default { 32, { "input": [ - 325, + 324, "LogicFunctionIdInput!" ] } @@ -5851,7 +5831,7 @@ export default { 15, { "input": [ - 325, + 324, "LogicFunctionIdInput!" ] } @@ -5860,13 +5840,13 @@ export default { 1, { "input": [ - 325, + 324, "LogicFunctionIdInput!" ] } ], "objectRecordCounts": [ - 178 + 167 ], "object": [ 46, @@ -5878,7 +5858,7 @@ export default { } ], "objects": [ - 179, + 168, { "paging": [ 39, @@ -5974,7 +5954,7 @@ export default { } ], "indexMetadatas": [ - 173, + 162, { "paging": [ 39, @@ -5987,10 +5967,10 @@ export default { } ], "commandMenuItems": [ - 225 + 224 ], "commandMenuItem": [ - 225, + 224, { "id": [ 3, @@ -5999,10 +5979,10 @@ export default { } ], "frontComponents": [ - 224 + 223 ], "frontComponent": [ - 224, + 223, { "id": [ 3, @@ -6017,13 +5997,13 @@ export default { 25, { "input": [ - 327, + 326, "AgentIdInput!" ] } ], "billingPortalSession": [ - 145, + 133, { "returnUrlPath": [ 1 @@ -6031,10 +6011,10 @@ export default { } ], "listPlans": [ - 144 + 132 ], "getMeteredProductsUsage": [ - 143 + 131 ], "enterprisePortalSession": [ 1, @@ -6053,16 +6033,16 @@ export default { } ], "enterpriseSubscriptionStatus": [ - 148 + 136 ], "getRoles": [ 29 ], "findWorkspaceInvitations": [ - 151 + 140 ], "getApprovedAccessDomains": [ - 150 + 138 ], "apiKeys": [ 2 @@ -6071,13 +6051,13 @@ export default { 2, { "input": [ - 328, + 327, "GetApiKeyInput!" ] } ], "getToolIndex": [ - 220 + 219 ], "getToolInputSchema": [ 15, @@ -6098,7 +6078,7 @@ export default { } ], "fields": [ - 191, + 180, { "paging": [ 39, @@ -6168,7 +6148,7 @@ export default { 66 ], "getPublicWorkspaceDataByDomain": [ - 170, + 159, { "origin": [ 1 @@ -6176,7 +6156,7 @@ export default { } ], "findApplicationRegistrationByClientId": [ - 187, + 176, { "clientId": [ 1, @@ -6206,7 +6186,7 @@ export default { } ], "findApplicationRegistrationStats": [ - 185, + 174, { "id": [ 1, @@ -6233,7 +6213,7 @@ export default { } ], "checkUserExists": [ - 215, + 204, { "email": [ 1, @@ -6245,7 +6225,7 @@ export default { } ], "checkWorkspaceInviteHashIsValid": [ - 216, + 205, { "inviteHash": [ 1, @@ -6263,7 +6243,7 @@ export default { } ], "validatePasswordResetToken": [ - 210, + 199, { "passwordResetToken": [ 1, @@ -6272,13 +6252,13 @@ export default { } ], "getSSOIdentityProviders": [ - 195 + 184 ], "webhooks": [ - 241 + 240 ], "webhook": [ - 241, + 240, { "id": [ 3, @@ -6287,10 +6267,10 @@ export default { } ], "minimalMetadata": [ - 240 + 239 ], "chatThread": [ - 228, + 227, { "id": [ 3, @@ -6299,7 +6279,7 @@ export default { } ], "chatMessages": [ - 229, + 228, { "threadId": [ 3, @@ -6308,13 +6288,13 @@ export default { } ], "getAISystemPromptPreview": [ - 231 + 230 ], "skills": [ - 222 + 221 ], "skill": [ - 222, + 221, { "id": [ 3, @@ -6323,24 +6303,24 @@ export default { } ], "chatThreads": [ - 233, + 232, { "paging": [ 39, "CursorPaging!" ], "filter": [ - 329, + 328, "AgentChatThreadFilter!" ], "sorting": [ - 332, + 331, "[AgentChatThreadSort!]!" ] } ], "agentTurns": [ - 235, + 234, { "agentId": [ 3, @@ -6349,43 +6329,43 @@ export default { } ], "eventLogs": [ - 323, + 322, { "input": [ - 336, + 335, "EventLogQueryInput!" ] } ], "pieChartData": [ - 319, + 318, { "input": [ - 340, + 339, "PieChartDataInput!" ] } ], "lineChartData": [ - 317, + 316, { "input": [ - 341, + 340, "LineChartDataInput!" ] } ], "barChartData": [ - 314, + 313, { "input": [ - 342, + 341, "BarChartDataInput!" ] } ], "getConnectedImapSmtpCaldavAccount": [ - 309, + 308, { "id": [ 3, @@ -6394,7 +6374,7 @@ export default { } ], "getAutoCompleteAddress": [ - 304, + 303, { "address": [ 1, @@ -6413,7 +6393,7 @@ export default { } ], "getAddressDetails": [ - 306, + 305, { "placeId": [ 1, @@ -6426,40 +6406,40 @@ export default { } ], "getConfigVariablesGrouped": [ - 264 + 263 ], "getSystemHealthStatus": [ - 275 + 274 ], "getIndicatorHealthStatus": [ - 281, + 280, { "indicatorId": [ - 273, + 272, "HealthIndicatorId!" ] } ], "getQueueMetrics": [ - 285, + 284, { "queueName": [ 1, "String!" ], "timeRange": [ - 286 + 285 ] } ], "versionInfo": [ - 279 + 278 ], "getAdminAiModels": [ - 248 + 247 ], "getDatabaseConfigVariable": [ - 259, + 258, { "key": [ 1, @@ -6468,14 +6448,14 @@ export default { } ], "getQueueJobs": [ - 270, + 269, { "queueName": [ 1, "String!" ], "state": [ - 268, + 267, "JobState!" ], "limit": [ @@ -6490,19 +6470,19 @@ export default { 7 ], "getPostgresCredentials": [ - 311 + 310 ], "findManyPublicDomains": [ - 299 - ], - "getEmailingDomains": [ - 301 - ], - "findManyMarketplaceApps": [ 298 ], + "getEmailingDomains": [ + 300 + ], + "findManyMarketplaceApps": [ + 297 + ], "findOneMarketplaceApp": [ - 298, + 297, { "universalIdentifier": [ 1, @@ -6530,7 +6510,7 @@ export default { }, "LogicFunctionIdInput": { "id": [ - 326 + 325 ], "__typename": [ 1 @@ -6555,16 +6535,16 @@ export default { }, "AgentChatThreadFilter": { "and": [ - 329 + 328 ], "or": [ - 329 + 328 ], "id": [ 42 ], "updatedAt": [ - 330 + 329 ], "__typename": [ 1 @@ -6602,10 +6582,10 @@ export default { 4 ], "between": [ - 331 + 330 ], "notBetween": [ - 331 + 330 ], "__typename": [ 1 @@ -6624,13 +6604,13 @@ export default { }, "AgentChatThreadSort": { "field": [ - 333 + 332 ], "direction": [ - 334 + 333 ], "nulls": [ - 335 + 334 ], "__typename": [ 1 @@ -6641,10 +6621,10 @@ export default { "SortNulls": {}, "EventLogQueryInput": { "table": [ - 337 + 336 ], "filters": [ - 338 + 337 ], "first": [ 21 @@ -6665,7 +6645,7 @@ export default { 1 ], "dateRange": [ - 339 + 338 ], "recordId": [ 1 @@ -6726,7 +6706,7 @@ export default { 6, { "input": [ - 344, + 343, "AddQuerySubscriptionInput!" ] } @@ -6735,31 +6715,31 @@ export default { 6, { "input": [ - 345, + 344, "RemoveQueryFromEventStreamInput!" ] } ], "createNavigationMenuItem": [ - 115, + 207, { "input": [ - 346, + 345, "CreateNavigationMenuItemInput!" ] } ], "updateNavigationMenuItem": [ - 115, + 207, { "input": [ - 347, + 346, "UpdateOneNavigationMenuItemInput!" ] } ], "deleteNavigationMenuItem": [ - 115, + 207, { "id": [ 3, @@ -6768,46 +6748,46 @@ export default { } ], "uploadAIChatFile": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ] } ], "uploadWorkflowFile": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ] } ], "uploadWorkspaceLogo": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ] } ], "uploadWorkspaceMemberProfilePicture": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ] } ], "uploadFilesFieldFile": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ], "fieldMetadataId": [ @@ -6817,10 +6797,10 @@ export default { } ], "uploadFilesFieldFileByUniversalIdentifier": [ - 113, + 139, { "file": [ - 349, + 348, "Upload!" ], "fieldMetadataUniversalIdentifier": [ @@ -6830,7 +6810,7 @@ export default { } ], "createObjectEvent": [ - 125, + 113, { "event": [ 1, @@ -6850,10 +6830,10 @@ export default { } ], "trackAnalytics": [ - 125, + 113, { "type": [ - 350, + 349, "AnalyticsType!" ], "name": [ @@ -6871,7 +6851,7 @@ export default { 75, { "input": [ - 351, + 350, "CreatePageLayoutWidgetInput!" ] } @@ -6884,7 +6864,7 @@ export default { "String!" ], "input": [ - 353, + 352, "UpdatePageLayoutWidgetInput!" ] } @@ -6902,7 +6882,7 @@ export default { 110, { "input": [ - 354, + 353, "CreatePageLayoutTabInput!" ] } @@ -6915,7 +6895,7 @@ export default { "String!" ], "input": [ - 355, + 354, "UpdatePageLayoutTabInput!" ] } @@ -6933,7 +6913,7 @@ export default { 111, { "input": [ - 356, + 355, "CreatePageLayoutInput!" ] } @@ -6946,7 +6926,7 @@ export default { "String!" ], "input": [ - 357, + 356, "UpdatePageLayoutInput!" ] } @@ -6968,7 +6948,7 @@ export default { "String!" ], "input": [ - 358, + 357, "UpdatePageLayoutWithTabsInput!" ] } @@ -6977,7 +6957,7 @@ export default { 32, { "input": [ - 325, + 324, "LogicFunctionIdInput!" ] } @@ -6986,16 +6966,16 @@ export default { 32, { "input": [ - 361, + 360, "CreateLogicFunctionFromSourceInput!" ] } ], "executeOneLogicFunction": [ - 217, + 216, { "input": [ - 362, + 361, "ExecuteOneLogicFunctionInput!" ] } @@ -7004,7 +6984,7 @@ export default { 6, { "input": [ - 363, + 362, "UpdateLogicFunctionFromSourceInput!" ] } @@ -7013,7 +6993,7 @@ export default { 46, { "input": [ - 365, + 364, "CreateOneObjectInput!" ] } @@ -7022,7 +7002,7 @@ export default { 46, { "input": [ - 367, + 366, "DeleteOneObjectInput!" ] } @@ -7031,7 +7011,7 @@ export default { 46, { "input": [ - 368, + 367, "UpdateOneObjectInput!" ] } @@ -7040,7 +7020,7 @@ export default { 50, { "input": [ - 370, + 369, "UpdateViewFieldInput!" ] } @@ -7049,7 +7029,7 @@ export default { 50, { "input": [ - 372, + 371, "CreateViewFieldInput!" ] } @@ -7058,7 +7038,7 @@ export default { 50, { "inputs": [ - 372, + 371, "[CreateViewFieldInput!]!" ] } @@ -7067,7 +7047,7 @@ export default { 50, { "input": [ - 373, + 372, "DeleteViewFieldInput!" ] } @@ -7076,7 +7056,7 @@ export default { 50, { "input": [ - 374, + 373, "DestroyViewFieldInput!" ] } @@ -7085,7 +7065,7 @@ export default { 60, { "input": [ - 375, + 374, "CreateViewInput!" ] } @@ -7098,7 +7078,7 @@ export default { "String!" ], "input": [ - 376, + 375, "UpdateViewInput!" ] } @@ -7125,7 +7105,7 @@ export default { 57, { "input": [ - 377, + 376, "CreateViewSortInput!" ] } @@ -7134,7 +7114,7 @@ export default { 57, { "input": [ - 378, + 377, "UpdateViewSortInput!" ] } @@ -7143,7 +7123,7 @@ export default { 6, { "input": [ - 380, + 379, "DeleteViewSortInput!" ] } @@ -7152,7 +7132,7 @@ export default { 6, { "input": [ - 381, + 380, "DestroyViewSortInput!" ] } @@ -7161,7 +7141,7 @@ export default { 59, { "input": [ - 382, + 381, "UpdateViewFieldGroupInput!" ] } @@ -7170,7 +7150,7 @@ export default { 59, { "input": [ - 384, + 383, "CreateViewFieldGroupInput!" ] } @@ -7179,7 +7159,7 @@ export default { 59, { "inputs": [ - 384, + 383, "[CreateViewFieldGroupInput!]!" ] } @@ -7188,7 +7168,7 @@ export default { 59, { "input": [ - 385, + 384, "DeleteViewFieldGroupInput!" ] } @@ -7197,7 +7177,7 @@ export default { 59, { "input": [ - 386, + 385, "DestroyViewFieldGroupInput!" ] } @@ -7206,31 +7186,31 @@ export default { 60, { "input": [ - 387, + 386, "UpsertFieldsWidgetInput!" ] } ], "createCommandMenuItem": [ - 225, + 224, { "input": [ - 390, + 389, "CreateCommandMenuItemInput!" ] } ], "updateCommandMenuItem": [ - 225, + 224, { "input": [ - 391, + 390, "UpdateCommandMenuItemInput!" ] } ], "deleteCommandMenuItem": [ - 225, + 224, { "id": [ 3, @@ -7239,25 +7219,25 @@ export default { } ], "createFrontComponent": [ - 224, + 223, { "input": [ - 392, + 391, "CreateFrontComponentInput!" ] } ], "updateFrontComponent": [ - 224, + 223, { "input": [ - 393, + 392, "UpdateFrontComponentInput!" ] } ], "deleteFrontComponent": [ - 224, + 223, { "id": [ 3, @@ -7269,7 +7249,7 @@ export default { 25, { "input": [ - 395, + 394, "CreateAgentInput!" ] } @@ -7278,7 +7258,7 @@ export default { 25, { "input": [ - 396, + 395, "UpdateAgentInput!" ] } @@ -7287,20 +7267,20 @@ export default { 25, { "input": [ - 327, + 326, "AgentIdInput!" ] } ], "checkoutSession": [ - 145, + 133, { "recurringInterval": [ - 133, + 121, "SubscriptionInterval!" ], "plan": [ - 129, + 117, "BillingPlanKey!" ], "requirePaymentMethod": [ @@ -7313,19 +7293,19 @@ export default { } ], "switchSubscriptionInterval": [ - 146 + 134 ], "switchBillingPlan": [ - 146 + 134 ], "cancelSwitchBillingPlan": [ - 146 + 134 ], "cancelSwitchBillingInterval": [ - 146 + 134 ], "setMeteredSubscriptionPrice": [ - 146, + 134, { "priceId": [ 1, @@ -7334,16 +7314,16 @@ export default { } ], "endSubscriptionTrialPeriod": [ - 142 + 130 ], "cancelSwitchMeteredPrice": [ - 146 + 134 ], "refreshEnterpriseValidityToken": [ 6 ], "setEnterpriseKey": [ - 147, + 135, { "enterpriseKey": [ 1, @@ -7368,7 +7348,7 @@ export default { 29, { "createRoleInput": [ - 397, + 396, "CreateRoleInput!" ] } @@ -7377,7 +7357,7 @@ export default { 29, { "updateRoleInput": [ - 398, + 397, "UpdateRoleInput!" ] } @@ -7395,7 +7375,7 @@ export default { 16, { "upsertObjectPermissionsInput": [ - 400, + 399, "UpsertObjectPermissionsInput!" ] } @@ -7404,7 +7384,7 @@ export default { 27, { "upsertPermissionFlagsInput": [ - 402, + 401, "UpsertPermissionFlagsInput!" ] } @@ -7413,16 +7393,16 @@ export default { 26, { "upsertFieldPermissionsInput": [ - 403, + 402, "UpsertFieldPermissionsInput!" ] } ], "upsertRowLevelPermissionPredicates": [ - 183, + 172, { "input": [ - 405, + 404, "UpsertRowLevelPermissionPredicatesInput!" ] } @@ -7450,10 +7430,10 @@ export default { } ], "skipSyncEmailOnboardingStep": [ - 149 + 137 ], "skipBookOnboardingStep": [ - 149 + 137 ], "deleteWorkspaceInvitation": [ 1, @@ -7465,7 +7445,7 @@ export default { } ], "resendWorkspaceInvitation": [ - 152, + 141, { "appTokenId": [ 1, @@ -7474,7 +7454,7 @@ export default { } ], "sendInvitations": [ - 152, + 141, { "emails": [ 1, @@ -7486,10 +7466,10 @@ export default { } ], "createApprovedAccessDomain": [ - 150, + 138, { "input": [ - 408, + 407, "CreateApprovedAccessDomainInput!" ] } @@ -7498,16 +7478,16 @@ export default { 6, { "input": [ - 409, + 408, "DeleteApprovedAccessDomainInput!" ] } ], "validateApprovedAccessDomain": [ - 150, + 138, { "input": [ - 410, + 409, "ValidateApprovedAccessDomainInput!" ] } @@ -7516,7 +7496,7 @@ export default { 2, { "input": [ - 411, + 410, "CreateApiKeyInput!" ] } @@ -7525,7 +7505,7 @@ export default { 2, { "input": [ - 412, + 411, "UpdateApiKeyInput!" ] } @@ -7534,7 +7514,7 @@ export default { 2, { "input": [ - 413, + 412, "RevokeApiKeyInput!" ] } @@ -7556,7 +7536,7 @@ export default { 34, { "input": [ - 414, + 413, "CreateOneFieldMetadataInput!" ] } @@ -7565,7 +7545,7 @@ export default { 34, { "input": [ - 416, + 415, "UpdateOneFieldMetadataInput!" ] } @@ -7574,7 +7554,7 @@ export default { 34, { "input": [ - 418, + 417, "DeleteOneFieldInput!" ] } @@ -7583,7 +7563,7 @@ export default { 56, { "input": [ - 419, + 418, "CreateViewGroupInput!" ] } @@ -7592,7 +7572,7 @@ export default { 56, { "inputs": [ - 419, + 418, "[CreateViewGroupInput!]!" ] } @@ -7601,7 +7581,7 @@ export default { 56, { "input": [ - 420, + 419, "UpdateViewGroupInput!" ] } @@ -7610,7 +7590,7 @@ export default { 56, { "input": [ - 422, + 421, "DeleteViewGroupInput!" ] } @@ -7619,7 +7599,7 @@ export default { 56, { "input": [ - 423, + 422, "DestroyViewGroupInput!" ] } @@ -7628,7 +7608,7 @@ export default { 54, { "input": [ - 424, + 423, "CreateViewFilterInput!" ] } @@ -7637,7 +7617,7 @@ export default { 54, { "input": [ - 425, + 424, "UpdateViewFilterInput!" ] } @@ -7646,7 +7626,7 @@ export default { 54, { "input": [ - 427, + 426, "DeleteViewFilterInput!" ] } @@ -7655,7 +7635,7 @@ export default { 54, { "input": [ - 428, + 427, "DestroyViewFilterInput!" ] } @@ -7664,7 +7644,7 @@ export default { 52, { "input": [ - 429, + 428, "CreateViewFilterGroupInput!" ] } @@ -7677,7 +7657,7 @@ export default { "String!" ], "input": [ - 430, + 429, "UpdateViewFilterGroupInput!" ] } @@ -7725,7 +7705,7 @@ export default { } ], "resendEmailVerificationToken": [ - 153, + 142, { "email": [ 1, @@ -7741,7 +7721,7 @@ export default { 66, { "data": [ - 431, + 430, "ActivateWorkspaceInput!" ] } @@ -7750,7 +7730,7 @@ export default { 66, { "data": [ - 432, + 431, "UpdateWorkspaceInput!" ] } @@ -7759,13 +7739,13 @@ export default { 66 ], "checkCustomDomainValidRecords": [ - 164 + 153 ], "createApplicationRegistration": [ - 186, + 175, { "input": [ - 433, + 432, "CreateApplicationRegistrationInput!" ] } @@ -7774,7 +7754,7 @@ export default { 7, { "input": [ - 434, + 433, "UpdateApplicationRegistrationInput!" ] } @@ -7789,7 +7769,7 @@ export default { } ], "rotateApplicationRegistrationClientSecret": [ - 188, + 177, { "id": [ 1, @@ -7801,7 +7781,7 @@ export default { 5, { "input": [ - 436, + 435, "CreateApplicationRegistrationVariableInput!" ] } @@ -7810,7 +7790,7 @@ export default { 5, { "input": [ - 437, + 436, "UpdateApplicationRegistrationVariableInput!" ] } @@ -7828,7 +7808,7 @@ export default { 7, { "file": [ - 349, + 348, "Upload!" ], "universalIdentifier": [ @@ -7850,16 +7830,16 @@ export default { } ], "getAuthorizationUrlForSSO": [ - 205, + 194, { "input": [ - 439, + 438, "GetAuthorizationUrlForSSOInput!" ] } ], "getLoginTokenFromCredentials": [ - 214, + 203, { "email": [ 1, @@ -7885,7 +7865,7 @@ export default { } ], "signIn": [ - 203, + 192, { "email": [ 1, @@ -7907,7 +7887,7 @@ export default { } ], "verifyEmailAndGetLoginToken": [ - 211, + 200, { "emailVerificationToken": [ 1, @@ -7927,7 +7907,7 @@ export default { } ], "verifyEmailAndGetWorkspaceAgnosticToken": [ - 203, + 192, { "emailVerificationToken": [ 1, @@ -7943,7 +7923,7 @@ export default { } ], "getAuthTokensFromOTP": [ - 213, + 202, { "otp": [ 1, @@ -7963,7 +7943,7 @@ export default { } ], "signUp": [ - 203, + 192, { "email": [ 1, @@ -7985,7 +7965,7 @@ export default { } ], "signUpInWorkspace": [ - 208, + 197, { "email": [ 1, @@ -8016,13 +7996,13 @@ export default { } ], "signUpInNewWorkspace": [ - 208 + 197 ], "generateTransientToken": [ - 209 + 198 ], "getAuthTokensFromLoginToken": [ - 213, + 202, { "loginToken": [ 1, @@ -8035,7 +8015,7 @@ export default { } ], "authorizeApp": [ - 200, + 189, { "clientId": [ 1, @@ -8057,7 +8037,7 @@ export default { } ], "renewToken": [ - 213, + 202, { "appToken": [ 1, @@ -8066,7 +8046,7 @@ export default { } ], "generateApiKeyToken": [ - 212, + 201, { "apiKeyId": [ 3, @@ -8079,7 +8059,7 @@ export default { } ], "emailPasswordResetLink": [ - 204, + 193, { "email": [ 1, @@ -8091,7 +8071,7 @@ export default { } ], "updatePasswordViaResetToken": [ - 206, + 195, { "passwordResetToken": [ 1, @@ -8104,7 +8084,7 @@ export default { } ], "initiateOTPProvisioning": [ - 198, + 187, { "loginToken": [ 1, @@ -8117,10 +8097,10 @@ export default { } ], "initiateOTPProvisioningForAuthenticatedUser": [ - 198 + 187 ], "deleteTwoFactorAuthenticationMethod": [ - 197, + 186, { "twoFactorAuthenticationMethodId": [ 3, @@ -8129,7 +8109,7 @@ export default { } ], "verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [ - 199, + 188, { "otp": [ 1, @@ -8138,61 +8118,61 @@ export default { } ], "createOIDCIdentityProvider": [ - 196, + 185, { "input": [ - 440, + 439, "SetupOIDCSsoInput!" ] } ], "createSAMLIdentityProvider": [ - 196, + 185, { "input": [ - 441, + 440, "SetupSAMLSsoInput!" ] } ], "deleteSSOIdentityProvider": [ - 192, + 181, { "input": [ - 442, + 441, "DeleteSsoInput!" ] } ], "editSSOIdentityProvider": [ - 193, + 182, { "input": [ - 443, + 442, "EditSsoInput!" ] } ], "createWebhook": [ - 241, + 240, { "input": [ - 444, + 443, "CreateWebhookInput!" ] } ], "updateWebhook": [ - 241, + 240, { "input": [ - 445, + 444, "UpdateWebhookInput!" ] } ], "deleteWebhook": [ - 241, + 240, { "id": [ 3, @@ -8201,28 +8181,28 @@ export default { } ], "createChatThread": [ - 228 + 227 ], "createSkill": [ - 222, + 221, { "input": [ - 447, + 446, "CreateSkillInput!" ] } ], "updateSkill": [ - 222, + 221, { "input": [ - 448, + 447, "UpdateSkillInput!" ] } ], "deleteSkill": [ - 222, + 221, { "id": [ 3, @@ -8231,7 +8211,7 @@ export default { } ], "activateSkill": [ - 222, + 221, { "id": [ 3, @@ -8240,7 +8220,7 @@ export default { } ], "deactivateSkill": [ - 222, + 221, { "id": [ 3, @@ -8249,7 +8229,7 @@ export default { } ], "evaluateAgentTurn": [ - 234, + 233, { "turnId": [ 3, @@ -8258,7 +8238,7 @@ export default { } ], "runEvaluationInput": [ - 235, + 234, { "agentId": [ 3, @@ -8271,7 +8251,7 @@ export default { } ], "duplicateDashboard": [ - 320, + 319, { "id": [ 3, @@ -8280,7 +8260,7 @@ export default { } ], "impersonate": [ - 287, + 286, { "userId": [ 3, @@ -8293,7 +8273,7 @@ export default { } ], "startChannelSync": [ - 312, + 311, { "connectedAccountId": [ 3, @@ -8302,7 +8282,7 @@ export default { } ], "saveImapSmtpCaldavAccount": [ - 310, + 309, { "accountOwnerId": [ 3, @@ -8313,7 +8293,7 @@ export default { "String!" ], "connectionParameters": [ - 449, + 448, "EmailAccountConnectionParameters!" ], "id": [ @@ -8322,16 +8302,16 @@ export default { } ], "updateLabPublicFeatureFlag": [ - 165, + 154, { "input": [ - 451, + 450, "UpdateLabPublicFeatureFlagInput!" ] } ], "userLookupAdminPanel": [ - 278, + 277, { "userIdentifier": [ 1, @@ -8405,7 +8385,7 @@ export default { } ], "retryJobs": [ - 271, + 270, { "queueName": [ 1, @@ -8418,7 +8398,7 @@ export default { } ], "deleteJobs": [ - 266, + 265, { "queueName": [ 1, @@ -8431,13 +8411,13 @@ export default { } ], "enablePostgresProxy": [ - 311 + 310 ], "disablePostgresProxy": [ - 311 + 310 ], "createPublicDomain": [ - 299, + 298, { "domain": [ 1, @@ -8455,7 +8435,7 @@ export default { } ], "checkPublicDomainValidRecords": [ - 164, + 153, { "domain": [ 1, @@ -8464,14 +8444,14 @@ export default { } ], "createEmailingDomain": [ - 301, + 300, { "domain": [ 1, "String!" ], "driver": [ - 302, + 301, "EmailingDomainDriver!" ] } @@ -8486,7 +8466,7 @@ export default { } ], "verifyEmailingDomain": [ - 301, + 300, { "id": [ 1, @@ -8498,7 +8478,7 @@ export default { 68, { "input": [ - 452, + 451, "CreateOneAppTokenInput!" ] } @@ -8531,7 +8511,7 @@ export default { 6, { "workspaceMigration": [ - 454, + 453, "WorkspaceMigrationInput!" ] } @@ -8563,7 +8543,7 @@ export default { } ], "createDevelopmentApplication": [ - 288, + 287, { "universalIdentifier": [ 1, @@ -8576,7 +8556,7 @@ export default { } ], "generateApplicationToken": [ - 223, + 222, { "applicationId": [ 3, @@ -8585,7 +8565,7 @@ export default { } ], "syncApplication": [ - 289, + 288, { "manifest": [ 15, @@ -8594,10 +8574,10 @@ export default { } ], "uploadApplicationFile": [ - 290, + 289, { "file": [ - 349, + 348, "Upload!" ], "applicationUniversalIdentifier": [ @@ -8605,7 +8585,7 @@ export default { "String!" ], "fileFolder": [ - 457, + 456, "FileFolder!" ], "filePath": [ @@ -8628,7 +8608,7 @@ export default { } ], "renewApplicationToken": [ - 223, + 222, { "applicationRefreshToken": [ 1, @@ -8682,7 +8662,7 @@ export default { 3 ], "type": [ - 116 + 208 ], "name": [ 1 @@ -8711,7 +8691,7 @@ export default { 3 ], "update": [ - 348 + 347 ], "__typename": [ 1 @@ -8756,7 +8736,7 @@ export default { 3 ], "gridPosition": [ - 352 + 351 ], "position": [ 15 @@ -8796,7 +8776,7 @@ export default { 3 ], "gridPosition": [ - 352 + 351 ], "position": [ 15 @@ -8884,7 +8864,7 @@ export default { 3 ], "tabs": [ - 359 + 358 ], "__typename": [ 1 @@ -8907,7 +8887,7 @@ export default { 79 ], "widgets": [ - 360 + 359 ], "__typename": [ 1 @@ -8930,7 +8910,7 @@ export default { 3 ], "gridPosition": [ - 352 + 351 ], "position": [ 15 @@ -8999,7 +8979,7 @@ export default { 3 ], "update": [ - 364 + 363 ], "__typename": [ 1 @@ -9045,7 +9025,7 @@ export default { }, "CreateOneObjectInput": { "object": [ - 366 + 365 ], "__typename": [ 1 @@ -9105,7 +9085,7 @@ export default { }, "UpdateOneObjectInput": { "update": [ - 369 + 368 ], "id": [ 3 @@ -9160,7 +9140,7 @@ export default { 3 ], "update": [ - 371 + 370 ], "__typename": [ 1 @@ -9359,7 +9339,7 @@ export default { 3 ], "update": [ - 379 + 378 ], "__typename": [ 1 @@ -9394,7 +9374,7 @@ export default { 3 ], "update": [ - 383 + 382 ], "__typename": [ 1 @@ -9458,10 +9438,10 @@ export default { 3 ], "groups": [ - 388 + 387 ], "fields": [ - 389 + 388 ], "__typename": [ 1 @@ -9481,7 +9461,7 @@ export default { 6 ], "fields": [ - 389 + 388 ], "__typename": [ 1 @@ -9509,7 +9489,7 @@ export default { 3 ], "engineComponentKey": [ - 226 + 225 ], "label": [ 1 @@ -9527,7 +9507,7 @@ export default { 6 ], "availabilityType": [ - 227 + 226 ], "hotKeys": [ 1 @@ -9562,13 +9542,13 @@ export default { 6 ], "availabilityType": [ - 227 + 226 ], "availabilityObjectMetadataId": [ 3 ], "engineComponentKey": [ - 226 + 225 ], "hotKeys": [ 1 @@ -9608,7 +9588,7 @@ export default { 3 ], "update": [ - 394 + 393 ], "__typename": [ 1 @@ -9744,7 +9724,7 @@ export default { }, "UpdateRoleInput": { "update": [ - 399 + 398 ], "id": [ 3 @@ -9799,7 +9779,7 @@ export default { 3 ], "objectPermissions": [ - 401 + 400 ], "__typename": [ 1 @@ -9841,7 +9821,7 @@ export default { 3 ], "fieldPermissions": [ - 404 + 403 ], "__typename": [ 1 @@ -9872,10 +9852,10 @@ export default { 3 ], "predicates": [ - 406 + 405 ], "predicateGroups": [ - 407 + 406 ], "__typename": [ 1 @@ -10007,7 +9987,7 @@ export default { }, "CreateOneFieldMetadataInput": { "field": [ - 415 + 414 ], "__typename": [ 1 @@ -10080,7 +10060,7 @@ export default { 3 ], "update": [ - 417 + 416 ], "__typename": [ 1 @@ -10169,7 +10149,7 @@ export default { 3 ], "update": [ - 421 + 420 ], "__typename": [ 1 @@ -10242,7 +10222,7 @@ export default { 3 ], "update": [ - 426 + 425 ], "__typename": [ 1 @@ -10452,7 +10432,7 @@ export default { 1 ], "update": [ - 435 + 434 ], "__typename": [ 1 @@ -10515,7 +10495,7 @@ export default { 1 ], "update": [ - 438 + 437 ], "__typename": [ 1 @@ -10596,7 +10576,7 @@ export default { 3 ], "status": [ - 157 + 146 ], "__typename": [ 1 @@ -10627,7 +10607,7 @@ export default { 3 ], "update": [ - 446 + 445 ], "__typename": [ 1 @@ -10701,13 +10681,13 @@ export default { }, "EmailAccountConnectionParameters": { "IMAP": [ - 450 + 449 ], "SMTP": [ - 450 + 449 ], "CALDAV": [ - 450 + 449 ], "__typename": [ 1 @@ -10746,7 +10726,7 @@ export default { }, "CreateOneAppTokenInput": { "appToken": [ - 453 + 452 ], "__typename": [ 1 @@ -10762,7 +10742,7 @@ export default { }, "WorkspaceMigrationInput": { "actions": [ - 455 + 454 ], "__typename": [ 1 @@ -10770,10 +10750,10 @@ export default { }, "WorkspaceMigrationDeleteActionInput": { "type": [ - 456 + 455 ], "metadataName": [ - 237 + 236 ], "universalIdentifier": [ 1 @@ -10785,17 +10765,8 @@ export default { "WorkspaceMigrationActionType": {}, "FileFolder": {}, "Subscription": { - "onDbEvent": [ - 124, - { - "input": [ - 459, - "OnDbEventInput!" - ] - } - ], "onEventSubscription": [ - 123, + 215, { "eventStreamId": [ 1, @@ -10804,10 +10775,10 @@ export default { } ], "logicFunctionLogs": [ - 219, + 218, { "input": [ - 460, + 458, "LogicFunctionLogsInput!" ] } @@ -10816,20 +10787,6 @@ export default { 1 ] }, - "OnDbEventInput": { - "action": [ - 121 - ], - "objectNameSingular": [ - 1 - ], - "recordId": [ - 3 - ], - "__typename": [ - 1 - ] - }, "LogicFunctionLogsInput": { "applicationId": [ 3 diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts index e33c29b7f21..539757cc775 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/enums/database-event-action.ts @@ -1,3 +1,5 @@ +import { registerEnumType } from '@nestjs/graphql'; + export enum DatabaseEventAction { CREATED = 'created', UPDATED = 'updated', @@ -6,3 +8,8 @@ export enum DatabaseEventAction { RESTORED = 'restored', UPSERTED = 'upserted', } + +registerEnumType(DatabaseEventAction, { + name: 'DatabaseEventAction', + description: 'Database Event Action', +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts index a7a18fa9fb5..5bfc58ef7a6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/listeners/entity-events-to-db.listener.ts @@ -20,7 +20,7 @@ import { CallWebhookJobsJob } from 'src/engine/metadata-modules/webhook/jobs/cal import { WorkspaceEventBatchForWebhook } from 'src/engine/metadata-modules/webhook/types/workspace-event-batch-for-webhook.type'; import { CallDatabaseEventTriggerJobsJob } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/call-database-event-trigger-jobs.job'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service'; +import { ObjectRecordEventPublisher } from 'src/engine/subscriptions/object-record-event/object-record-event-publisher'; import { UpsertTimelineActivityFromInternalEvent } from 'src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job'; @Injectable() @@ -32,7 +32,7 @@ export class EntityEventsToDbListener { private readonly webhookQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.triggerQueue) private readonly triggerQueueService: MessageQueueService, - private readonly workspaceEventEmitterService: WorkspaceEventEmitterService, + private readonly objectRecordEventPublisher: ObjectRecordEventPublisher, ) {} @OnDatabaseBatchEvent('*', DatabaseEventAction.CREATED) @@ -79,7 +79,7 @@ export class EntityEventsToDbListener { }; const promises = [ - this.workspaceEventEmitterService.publish(batchEvent), + this.objectRecordEventPublisher.publish(batchEvent), this.webhookQueueService.add>( CallWebhookJobsJob.name, batchEventForWebhook, diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index fe563e90f7b..fc14288e864 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -65,7 +65,6 @@ import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; import { RowLevelPermissionModule } from 'src/engine/metadata-modules/row-level-permission-predicate/row-level-permission.module'; import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.module'; -import { MetadataEventEmitterModule } from 'src/engine/metadata-event-emitter/metadata-event-emitter.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; import { ChannelSyncModule } from 'src/modules/connected-account/channel-sync/channel-sync.module'; import { DashboardModule } from 'src/modules/dashboard/dashboard.module'; @@ -110,7 +109,6 @@ import { FileModule } from './file/file.module'; PostgresCredentialsModule, WorkflowApiModule, WorkspaceEventEmitterModule, - MetadataEventEmitterModule, ActorModule, TelemetryModule, AdminPanelModule, diff --git a/packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.module.ts b/packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.module.ts deleted file mode 100644 index 3e89c281a66..00000000000 --- a/packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Global, Module } from '@nestjs/common'; - -import { MetadataEventsToDbListener } from 'src/engine/metadata-event-emitter/listeners/metadata-events-to-db.listener'; -import { MetadataEventEmitter } from 'src/engine/metadata-event-emitter/metadata-event-emitter'; -import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; -import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; - -@Global() -@Module({ - imports: [SubscriptionsModule, WorkspaceCacheModule], - providers: [MetadataEventEmitter, MetadataEventsToDbListener], - exports: [MetadataEventEmitter], -}) -export class MetadataEventEmitterModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs-for-metadata.job.ts b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs-for-metadata.job.ts index 9261cafab8c..cfbf4a8d2c0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs-for-metadata.job.ts +++ b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs-for-metadata.job.ts @@ -6,7 +6,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { type MetadataEventBatch } from 'src/engine/metadata-event-emitter/types/metadata-event-batch.type'; +import { type MetadataEventBatch } from 'src/engine/subscriptions/metadata-event/types/metadata-event-batch.type'; import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type'; import { CallWebhookJob } from 'src/engine/metadata-modules/webhook/jobs/call-webhook.job'; import { type CallMetadataWebhookJobData } from 'src/engine/metadata-modules/webhook/types/webhook-job-data.type'; diff --git a/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.dto.ts b/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.dto.ts deleted file mode 100644 index 279629f6c87..00000000000 --- a/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.dto.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; - -import GraphQLJSON from 'graphql-type-json'; -import { ObjectRecord } from 'twenty-shared/types'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; - -registerEnumType(DatabaseEventAction, { - name: 'DatabaseEventAction', - description: 'Database Event Action', -}); - -@ObjectType('OnDbEvent') -export class OnDbEventDTO { - @Field(() => DatabaseEventAction) - action: DatabaseEventAction; - - @Field(() => String) - objectNameSingular: string; - - @Field() - eventDate: Date; - - @Field(() => GraphQLJSON) - record: ObjectRecord; - - @Field(() => [String], { nullable: true }) - updatedFields?: string[]; -} diff --git a/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.input.ts b/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.input.ts deleted file mode 100644 index 4ff18e48393..00000000000 --- a/packages/twenty-server/src/engine/subscriptions/dtos/on-db-event.input.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; - -@InputType() -export class OnDbEventInput { - @Field(() => DatabaseEventAction, { nullable: true }) - action?: DatabaseEventAction; - - @Field(() => String, { nullable: true }) - objectNameSingular?: string; - - @Field(() => UUIDScalarType, { nullable: true }) - recordId?: string; -} diff --git a/packages/twenty-server/src/engine/subscriptions/enums/subscription-channel.enum.ts b/packages/twenty-server/src/engine/subscriptions/enums/subscription-channel.enum.ts index aba3df8755e..e5ce42063c5 100644 --- a/packages/twenty-server/src/engine/subscriptions/enums/subscription-channel.enum.ts +++ b/packages/twenty-server/src/engine/subscriptions/enums/subscription-channel.enum.ts @@ -1,5 +1,4 @@ export enum SubscriptionChannel { - DATABASE_EVENT_CHANNEL = 'DATABASE_EVENT_CHANNEL', LOGIC_FUNCTION_LOGS_CHANNEL = 'LOGIC_FUNCTION_LOGS_CHANNEL', EVENT_STREAM_CHANNEL = 'EVENT_STREAM_CHANNEL', } diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter-exception.filter.ts b/packages/twenty-server/src/engine/subscriptions/event-stream-exception.filter.ts similarity index 91% rename from packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter-exception.filter.ts rename to packages/twenty-server/src/engine/subscriptions/event-stream-exception.filter.ts index 50eeff50cc8..4474bbec540 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter-exception.filter.ts +++ b/packages/twenty-server/src/engine/subscriptions/event-stream-exception.filter.ts @@ -10,9 +10,7 @@ import { } from 'src/engine/subscriptions/event-stream.exception'; @Catch(EventStreamException) -export class WorkspaceEventEmitterExceptionFilter - implements GqlExceptionFilter -{ +export class EventStreamExceptionFilter implements GqlExceptionFilter { catch(exception: EventStreamException) { switch (exception.code) { case EventStreamExceptionCode.EVENT_STREAM_ALREADY_EXISTS: diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.resolver.ts b/packages/twenty-server/src/engine/subscriptions/event-stream.resolver.ts similarity index 80% rename from packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.resolver.ts rename to packages/twenty-server/src/engine/subscriptions/event-stream.resolver.ts index 119607040b6..2c56b2c0012 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.resolver.ts +++ b/packages/twenty-server/src/engine/subscriptions/event-stream.resolver.ts @@ -19,10 +19,8 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { EVENT_STREAM_TTL_MS } from 'src/engine/subscriptions/constants/event-stream-ttl.constant'; import { AddQuerySubscriptionInput } from 'src/engine/subscriptions/dtos/add-query-subscription.input'; import { EventSubscriptionDTO } from 'src/engine/subscriptions/dtos/event-subscription.dto'; -import { OnDbEventDTO } from 'src/engine/subscriptions/dtos/on-db-event.dto'; -import { OnDbEventInput } from 'src/engine/subscriptions/dtos/on-db-event.input'; import { RemoveQueryFromEventStreamInput } from 'src/engine/subscriptions/dtos/remove-query-subscription.input'; -import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum'; +import { EventStreamExceptionFilter } from 'src/engine/subscriptions/event-stream-exception.filter'; import { EventStreamException, EventStreamExceptionCode, @@ -30,57 +28,19 @@ import { import { EventStreamService } from 'src/engine/subscriptions/event-stream.service'; import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; import { type EventStreamPayload } from 'src/engine/subscriptions/types/event-stream-payload.type'; -import { wrapAsyncIteratorWithLifecycle } from 'src/engine/workspace-event-emitter/utils/wrap-async-iterator-with-lifecycle'; -import { WorkspaceEventEmitterExceptionFilter } from 'src/engine/workspace-event-emitter/workspace-event-emitter-exception.filter'; - -import { eventStreamIdToChannelId } from './utils/get-channel-id-from-event-stream-id'; +import { eventStreamIdToChannelId } from 'src/engine/subscriptions/utils/get-channel-id-from-event-stream-id'; +import { wrapAsyncIteratorWithLifecycle } from 'src/engine/subscriptions/utils/wrap-async-iterator-with-lifecycle'; @MetadataResolver() @UseGuards(WorkspaceAuthGuard, UserAuthGuard, NoPermissionGuard) @UsePipes(ResolverValidationPipe) -@UseFilters( - WorkspaceEventEmitterExceptionFilter, - PreventNestToAutoLogGraphqlErrorsFilter, -) -export class WorkspaceEventEmitterResolver { +@UseFilters(EventStreamExceptionFilter, PreventNestToAutoLogGraphqlErrorsFilter) +export class EventStreamResolver { constructor( private readonly subscriptionService: SubscriptionService, private readonly eventStreamService: EventStreamService, ) {} - @Subscription(() => OnDbEventDTO, { - filter: ( - payload: { onDbEvent: OnDbEventDTO }, - variables: { input: OnDbEventInput }, - ) => { - const isActionMatching = - !isDefined(variables.input.action) || - payload.onDbEvent.action === variables.input.action; - - const isObjectNameSingularMatching = - !isDefined(variables.input.objectNameSingular) || - payload.onDbEvent.objectNameSingular === - variables.input.objectNameSingular; - - const isRecordIdMatching = - !isDefined(variables.input.recordId) || - payload.onDbEvent.record.id === variables.input.recordId; - - return ( - isActionMatching && isObjectNameSingularMatching && isRecordIdMatching - ); - }, - }) - onDbEvent( - @Args('input') _: OnDbEventInput, - @AuthWorkspace() workspace: WorkspaceEntity, - ) { - return this.subscriptionService.subscribe({ - channel: SubscriptionChannel.DATABASE_EVENT_CHANNEL, - workspaceId: workspace.id, - }); - } - @Subscription(() => EventSubscriptionDTO, { nullable: true, resolve: ( diff --git a/packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts similarity index 93% rename from packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.ts rename to packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts index 16edb98c1f8..6912b9219e5 100644 --- a/packages/twenty-server/src/engine/metadata-event-emitter/metadata-event-emitter.ts +++ b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts @@ -6,8 +6,8 @@ import { isDefined } from 'twenty-shared/utils'; import { getWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; -import { MetadataEventBatch } from 'src/engine/metadata-event-emitter/types/metadata-event-batch.type'; -import { computeMetadataEventName } from 'src/engine/metadata-event-emitter/utils/compute-metadata-event-name.util'; +import { MetadataEventBatch } from 'src/engine/subscriptions/metadata-event/types/metadata-event-batch.type'; +import { computeMetadataEventName } from 'src/engine/subscriptions/metadata-event/utils/compute-metadata-event-name.util'; import { AllMetadataEventName, AllMetadataEventType, diff --git a/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.ts new file mode 100644 index 00000000000..e712d828d61 --- /dev/null +++ b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-publisher.ts @@ -0,0 +1,250 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; + +import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service'; +import { type MetadataEventBatch } from 'src/engine/subscriptions/metadata-event/types/metadata-event-batch.type'; +import { type EventStreamPayload } from 'src/engine/subscriptions/types/event-stream-payload.type'; +import { EventStreamService } from 'src/engine/subscriptions/event-stream.service'; +import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; +import { enrichFieldMetadataEventWithRelations } from 'src/engine/subscriptions/metadata-event/utils/enrich-field-metadata-event-with-relations.util'; + +@Injectable() +export class MetadataEventPublisher { + constructor( + private readonly subscriptionService: SubscriptionService, + private readonly eventStreamService: EventStreamService, + private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly navigationMenuItemRecordIdentifierService: NavigationMenuItemRecordIdentifierService, + ) {} + + async publish(metadataEventBatch: MetadataEventBatch): Promise { + const workspaceId = metadataEventBatch.workspaceId; + + const activeStreamIds = + await this.eventStreamService.getActiveStreamIds(workspaceId); + + if (activeStreamIds.length === 0) { + return; + } + + const streamsData = await this.eventStreamService.getStreamsData( + workspaceId, + activeStreamIds, + ); + + const enrichedBatch = + await this.enrichMetadataEventBatch(metadataEventBatch); + + const streamIdsToRemove: string[] = []; + + for (const [streamChannelId, streamData] of streamsData) { + if (!isDefined(streamData)) { + streamIdsToRemove.push(streamChannelId); + continue; + } + + await this.publishToStream({ + streamChannelId, + metadataEventBatch: enrichedBatch, + }); + } + + await this.eventStreamService.removeFromActiveStreams( + workspaceId, + streamIdsToRemove, + ); + } + + private async enrichMetadataEventBatch( + metadataEventBatch: MetadataEventBatch, + ): Promise { + switch (metadataEventBatch.metadataName) { + case 'fieldMetadata': + return this.enrichFieldMetadataEventsWithRelations( + metadataEventBatch as MetadataEventBatch<'fieldMetadata'>, + ); + case 'navigationMenuItem': + return this.enrichNavigationMenuItemEventsWithTargetRecordIdentifier( + metadataEventBatch as MetadataEventBatch<'navigationMenuItem'>, + ); + case 'objectMetadata': + return this.resolveObjectMetadataStandardOverrides( + metadataEventBatch as MetadataEventBatch<'objectMetadata'>, + ); + default: + return metadataEventBatch; + } + } + + private async publishToStream({ + streamChannelId, + metadataEventBatch, + }: { + streamChannelId: string; + metadataEventBatch: MetadataEventBatch; + }): Promise { + if (!isNonEmptyArray(metadataEventBatch.events)) { + return; + } + + const metadataEvents = metadataEventBatch.events.map((metadataEvent) => ({ + ...metadataEvent, + updatedCollectionHash: metadataEventBatch.updatedCollectionHash, + })); + + const payload: EventStreamPayload = { + objectRecordEventsWithQueryIds: [], + metadataEvents, + }; + + await this.subscriptionService.publishToEventStream({ + workspaceId: metadataEventBatch.workspaceId, + eventStreamChannelId: streamChannelId, + payload, + }); + } + + private async enrichFieldMetadataEventsWithRelations( + metadataEventBatch: MetadataEventBatch<'fieldMetadata'>, + ): Promise> { + const { flatFieldMetadataMaps, flatObjectMetadataMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId: metadataEventBatch.workspaceId, + flatMapsKeys: ['flatFieldMetadataMaps', 'flatObjectMetadataMaps'], + }, + ); + + const enrichedEvents = metadataEventBatch.events.map((event) => { + if ( + !('after' in event.properties) || + !isDefined(event.properties.after) + ) { + return event; + } + + const enrichedAfter = enrichFieldMetadataEventWithRelations({ + record: event.properties.after as Record, + flatFieldMetadataMaps, + flatObjectMetadataMaps, + }); + + return { + ...event, + properties: { + ...event.properties, + after: enrichedAfter, + }, + } as typeof event; + }); + + return { ...metadataEventBatch, events: enrichedEvents }; + } + + private async enrichNavigationMenuItemEventsWithTargetRecordIdentifier( + metadataEventBatch: MetadataEventBatch<'navigationMenuItem'>, + ): Promise> { + const enrichedEvents = await Promise.all( + metadataEventBatch.events.map(async (event) => { + if ( + !('after' in event.properties) || + !isDefined(event.properties.after) + ) { + return event; + } + + const after = event.properties.after as Record; + const targetRecordId = after.targetRecordId as string | undefined; + const targetObjectMetadataId = after.targetObjectMetadataId as + | string + | undefined; + + if (!isDefined(targetRecordId) || !isDefined(targetObjectMetadataId)) { + return event; + } + + const targetRecordIdentifier = + await this.navigationMenuItemRecordIdentifierService.resolveRecordIdentifier( + { + targetRecordId, + targetObjectMetadataId, + workspaceId: metadataEventBatch.workspaceId, + }, + ); + + const enrichedAfter: Record = { + ...after, + targetRecordIdentifier, + }; + + return { + ...event, + properties: { + ...event.properties, + after: enrichedAfter, + }, + } as typeof event; + }), + ); + + return { ...metadataEventBatch, events: enrichedEvents }; + } + + private resolveObjectMetadataStandardOverrides( + metadataEventBatch: MetadataEventBatch<'objectMetadata'>, + ): MetadataEventBatch<'objectMetadata'> { + const enrichedEvents = metadataEventBatch.events.map((event) => { + const enrichedProperties = { ...event.properties }; + + if ( + 'before' in enrichedProperties && + isDefined(enrichedProperties.before) + ) { + enrichedProperties.before = + this.applyStandardOverridesToObjectMetadataRecord( + enrichedProperties.before as Record, + ) as typeof enrichedProperties.before; + } + + if ( + 'after' in enrichedProperties && + isDefined(enrichedProperties.after) + ) { + enrichedProperties.after = + this.applyStandardOverridesToObjectMetadataRecord( + enrichedProperties.after as Record, + ) as typeof enrichedProperties.after; + } + + return { ...event, properties: enrichedProperties } as typeof event; + }); + + return { ...metadataEventBatch, events: enrichedEvents }; + } + + private applyStandardOverridesToObjectMetadataRecord( + record: Record, + ): Record { + const standardOverrides = record.standardOverrides as + | Record + | null + | undefined; + + if (!isDefined(standardOverrides)) { + return record; + } + + const resolved = { ...record }; + + for (const key of OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES) { + if (isDefined(standardOverrides[key])) { + resolved[key] = standardOverrides[key]; + } + } + + return resolved; + } +} diff --git a/packages/twenty-server/src/engine/metadata-event-emitter/listeners/metadata-events-to-db.listener.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-events-to-db.listener.ts similarity index 88% rename from packages/twenty-server/src/engine/metadata-event-emitter/listeners/metadata-events-to-db.listener.ts rename to packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-events-to-db.listener.ts index e11e09fe1bc..f5dab0088ab 100644 --- a/packages/twenty-server/src/engine/metadata-event-emitter/listeners/metadata-events-to-db.listener.ts +++ b/packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-events-to-db.listener.ts @@ -6,12 +6,12 @@ import { AllMetadataName } from 'twenty-shared/metadata'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { type MetadataEventBatch } from 'src/engine/metadata-event-emitter/types/metadata-event-batch.type'; +import { type MetadataEventBatch } from 'src/engine/subscriptions/metadata-event/types/metadata-event-batch.type'; import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; import { CallWebhookJobsForMetadataJob } from 'src/engine/metadata-modules/webhook/jobs/call-webhook-jobs-for-metadata.job'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { type WorkspaceCacheKeyName } from 'src/engine/workspace-cache/types/workspace-cache-key.type'; -import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service'; +import { MetadataEventPublisher } from 'src/engine/subscriptions/metadata-event/metadata-event-publisher'; import { type AllMetadataEventType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/metadata-event'; @Injectable() @@ -19,7 +19,7 @@ export class MetadataEventsToDbListener { constructor( @InjectMessageQueue(MessageQueue.webhookQueue) private readonly webhookQueueService: MessageQueueService, - private readonly workspaceEventEmitterService: WorkspaceEventEmitterService, + private readonly metadataEventPublisher: MetadataEventPublisher, private readonly workspaceCacheService: WorkspaceCacheService, ) {} @@ -68,7 +68,7 @@ export class MetadataEventsToDbListener { const updatedCollectionHash = cacheHashes[cacheKeyName]; - await this.workspaceEventEmitterService.publish({ + await this.metadataEventPublisher.publish({ ...metadataEventBatch, updatedCollectionHash, }); diff --git a/packages/twenty-server/src/engine/metadata-event-emitter/types/metadata-event-batch.type.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/types/metadata-event-batch.type.ts similarity index 100% rename from packages/twenty-server/src/engine/metadata-event-emitter/types/metadata-event-batch.type.ts rename to packages/twenty-server/src/engine/subscriptions/metadata-event/types/metadata-event-batch.type.ts diff --git a/packages/twenty-server/src/engine/metadata-event-emitter/utils/compute-metadata-event-name.util.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/utils/compute-metadata-event-name.util.ts similarity index 100% rename from packages/twenty-server/src/engine/metadata-event-emitter/utils/compute-metadata-event-name.util.ts rename to packages/twenty-server/src/engine/subscriptions/metadata-event/utils/compute-metadata-event-name.util.ts diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/utils/enrich-field-metadata-event-with-relations.util.ts b/packages/twenty-server/src/engine/subscriptions/metadata-event/utils/enrich-field-metadata-event-with-relations.util.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-event-emitter/utils/enrich-field-metadata-event-with-relations.util.ts rename to packages/twenty-server/src/engine/subscriptions/metadata-event/utils/enrich-field-metadata-event-with-relations.util.ts diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/__tests__/workspace-event-emitter.service.spec.ts b/packages/twenty-server/src/engine/subscriptions/object-record-event/__tests__/object-record-event-publisher.spec.ts similarity index 98% rename from packages/twenty-server/src/engine/workspace-event-emitter/__tests__/workspace-event-emitter.service.spec.ts rename to packages/twenty-server/src/engine/subscriptions/object-record-event/__tests__/object-record-event-publisher.spec.ts index c1a5845208a..4fe1c4c2c45 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/__tests__/workspace-event-emitter.service.spec.ts +++ b/packages/twenty-server/src/engine/subscriptions/object-record-event/__tests__/object-record-event-publisher.spec.ts @@ -22,8 +22,7 @@ import { type EventStreamData } from 'src/engine/subscriptions/types/event-strea import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service'; -import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service'; +import { ObjectRecordEventPublisher } from 'src/engine/subscriptions/object-record-event/object-record-event-publisher'; jest.mock( 'src/engine/twenty-orm/utils/build-row-level-permission-record-filter.util', @@ -70,8 +69,8 @@ const buildFlatFieldMetadataMaps = ( createEmptyFlatEntityMaps() as FlatEntityMaps, ); -describe('WorkspaceEventEmitterService', () => { - let service: WorkspaceEventEmitterService; +describe('ObjectRecordEventPublisher', () => { + let service: ObjectRecordEventPublisher; let mockSubscriptionService: jest.Mocked< Pick >; @@ -273,7 +272,7 @@ describe('WorkspaceEventEmitterService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - WorkspaceEventEmitterService, + ObjectRecordEventPublisher, { provide: SubscriptionService, useValue: mockSubscriptionService, @@ -302,19 +301,11 @@ describe('WorkspaceEventEmitterService', () => { provide: CommonSelectFieldsHelper, useValue: new CommonSelectFieldsHelper(), }, - { - provide: NavigationMenuItemRecordIdentifierService, - useValue: { - enrichNavigationMenuItemEventsWithRecordIdentifiers: jest - .fn() - .mockResolvedValue(undefined), - }, - }, ], }).compile(); - service = module.get( - WorkspaceEventEmitterService, + service = module.get( + ObjectRecordEventPublisher, ); }); diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts b/packages/twenty-server/src/engine/subscriptions/object-record-event/object-record-event-publisher.ts similarity index 66% rename from packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts rename to packages/twenty-server/src/engine/subscriptions/object-record-event/object-record-event-publisher.ts index 21ea1920172..ee949d4c5a7 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.service.ts +++ b/packages/twenty-server/src/engine/subscriptions/object-record-event/object-record-event-publisher.ts @@ -13,7 +13,6 @@ import { import { combineFilters, isDefined, - isNonEmptyArray, isRecordGqlOperationSignature, } from 'twenty-shared/utils'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; @@ -25,20 +24,14 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/ import { type SerializableAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type FlatWorkspaceMemberMaps } from 'src/engine/core-modules/user/types/flat-workspace-member-maps.type'; -import { type MetadataEventBatch } from 'src/engine/metadata-event-emitter/types/metadata-event-batch.type'; -import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; -import { NavigationMenuItemRecordIdentifierService } from 'src/engine/metadata-modules/navigation-menu-item/services/navigation-menu-item-record-identifier.service'; -import { enrichFieldMetadataEventWithRelations } from 'src/engine/workspace-event-emitter/utils/enrich-field-metadata-event-with-relations.util'; import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/types/user-workspace-role-map'; import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type'; import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type'; -import { transformEventToWebhookEvent } from 'src/engine/metadata-modules/webhook/utils/transform-event-to-webhook-event'; -import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum'; import { EventStreamService } from 'src/engine/subscriptions/event-stream.service'; import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; import { @@ -54,8 +47,9 @@ import { isRecordMatchingRLSRowLevelPermissionPredicate } from 'src/engine/twent import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { parseEventNameOrThrow } from 'src/engine/workspace-event-emitter/utils/parse-event-name'; + @Injectable() -export class WorkspaceEventEmitterService { +export class ObjectRecordEventPublisher { constructor( private readonly subscriptionService: SubscriptionService, private readonly eventStreamService: EventStreamService, @@ -64,57 +58,12 @@ export class WorkspaceEventEmitterService { private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly commonSelectFieldsHelper: CommonSelectFieldsHelper, - private readonly navigationMenuItemRecordIdentifierService: NavigationMenuItemRecordIdentifierService, ) {} async publish( - eventBatch: WorkspaceEventBatch | MetadataEventBatch, - ): Promise { - if (!this.isMetadataEventBatch(eventBatch)) { - await this.publishToLegacyChannel(eventBatch); - } - - await this.publishToEventStreams(eventBatch); - } - - private isMetadataEventBatch( - eventBatch: WorkspaceEventBatch | MetadataEventBatch, - ): eventBatch is MetadataEventBatch { - return 'metadataName' in eventBatch; - } - - private async publishToLegacyChannel( eventBatch: WorkspaceEventBatch, - ): Promise { - const [nameSingular, operation] = eventBatch.name.split('.'); - - for (const eventData of eventBatch.events) { - const { record, updatedFields } = transformEventToWebhookEvent({ - eventName: eventBatch.name, - event: eventData, - }); - - const event = { - action: operation, - objectNameSingular: nameSingular, - eventDate: new Date(), - record, - ...(updatedFields && { updatedFields }), - }; - - await this.subscriptionService.publish({ - channel: SubscriptionChannel.DATABASE_EVENT_CHANNEL, - workspaceId: eventBatch.workspaceId, - payload: { onDbEvent: event }, - }); - } - } - - private async publishToEventStreams( - eventBatch: WorkspaceEventBatch | MetadataEventBatch, ): Promise { const workspaceId = eventBatch.workspaceId; - const isMetadata = this.isMetadataEventBatch(eventBatch); const activeStreamIds = await this.eventStreamService.getActiveStreamIds(workspaceId); @@ -128,53 +77,28 @@ export class WorkspaceEventEmitterService { activeStreamIds, ); + const { permissionsContext, flatWorkspaceMemberMaps } = + await this.fetchObjectRecordStreamContext(workspaceId); + const streamIdsToRemove: string[] = []; - const objectRecordStreamContext = !isMetadata - ? await this.fetchObjectRecordStreamContext(workspaceId) - : undefined; - - const enrichedMetadataEventBatch = isMetadata - ? await this.enrichFieldMetadataEventsWithRelations( - eventBatch as MetadataEventBatch, - ) - .then((batch) => - this.enrichNavigationMenuItemEventsWithTargetRecordIdentifier( - batch, - ), - ) - .then((batch) => this.resolveObjectMetadataStandardOverrides(batch)) - : undefined; - for (const [streamChannelId, streamData] of streamsData) { if (!isDefined(streamData)) { streamIdsToRemove.push(streamChannelId); continue; } - if (isMetadata && isDefined(enrichedMetadataEventBatch)) { - await this.processMetadataStreamEvents( - streamChannelId, - streamData, - enrichedMetadataEventBatch, - ); - } else if (!isMetadata) { - if (Object.keys(streamData.queries).length === 0) { - continue; - } - - if (!isDefined(objectRecordStreamContext)) { - continue; - } - - await this.processObjectRecordStreamEvents( - streamChannelId, - streamData, - eventBatch as WorkspaceEventBatch, - objectRecordStreamContext.permissionsContext, - objectRecordStreamContext.flatWorkspaceMemberMaps, - ); + if (Object.keys(streamData.queries).length === 0) { + continue; } + + await this.processObjectRecordStreamEvents({ + streamChannelId, + streamData, + workspaceEventBatch: eventBatch, + permissionsContext, + flatWorkspaceMemberMaps, + }); } await this.eventStreamService.removeFromActiveStreams( @@ -193,198 +117,25 @@ export class WorkspaceEventEmitterService { return { permissionsContext, flatWorkspaceMemberMaps }; } - private async processMetadataStreamEvents( - streamChannelId: string, - _streamData: EventStreamData, - metadataEventBatch: MetadataEventBatch, - ): Promise { - if (!isNonEmptyArray(metadataEventBatch.events)) { - return; - } - - const metadataEvents = metadataEventBatch.events.map((metadataEvent) => ({ - ...metadataEvent, - updatedCollectionHash: metadataEventBatch.updatedCollectionHash, - })); - - const payload: EventStreamPayload = { - objectRecordEventsWithQueryIds: [], - metadataEvents, - }; - - await this.subscriptionService.publishToEventStream({ - workspaceId: metadataEventBatch.workspaceId, - eventStreamChannelId: streamChannelId, - payload, - }); - } - - private async enrichFieldMetadataEventsWithRelations( - metadataEventBatch: MetadataEventBatch, - ): Promise { - if (metadataEventBatch.metadataName !== 'fieldMetadata') { - return metadataEventBatch; - } - - const { flatFieldMetadataMaps, flatObjectMetadataMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId: metadataEventBatch.workspaceId, - flatMapsKeys: ['flatFieldMetadataMaps', 'flatObjectMetadataMaps'], - }, - ); - - const enrichedEvents = metadataEventBatch.events.map((event) => { - if ( - !('after' in event.properties) || - !isDefined(event.properties.after) - ) { - return event; - } - - const enrichedAfter = enrichFieldMetadataEventWithRelations({ - record: event.properties.after as Record, - flatFieldMetadataMaps, - flatObjectMetadataMaps, - }); - - return { - ...event, - properties: { - ...event.properties, - after: enrichedAfter, - }, - } as typeof event; - }); - - return { ...metadataEventBatch, events: enrichedEvents }; - } - - private async enrichNavigationMenuItemEventsWithTargetRecordIdentifier( - metadataEventBatch: MetadataEventBatch, - ): Promise { - if (metadataEventBatch.metadataName !== 'navigationMenuItem') { - return metadataEventBatch; - } - - const enrichedEvents = await Promise.all( - metadataEventBatch.events.map(async (event) => { - if ( - !('after' in event.properties) || - !isDefined(event.properties.after) - ) { - return event; - } - - const after = event.properties.after as Record; - const targetRecordId = after.targetRecordId as string | undefined; - const targetObjectMetadataId = after.targetObjectMetadataId as - | string - | undefined; - - if (!isDefined(targetRecordId) || !isDefined(targetObjectMetadataId)) { - return event; - } - - const targetRecordIdentifier = - await this.navigationMenuItemRecordIdentifierService.resolveRecordIdentifier( - { - targetRecordId, - targetObjectMetadataId, - workspaceId: metadataEventBatch.workspaceId, - }, - ); - - const enrichedAfter: Record = { - ...after, - targetRecordIdentifier, - }; - - return { - ...event, - properties: { - ...event.properties, - after: enrichedAfter, - }, - } as typeof event; - }), - ); - - return { ...metadataEventBatch, events: enrichedEvents }; - } - - private resolveObjectMetadataStandardOverrides( - metadataEventBatch: MetadataEventBatch, - ): MetadataEventBatch { - if (metadataEventBatch.metadataName !== 'objectMetadata') { - return metadataEventBatch; - } - - const enrichedEvents = metadataEventBatch.events.map((event) => { - const enrichedProperties = { ...event.properties }; - - if ( - 'before' in enrichedProperties && - isDefined(enrichedProperties.before) - ) { - enrichedProperties.before = - this.applyStandardOverridesToObjectMetadataRecord( - enrichedProperties.before as Record, - ) as typeof enrichedProperties.before; - } - - if ( - 'after' in enrichedProperties && - isDefined(enrichedProperties.after) - ) { - enrichedProperties.after = - this.applyStandardOverridesToObjectMetadataRecord( - enrichedProperties.after as Record, - ) as typeof enrichedProperties.after; - } - - return { ...event, properties: enrichedProperties } as typeof event; - }); - - return { ...metadataEventBatch, events: enrichedEvents }; - } - - private applyStandardOverridesToObjectMetadataRecord( - record: Record, - ): Record { - const standardOverrides = record.standardOverrides as - | Record - | null - | undefined; - - if (!isDefined(standardOverrides)) { - return record; - } - - const resolved = { ...record }; - - for (const key of OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES) { - if (isDefined(standardOverrides[key])) { - resolved[key] = standardOverrides[key]; - } - } - - return resolved; - } - - private async processObjectRecordStreamEvents( - streamChannelId: string, - streamData: EventStreamData, - workspaceEventBatch: WorkspaceEventBatch, + private async processObjectRecordStreamEvents({ + streamChannelId, + streamData, + workspaceEventBatch, + permissionsContext, + flatWorkspaceMemberMaps, + }: { + streamChannelId: string; + streamData: EventStreamData; + workspaceEventBatch: WorkspaceEventBatch; permissionsContext: { flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps; flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps; flatFieldMetadataMaps: FlatEntityMaps; userWorkspaceRoleMap: Record; rolesPermissions: ObjectsPermissionsByRoleId; - }, - flatWorkspaceMemberMaps: FlatWorkspaceMemberMaps, - ): Promise { + }; + flatWorkspaceMemberMaps: FlatWorkspaceMemberMaps; + }): Promise { const { userWorkspaceId } = streamData.authContext; if (!isDefined(userWorkspaceId)) { @@ -470,7 +221,9 @@ export class WorkspaceEventEmitterService { if (matchedEvents.length > 0) { await this.enrichEventBatchWithNestedRelations({ objectMetadata: workspaceEventBatch.objectMetadata, - events: matchedEvents.map((e) => e.objectRecordEvent), + events: matchedEvents.map( + (matchedEvent) => matchedEvent.objectRecordEvent, + ), streamData, permissionsContext, workspaceId: workspaceEventBatch.workspaceId, diff --git a/packages/twenty-server/src/engine/subscriptions/subscriptions.module.ts b/packages/twenty-server/src/engine/subscriptions/subscriptions.module.ts index fddd8a010ed..121c7cbb603 100644 --- a/packages/twenty-server/src/engine/subscriptions/subscriptions.module.ts +++ b/packages/twenty-server/src/engine/subscriptions/subscriptions.module.ts @@ -1,14 +1,26 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProcessNestedRelationsV2Helper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper'; +import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper'; import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module'; import { CacheStorageModule } from 'src/engine/core-modules/cache-storage/cache-storage.module'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; +import { EventStreamResolver } from 'src/engine/subscriptions/event-stream.resolver'; import { EventStreamService } from 'src/engine/subscriptions/event-stream.service'; +import { MetadataEventEmitter } from 'src/engine/subscriptions/metadata-event/metadata-event-emitter'; +import { MetadataEventPublisher } from 'src/engine/subscriptions/metadata-event/metadata-event-publisher'; +import { MetadataEventsToDbListener } from 'src/engine/subscriptions/metadata-event/metadata-events-to-db.listener'; +import { ObjectRecordEventPublisher } from 'src/engine/subscriptions/object-record-event/object-record-event-publisher'; import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +@Global() @Module({ imports: [ RedisClientModule, @@ -16,8 +28,26 @@ import { SubscriptionService } from 'src/engine/subscriptions/subscription.servi CacheLockModule, MetricsModule, TypeOrmModule.forFeature([WorkspaceEntity]), + WorkspaceCacheModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, + NavigationMenuItemModule, + ], + providers: [ + SubscriptionService, + EventStreamService, + EventStreamResolver, + ObjectRecordEventPublisher, + MetadataEventPublisher, + MetadataEventEmitter, + MetadataEventsToDbListener, + ProcessNestedRelationsHelper, + ProcessNestedRelationsV2Helper, + CommonSelectFieldsHelper, + ], + exports: [ + SubscriptionService, + ObjectRecordEventPublisher, + MetadataEventEmitter, ], - providers: [SubscriptionService, EventStreamService], - exports: [SubscriptionService, EventStreamService], }) export class SubscriptionsModule {} diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/utils/get-channel-id-from-event-stream-id.ts b/packages/twenty-server/src/engine/subscriptions/utils/get-channel-id-from-event-stream-id.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-event-emitter/utils/get-channel-id-from-event-stream-id.ts rename to packages/twenty-server/src/engine/subscriptions/utils/get-channel-id-from-event-stream-id.ts diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/utils/wrap-async-iterator-with-lifecycle.ts b/packages/twenty-server/src/engine/subscriptions/utils/wrap-async-iterator-with-lifecycle.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-event-emitter/utils/wrap-async-iterator-with-lifecycle.ts rename to packages/twenty-server/src/engine/subscriptions/utils/wrap-async-iterator-with-lifecycle.ts diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/utils/__tests__/is-metadata-record-matching-filter.util.spec.ts b/packages/twenty-server/src/engine/workspace-event-emitter/utils/__tests__/is-metadata-record-matching-filter.util.spec.ts deleted file mode 100644 index df93791f6ff..00000000000 --- a/packages/twenty-server/src/engine/workspace-event-emitter/utils/__tests__/is-metadata-record-matching-filter.util.spec.ts +++ /dev/null @@ -1,512 +0,0 @@ -import { isMetadataRecordMatchingFilter } from 'src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util'; - -const record = { - id: '1', - name: 'Test Object', - label: 'testObject', - isActive: true, - count: 42, -}; - -describe('isMetadataRecordMatchingFilter', () => { - describe('empty filters', () => { - it('should match any record when filter is empty', () => { - expect(isMetadataRecordMatchingFilter({ record, filter: {} })).toBe(true); - }); - }); - - describe('and filter', () => { - it('should match when all sub-filters match', () => { - const filter = { - and: [{ name: { eq: 'Test Object' } }, { isActive: { eq: true } }], - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should not match when any sub-filter fails', () => { - const filter = { - and: [{ name: { eq: 'Test Object' } }, { isActive: { eq: false } }], - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(false); - }); - - it('should match when and array is empty', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { and: [] }, - }), - ).toBe(true); - }); - - it('should throw when and value is not an array', () => { - expect(() => - isMetadataRecordMatchingFilter({ - record, - filter: { and: 'invalid' } as any, - }), - ).toThrow('Unexpected value for "and" filter'); - }); - }); - - describe('or filter', () => { - it('should match when any sub-filter matches', () => { - const filter = { - or: [{ name: { eq: 'Wrong Name' } }, { name: { eq: 'Test Object' } }], - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should not match when no sub-filter matches', () => { - const filter = { - or: [{ name: { eq: 'Wrong' } }, { name: { eq: 'Also Wrong' } }], - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(false); - }); - - it('should match when or array is empty', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { or: [] }, - }), - ).toBe(true); - }); - - it('should treat or with an object as an and', () => { - const filter = { - or: { name: { eq: 'Test Object' } }, - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should throw when or value is neither array nor object', () => { - expect(() => - isMetadataRecordMatchingFilter({ - record, - filter: { or: 'invalid' } as any, - }), - ).toThrow('Unexpected value for "or" filter'); - }); - }); - - describe('not filter', () => { - it('should negate a matching filter', () => { - const filter = { not: { name: { eq: 'Test Object' } } }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(false); - }); - - it('should negate a non-matching filter', () => { - const filter = { not: { name: { eq: 'Wrong' } } }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should match when not contains an empty object', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { not: {} }, - }), - ).toBe(true); - }); - }); - - describe('implicit and (multi-key filter)', () => { - it('should treat multiple keys as an implicit AND', () => { - const filter = { - name: { eq: 'Test Object' }, - isActive: { eq: true }, - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should fail if any key in implicit AND does not match', () => { - const filter = { - name: { eq: 'Test Object' }, - isActive: { eq: false }, - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(false); - }); - }); - - describe('eq operator', () => { - it('should match equal string values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { eq: 'Test Object' } }, - }), - ).toBe(true); - }); - - it('should not match different string values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { eq: 'Other' } }, - }), - ).toBe(false); - }); - - it('should match equal boolean values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { isActive: { eq: true } }, - }), - ).toBe(true); - }); - }); - - describe('neq operator', () => { - it('should match when values are different', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { neq: 'Other' } }, - }), - ).toBe(true); - }); - - it('should not match when values are equal', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { neq: 'Test Object' } }, - }), - ).toBe(false); - }); - }); - - describe('in operator', () => { - it('should match when value is in the array', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { in: ['Test Object', 'Other'] } }, - }), - ).toBe(true); - }); - - it('should not match when value is not in the array', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { in: ['A', 'B'] } }, - }), - ).toBe(false); - }); - - it('should return false when in value is not an array', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { in: 'not-array' } } as any, - }), - ).toBe(false); - }); - }); - - describe('is operator', () => { - it('should match NULL for undefined values', () => { - expect( - isMetadataRecordMatchingFilter({ - record: { ...record, optional: undefined }, - filter: { optional: { is: 'NULL' } }, - }), - ).toBe(true); - }); - - it('should not match NULL for defined values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { is: 'NULL' } }, - }), - ).toBe(false); - }); - - it('should match NOT_NULL for defined values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { is: 'NOT_NULL' } }, - }), - ).toBe(true); - }); - - it('should not match NOT_NULL for undefined values', () => { - expect( - isMetadataRecordMatchingFilter({ - record: { ...record, optional: undefined }, - filter: { optional: { is: 'NOT_NULL' } }, - }), - ).toBe(false); - }); - }); - - describe('like operator', () => { - it('should match with wildcard prefix', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { like: '%Object' } }, - }), - ).toBe(true); - }); - - it('should match with wildcard suffix', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { like: 'Test%' } }, - }), - ).toBe(true); - }); - - it('should match with wildcards on both sides', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { like: '%est Obj%' } }, - }), - ).toBe(true); - }); - - it('should not match when pattern does not match', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { like: 'wrong%' } }, - }), - ).toBe(false); - }); - - it('should escape regex special characters in pattern', () => { - const specialRecord = { ...record, name: 'foo.bar' }; - - expect( - isMetadataRecordMatchingFilter({ - record: specialRecord, - filter: { name: { like: 'foo.bar' } }, - }), - ).toBe(true); - - // A dot in the pattern should NOT match any character - expect( - isMetadataRecordMatchingFilter({ - record: { ...record, name: 'fooXbar' }, - filter: { name: { like: 'foo.bar' } }, - }), - ).toBe(false); - }); - - it('should return false for non-string values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { like: '42' } }, - }), - ).toBe(false); - }); - }); - - describe('ilike operator', () => { - it('should match case-insensitively', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: { ilike: '%test object%' } }, - }), - ).toBe(true); - }); - - it('should escape regex special characters', () => { - const specialRecord = { ...record, name: 'foo(bar)' }; - - expect( - isMetadataRecordMatchingFilter({ - record: specialRecord, - filter: { name: { ilike: 'FOO(BAR)' } }, - }), - ).toBe(true); - }); - - it('should return false for non-string values', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { ilike: '42' } }, - }), - ).toBe(false); - }); - }); - - describe('gt operator', () => { - it('should match when value is greater', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { gt: 40 } }, - }), - ).toBe(true); - }); - - it('should not match when value is equal', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { gt: 42 } }, - }), - ).toBe(false); - }); - - it('should not match when value is less', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { gt: 50 } }, - }), - ).toBe(false); - }); - }); - - describe('gte operator', () => { - it('should match when value is greater or equal', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { gte: 42 } }, - }), - ).toBe(true); - }); - - it('should not match when value is less', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { gte: 43 } }, - }), - ).toBe(false); - }); - }); - - describe('lt operator', () => { - it('should match when value is less', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { lt: 50 } }, - }), - ).toBe(true); - }); - - it('should not match when value is equal', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { lt: 42 } }, - }), - ).toBe(false); - }); - }); - - describe('lte operator', () => { - it('should match when value is less or equal', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { lte: 42 } }, - }), - ).toBe(true); - }); - - it('should not match when value is greater', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { count: { lte: 41 } }, - }), - ).toBe(false); - }); - }); - - describe('unknown operator', () => { - it('should throw for unsupported operators', () => { - expect(() => - isMetadataRecordMatchingFilter({ - record, - filter: { name: { unknownOp: 'value' } } as any, - }), - ).toThrow('Unsupported filter operator'); - }); - }); - - describe('nested logical operators', () => { - it('should handle deeply nested and/or/not', () => { - const filter = { - and: [ - { - or: [ - { name: { eq: 'Wrong' } }, - { not: { isActive: { eq: false } } }, - ], - }, - { count: { gte: 40 } }, - ], - }; - - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - - it('should handle not with nested and', () => { - const filter = { - not: { - and: [{ name: { eq: 'Test Object' } }, { count: { gt: 100 } }], - }, - }; - - // name matches but count > 100 fails, so AND = false, NOT = true - expect(isMetadataRecordMatchingFilter({ record, filter })).toBe(true); - }); - }); - - describe('edge cases', () => { - it('should skip non-object field filters', () => { - // When fieldFilter is a primitive, it is skipped (returns true) - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: 'Test Object' }, - }), - ).toBe(true); - }); - - it('should skip undefined field filters', () => { - expect( - isMetadataRecordMatchingFilter({ - record, - filter: { name: undefined }, - }), - ).toBe(true); - }); - - it('should handle records with missing fields', () => { - expect( - isMetadataRecordMatchingFilter({ - record: { id: '1' }, - filter: { name: { eq: 'Test' } }, - }), - ).toBe(false); - }); - }); -}); diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util.ts b/packages/twenty-server/src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util.ts deleted file mode 100644 index b9da93ac6b2..00000000000 --- a/packages/twenty-server/src/engine/workspace-event-emitter/utils/is-metadata-record-matching-filter.util.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { isObject } from '@sniptt/guards'; -import { isDefined } from 'twenty-shared/utils'; - -type MetadataFilter = Record; - -type MetadataScalarFilter = { - is?: 'NULL' | 'NOT_NULL'; - eq?: unknown; - neq?: unknown; - in?: unknown[]; - like?: string; - ilike?: string; - gt?: number; - gte?: number; - lt?: number; - lte?: number; -}; - -const isEmptyFilter = (filter: MetadataFilter): boolean => - Object.keys(filter).length === 0; - -const isAndFilter = (filter: MetadataFilter): boolean => - 'and' in filter && isDefined(filter.and); - -const isOrFilter = (filter: MetadataFilter): boolean => - 'or' in filter && isDefined(filter.or); - -const isNotFilter = (filter: MetadataFilter): boolean => - 'not' in filter && isDefined(filter.not); - -const isImplicitAndFilter = (filter: MetadataFilter): boolean => - Object.keys(filter).length > 1; - -const escapeRegExp = (str: string): string => - str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - -export const isMetadataRecordMatchingFilter = ({ - record, - filter, -}: { - record: Record; - filter: MetadataFilter; -}): boolean => { - if (isEmptyFilter(filter)) { - return true; - } - - if (isImplicitAndFilter(filter)) { - return Object.entries(filter).every(([key, value]) => - isMetadataRecordMatchingFilter({ - record, - filter: { [key]: value }, - }), - ); - } - - if (isAndFilter(filter)) { - const andValue = filter.and; - - if (!Array.isArray(andValue)) { - throw new Error( - 'Unexpected value for "and" filter: ' + JSON.stringify(andValue), - ); - } - - return ( - andValue.length === 0 || - andValue.every((subFilter: MetadataFilter) => - isMetadataRecordMatchingFilter({ record, filter: subFilter }), - ) - ); - } - - if (isOrFilter(filter)) { - const orValue = filter.or; - - if (Array.isArray(orValue)) { - return ( - orValue.length === 0 || - orValue.some((subFilter: MetadataFilter) => - isMetadataRecordMatchingFilter({ record, filter: subFilter }), - ) - ); - } - - if (isObject(orValue)) { - return isMetadataRecordMatchingFilter({ - record, - filter: orValue as MetadataFilter, - }); - } - - throw new Error( - 'Unexpected value for "or" filter: ' + JSON.stringify(orValue), - ); - } - - if (isNotFilter(filter)) { - const notValue = filter.not; - - if (isObject(notValue) && isEmptyFilter(notValue as MetadataFilter)) { - return true; - } - - return !isMetadataRecordMatchingFilter({ - record, - filter: notValue as MetadataFilter, - }); - } - - return Object.entries(filter).every(([fieldName, fieldFilter]) => { - if (!isDefined(fieldFilter) || !isObject(fieldFilter)) { - return true; - } - - const recordValue = record[fieldName]; - - return isScalarValueMatchingFilter( - recordValue, - fieldFilter as MetadataScalarFilter, - ); - }); -}; - -const isScalarValueMatchingFilter = ( - value: unknown, - fieldFilter: MetadataScalarFilter, -): boolean => { - if ('is' in fieldFilter) { - if (fieldFilter.is === 'NULL') { - return !isDefined(value); - } - - return isDefined(value); - } - - if ('eq' in fieldFilter) { - return value === fieldFilter.eq; - } - - if ('neq' in fieldFilter) { - return value !== fieldFilter.neq; - } - - if ('in' in fieldFilter) { - if (!Array.isArray(fieldFilter.in)) { - return false; - } - - return fieldFilter.in.includes(value); - } - - if ('like' in fieldFilter) { - if (typeof value !== 'string') { - return false; - } - - const pattern = String(fieldFilter.like) - .split('%') - .map(escapeRegExp) - .join('.*'); - - return new RegExp(`^${pattern}$`).test(value); - } - - if ('ilike' in fieldFilter) { - if (typeof value !== 'string') { - return false; - } - - const pattern = String(fieldFilter.ilike) - .split('%') - .map(escapeRegExp) - .join('.*'); - - return new RegExp(`^${pattern}$`, 'i').test(value); - } - - if ('gt' in fieldFilter) { - return isDefined(value) && (value as number) > (fieldFilter.gt as number); - } - - if ('gte' in fieldFilter) { - return isDefined(value) && (value as number) >= (fieldFilter.gte as number); - } - - if ('lt' in fieldFilter) { - return isDefined(value) && (value as number) < (fieldFilter.lt as number); - } - - if ('lte' in fieldFilter) { - return isDefined(value) && (value as number) <= (fieldFilter.lte as number); - } - - throw new Error( - 'Unsupported filter operator: ' + JSON.stringify(fieldFilter), - ); -}; diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.module.ts b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.module.ts index 12fa9fa4517..bd3df630904 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.module.ts +++ b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.module.ts @@ -1,32 +1,10 @@ import { Global, Module } from '@nestjs/common'; -import { ProcessNestedRelationsV2Helper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper'; -import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper'; -import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper'; -import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; -import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; -import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; -import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -import { WorkspaceEventEmitterResolver } from 'src/engine/workspace-event-emitter/workspace-event-emitter.resolver'; -import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service'; @Global() @Module({ - imports: [ - SubscriptionsModule, - WorkspaceCacheModule, - WorkspaceManyOrAllFlatEntityMapsCacheModule, - NavigationMenuItemModule, - ], - providers: [ - WorkspaceEventEmitter, - WorkspaceEventEmitterService, - WorkspaceEventEmitterResolver, - ProcessNestedRelationsHelper, - ProcessNestedRelationsV2Helper, - CommonSelectFieldsHelper, - ], - exports: [WorkspaceEventEmitter, WorkspaceEventEmitterService], + providers: [WorkspaceEventEmitter], + exports: [WorkspaceEventEmitter], }) export class WorkspaceEventEmitterModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts index 8f0a852b0df..8cc2f8ceed3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service.ts @@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils'; import { FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { MetadataEventEmitter } from 'src/engine/metadata-event-emitter/metadata-event-emitter'; +import { MetadataEventEmitter } from 'src/engine/subscriptions/metadata-event/metadata-event-emitter'; import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; import {