Files
twenty/packages/twenty-server
Sonarly Claude Code 86fea3aaf2 Slow POST /metadata: SELECT DISTINCT on fieldMetadata takes 1.4s, total 3.7s
https://sonarly.com/issue/3801?type=bug

The `POST /metadata` GraphQL endpoint takes 3.67s due to a `SELECT DISTINCT` query on `core.fieldMetadata` (1.37s) generated by NestJS-Query's `@CursorConnection` auto-resolver, plus ~2.3s of post-query GraphQL field resolution overhead.

Fix: ## Root Cause

`@CursorConnection('fields', () => FieldMetadataDTO)` on `ObjectMetadataDTO` causes NestJS-Query to auto-generate a resolver that executes:

```sql
SELECT DISTINCT "fields"."workspaceId", ... /* 26 columns, 4 JSONB */
FROM "core"."fieldMetadata" "fields"
WHERE "fields"."workspaceId" = $1
  AND "fields"."objectMetadataId" IN ($2, ..., $39)
LIMIT 1001
```

The `DISTINCT` deduplication across 4 JSONB columns takes 1,371ms. An optimized `fieldsList` ResolveField already exists — it uses a DataLoader backed by `WorkspaceManyOrAllFlatEntityMapsCacheService` (local 100ms TTL → Redis → provider recompute).

## Fix

### 1. Remove the slow `@CursorConnection` decorator

```typescript file=packages/twenty-server/src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto.ts
// REMOVED:
// @CursorConnection('fields', () => FieldMetadataDTO)
// Also removed: import { FieldMetadataDTO } from '...field-metadata.dto'
@CursorConnection('indexMetadatas', () => IndexMetadataDTO)
export class ObjectMetadataDTO { ... }
```

The `fieldsList` ResolveField (DataLoader path) remains as the correct, cache-backed way to fetch fields.

### 2. Update the internal REST API caller

```typescript file=packages/twenty-server/src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils.ts lines=70-76
const fieldsPart = selector?.fields
  ? `
  fieldsList {
    ${fieldsSelection}
  }
`
  : '';
```

### 3. Update the Zapier integration (3 files)

`requestDb.ts` — replace the cursor connection query with `fieldsList`:
```typescript
fieldsList {
  type name label description isNullable isActive defaultValue
}
```

`data.types.ts` — update `Node` type:
```typescript
export type Node = {
  nameSingular: string;
  namePlural: string;
  labelSingular: string;
  fieldsList: NodeField[];  // was: fields: { edges: { node: NodeField }[] }
};
// NodeField gains: isActive: boolean
```

`computeInputFields.ts` — preserve active-field-only filtering at the client level:
```typescript
for (const nodeField of node.fieldsList.filter((f) => f.isActive)) {
  // was: for (const field of node.fields.edges) { const nodeField = field.node;
```

## Impact

- Eliminates the 1,371ms `SELECT DISTINCT` query entirely.
- REST API response shape changes: `data.objects.edges[n].node.fields.edges[m].node` → `data.objects.edges[n].node.fieldsList[m]`. External REST consumers using the raw `/metadata/objects` endpoint with field selectors will need to update their response parsing.
- The top-level `fields(paging: ...)` GraphQL query (on `FieldMetadataDTO`) is a separate resolver and is **not** affected.
2026-03-06 08:01:07 +00:00
..
2025-12-17 08:48:17 +01:00