Flat entity maps cache generic service + runner dynamically retrieving invalidating update cache + view service v2 refactor (#14508)

# Introduction
Migrate previous runner only iterating on `flatObjectMetadataMaps` to
`allFlatEntityMaps`.
Refactored the optimistic to be handled inside the actions handler

## Workspace flat map cache
Introducing a new service and registry, that will dynamically retrieve
and or recompute requested cache when called

## Runner refactor
Runner now dynamically invalidate updated cache at the end of the
transaction

close
https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=129136356&issue=twentyhq%7Ccore-team-issues%7C1492
close
https://github.com/orgs/twentyhq/projects/1/views/8?pane=issue&itemId=129136210&issue=twentyhq%7Ccore-team-issues%7C1494
This commit is contained in:
Paul Rastoin
2025-09-16 14:39:42 +02:00
committed by GitHub
parent 8f42c33bd4
commit 371c26bc9b
79 changed files with 1534 additions and 575 deletions
@@ -0,0 +1,49 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { DiscoveryService } from '@nestjs/core';
import { AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { WORKSPACE_FLAT_MAP_CACHE_KEY } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
export class WorkspaceFlatMapCacheRegistryService implements OnModuleInit {
private readonly cacheServiceMap = new Map<
keyof AllFlatEntityMaps,
WorkspaceFlatMapCacheService<FlatEntityMaps<AllFlatEntities>>
>();
constructor(private readonly discoveryService: DiscoveryService) {}
async onModuleInit() {
this.discoverAndRegisterCacheServices();
}
private discoverAndRegisterCacheServices(): void {
const providers = this.discoveryService.getProviders();
providers.forEach((wrapper) => {
const { instance, metatype } = wrapper;
if (!instance || !metatype) {
return;
}
const cacheKey = Reflect.getMetadata(
WORKSPACE_FLAT_MAP_CACHE_KEY,
metatype,
);
if (cacheKey && instance instanceof WorkspaceFlatMapCacheService) {
this.cacheServiceMap.set(cacheKey, instance);
}
});
}
getCacheService(
flatEntityName: keyof AllFlatEntityMaps,
): WorkspaceFlatMapCacheService<FlatEntityMaps<AllFlatEntities>> | undefined {
return this.cacheServiceMap.get(flatEntityName);
}
}
@@ -99,6 +99,7 @@ export abstract class WorkspaceFlatMapCacheService<
}: {
workspaceId: string;
}): Promise<void> {
await this.beforeInvalidateCache({ workspaceId });
const { flatMapKey, hashKey } = this.buildRemoteCacheKeys({ workspaceId });
await this.cacheStorageService.del(flatMapKey);
@@ -108,6 +109,19 @@ export abstract class WorkspaceFlatMapCacheService<
this.localCacheHashes.delete(workspaceId);
await this.recomputeAndStoreInCache({ workspaceId });
await this.afterInvalidateCache({ workspaceId });
}
protected async beforeInvalidateCache(_args: {
workspaceId: string;
}): Promise<void> {
return;
}
protected async afterInvalidateCache(_args: {
workspaceId: string;
}): Promise<void> {
return;
}
private getFlatMapCacheKey(): string {
@@ -116,7 +130,7 @@ export abstract class WorkspaceFlatMapCacheService<
this.constructor,
);
if (!cacheKey) {
if (!isDefined(cacheKey)) {
throw new WorkspaceFlatMapCacheException(
`${this.constructor.name} must be decorated with @WorkspaceFlatMapCache('cacheKey')`,
WorkspaceFlatMapCacheExceptionCode.MISSING_DECORATOR,