[POC] Real-Time (#16633)
https://github.com/user-attachments/assets/3ad7a4f3-d08b-4a1c-b3c2-bb42ef9f3575 Real time POC. Next steps: - find out the correct API for subscription. Should be triggered directly in query hooks (useFindManyRecords, useLazy...) - improve query matching
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('SubscriptionMatch')
|
||||
export class SubscriptionMatchDTO {
|
||||
@Field(() => String)
|
||||
id: string;
|
||||
}
|
||||
|
||||
@ObjectType('SubscriptionMatches')
|
||||
export class SubscriptionMatchesDTO {
|
||||
@Field(() => [SubscriptionMatchDTO])
|
||||
subscriptions: SubscriptionMatchDTO[];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
|
||||
@InputType()
|
||||
export class SubscriptionInput {
|
||||
@Field()
|
||||
id: string;
|
||||
|
||||
@Field()
|
||||
query: string;
|
||||
|
||||
@Field(() => [DatabaseEventAction], { nullable: true })
|
||||
selectedEventActions?: DatabaseEventAction[];
|
||||
}
|
||||
@@ -1,11 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum';
|
||||
import { FieldNode, OperationDefinitionNode, parse } from 'graphql';
|
||||
|
||||
import { RedisClientService } from 'src/engine/core-modules/redis-client/redis-client.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { OnDbEventDTO } from 'src/engine/subscriptions/dtos/on-db-event.dto';
|
||||
import { SubscriptionInput } from 'src/engine/subscriptions/dtos/subscription.input';
|
||||
import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum';
|
||||
|
||||
@Injectable()
|
||||
export class SubscriptionService {
|
||||
constructor(private readonly redisClient: RedisClientService) {}
|
||||
constructor(
|
||||
private readonly redisClient: RedisClientService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
private getSubscriptionChannel({
|
||||
channel,
|
||||
@@ -47,4 +56,82 @@ export class SubscriptionService {
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
public async isSubscriptionMatchingEvent(
|
||||
subscription: SubscriptionInput,
|
||||
event: OnDbEventDTO,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
const objectName = this.parseQueryObjectName(subscription.query);
|
||||
|
||||
if (!objectName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
subscription.selectedEventActions &&
|
||||
!subscription.selectedEventActions.includes(event.action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const eventObjectMetadata = Object.values(flatObjectMetadataMaps.byId).find(
|
||||
(metadata: FlatObjectMetadata) =>
|
||||
metadata.nameSingular === event.objectNameSingular,
|
||||
);
|
||||
|
||||
if (!eventObjectMetadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const queryObjectNameLower = objectName.toLowerCase();
|
||||
const eventNameSingularLower =
|
||||
eventObjectMetadata.nameSingular.toLowerCase();
|
||||
const eventNamePluralLower = eventObjectMetadata.namePlural.toLowerCase();
|
||||
|
||||
return (
|
||||
queryObjectNameLower === eventNameSingularLower ||
|
||||
queryObjectNameLower === eventNamePluralLower
|
||||
);
|
||||
}
|
||||
|
||||
private parseQueryObjectName(queryString: string): string | null {
|
||||
try {
|
||||
const { query } = JSON.parse(queryString) as {
|
||||
query: string;
|
||||
variables?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
const ast = parse(query);
|
||||
|
||||
const firstOperation = ast.definitions.find(
|
||||
(def): def is OperationDefinitionNode =>
|
||||
def.kind === 'OperationDefinition',
|
||||
);
|
||||
|
||||
if (!firstOperation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootSelection = firstOperation.selectionSet.selections[0];
|
||||
|
||||
if (rootSelection.kind !== 'Field') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootField = rootSelection as FieldNode;
|
||||
|
||||
return rootField.name.value;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
|
||||
import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
|
||||
|
||||
@Module({
|
||||
imports: [RedisClientModule],
|
||||
imports: [RedisClientModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
|
||||
providers: [SubscriptionService],
|
||||
exports: [SubscriptionService],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user