Fix authContext missing in ALS WorkspaceContext for global datasource (#15810)

Fixes https://github.com/twentyhq/twenty/issues/15809

## Context
We recently introduced a global datasource now consuming workspace
context from ALS store however the authContext was missing (only the
workspaceId was there) which "broke" event emission, now missing the
workspaceMemberId

This PR adds the missing authContext so we can access from anywhere in
the datasource. We are still passing it as a parameters on repository
level for legacy but in theory we should be able to remove it from
everywhere and consume the context

<img width="929" height="253" alt="Screenshot 2025-11-13 at 18 58 16"
src="https://github.com/user-attachments/assets/3e04f264-95e6-4831-94f3-fc01603f19bd"
/>
This commit is contained in:
Weiko
2025-11-14 10:59:15 +01:00
committed by GitHub
parent bff079d3b5
commit 4dd6edb35a
4 changed files with 40 additions and 27 deletions
@@ -134,7 +134,7 @@ export abstract class CommonBaseQueryRunnerService<
if (isGlobalDatasourceEnabled) {
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
authContext.workspace.id,
authContext,
async () =>
this.executeQueryAndEnrichResults(
processedArgs,
@@ -16,10 +16,9 @@ import { EntitySchemaTransformer } from 'typeorm/entity-schema/EntitySchemaTrans
import { EntityMetadataNotFoundError } from 'typeorm/error/EntityMetadataNotFoundError';
import { EntityMetadataBuilder } from 'typeorm/metadata-builder/EntityMetadataBuilder';
import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import {
PermissionsException,
PermissionsExceptionCode,
@@ -67,6 +66,12 @@ export class GlobalWorkspaceDataSource extends DataSource {
});
}
get authContext(): WorkspaceAuthContext {
const context = getWorkspaceContext();
return context.authContext;
}
get featureFlagMap(): FeatureFlagMap {
const context = getWorkspaceContext();
@@ -82,18 +87,18 @@ export class GlobalWorkspaceDataSource extends DataSource {
override getRepository<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
permissionOptions?: RolePermissionConfig,
authContext?: AuthContext,
): WorkspaceRepository<Entity> {
const manager = this.createEntityManager();
return manager.getRepository(target, permissionOptions, authContext);
return manager.getRepository(target, permissionOptions, this.authContext);
}
override findMetadata(
target: EntityTarget<ObjectLiteral>,
): EntityMetadata | undefined {
const context = getWorkspaceContext();
const { workspaceId, metadataVersion } = context;
const { authContext, metadataVersion } = context;
const workspaceId = authContext.workspace.id;
const cacheKey = `${workspaceId}-${metadataVersion}`;
const cachedEntityMetadata = this.getCachedEntityMetadata(cacheKey);
@@ -123,12 +128,17 @@ export class GlobalWorkspaceDataSource extends DataSource {
}
const context = getWorkspaceContext();
const fullContext: WorkspaceInternalContext = {
...context,
eventEmitterService: this.eventEmitterService,
};
return new WorkspaceEntityManager(fullContext, this, queryRunner);
return new WorkspaceEntityManager(
{
workspaceId: context.authContext.workspace.id,
objectMetadataMaps: context.objectMetadataMaps,
featureFlagsMap: context.featureFlagsMap,
eventEmitterService: this.eventEmitterService,
},
this,
queryRunner,
);
}
override createQueryRunner(
@@ -2,13 +2,15 @@ import { Injectable, type Type } from '@nestjs/common';
import { type ObjectLiteral } from 'typeorm';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import {
type WorkspaceContextForStorage,
type WorkspaceContext,
withWorkspaceContext,
} from 'src/engine/twenty-orm/storage/workspace-context.storage';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
@@ -64,21 +66,21 @@ export class GlobalWorkspaceOrmManager {
}
async executeInWorkspaceContext<T>(
workspaceId: string,
authContext: WorkspaceAuthContext,
fn: () => T | Promise<T>,
): Promise<T> {
const context = await this.loadWorkspaceContext(workspaceId);
const context = await this.loadWorkspaceContext(authContext);
const globalDataSource =
this.globalWorkspaceDataSourceService.getGlobalWorkspaceDataSource();
if (
!globalDataSource.hasWorkspaceEntityMetadataCacheForVersion(
workspaceId,
authContext.workspace.id,
context.metadataVersion,
)
) {
await globalDataSource.buildWorkspaceMetadata(
workspaceId,
authContext.workspace.id,
context.metadataVersion,
context.objectMetadataMaps,
);
@@ -88,8 +90,9 @@ export class GlobalWorkspaceOrmManager {
}
private async loadWorkspaceContext(
workspaceId: string,
): Promise<WorkspaceContextForStorage> {
authContext: WorkspaceAuthContext,
): Promise<WorkspaceContext> {
const workspaceId = authContext.workspace.id;
const { objectMetadataMaps, metadataVersion } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
{
@@ -108,7 +111,7 @@ export class GlobalWorkspaceOrmManager {
});
return {
workspaceId,
authContext,
objectMetadataMaps,
metadataVersion,
featureFlagsMap,
@@ -2,11 +2,13 @@ import { AsyncLocalStorage } from 'async_hooks';
import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types';
import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export type WorkspaceContextForStorage = {
workspaceId: string;
export type WorkspaceContext = {
authContext: WorkspaceAuthContext;
objectMetadataMaps: ObjectMetadataMaps;
metadataVersion: number;
featureFlagsMap: Record<FeatureFlagKey, boolean>;
@@ -14,9 +16,9 @@ export type WorkspaceContextForStorage = {
};
export const workspaceContextStorage =
new AsyncLocalStorage<WorkspaceContextForStorage>();
new AsyncLocalStorage<WorkspaceContext>();
export const getWorkspaceContext = (): WorkspaceContextForStorage => {
export const getWorkspaceContext = (): WorkspaceContext => {
const context = workspaceContextStorage.getStore();
if (!context) {
@@ -29,14 +31,12 @@ export const getWorkspaceContext = (): WorkspaceContextForStorage => {
};
export const withWorkspaceContext = <T>(
context: WorkspaceContextForStorage,
context: WorkspaceContext,
fn: () => T | Promise<T>,
): T | Promise<T> => {
return workspaceContextStorage.run(context, fn);
};
export const setWorkspaceContext = (
context: WorkspaceContextForStorage,
): void => {
export const setWorkspaceContext = (context: WorkspaceContext): void => {
workspaceContextStorage.enterWith(context);
};