Skip to content

Compute output schema on frontend#16530

Merged
thomtrp merged 3 commits intomainfrom
tt-move-step-output-schema-computation-to-frontend
Dec 15, 2025
Merged

Compute output schema on frontend#16530
thomtrp merged 3 commits intomainfrom
tt-move-step-output-schema-computation-to-frontend

Conversation

@thomtrp
Copy link
Copy Markdown
Contributor

@thomtrp thomtrp commented Dec 12, 2025

Fixes twentyhq/core-team-issues#1382

Current issue : all step output schemas are computed and stored on backend side. Which means that, when the database schema is updated - like a field creation - steps needs to be deleted an recreated. Which is invisible to users.

Solution : schema generation is moved on frontend side

  1. Coming on the page the first time, the schema is populated for all steps except a few ones that are handled differently (Code, Webhook, http node, Agent)

  2. A separated state allow to determine if a step needs a recomputation.

  3. The user only needs a refresh to see the whole schema re-computed

Follow-up:

  • check if remaining backend steps could be moved to runtime computation. But Code will still require storage.
  • Clean backend service that is not used anymore

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Comment on lines +80 to +82
if (!shouldRecompute && shouldComputeOnFrontend) {
return;
}

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 24 files

Prompt for AI agents (all 5 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateRecordEventOutputSchema.ts">

<violation number="1" location="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateRecordEventOutputSchema.ts:178">
P2: Switch statement does not handle all `DatabaseEventAction` values (`RESTORED` and `UPSERTED` are missing). These cases silently fall through to the default, which may mask unintended behavior. Consider explicitly handling all cases or using a TypeScript exhaustiveness check.</violation>
</file>

<file name="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateFakeValue.ts">

<violation number="1" location="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateFakeValue.ts:38">
P2: Using `split(&#39;:&#39;)` to parse property types is fragile. If the value type contains a colon (e.g., for nested object types), only the first two parts will be captured, corrupting the type definition. Consider using `split(&#39;:&#39;)` with a limit or a different parsing strategy.</violation>
</file>

<file name="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateRecordOutputSchema.ts">

<violation number="1" location="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateRecordOutputSchema.ts:28">
P2: [gpt-5.2] `isActive` is nullable/optional on `FieldMetadataItem`, but this falsy check treats `null`/`undefined` as inactive and will exclude fields unexpectedly. Prefer an explicit `=== false` check so only explicitly disabled fields are excluded.</violation>

<violation number="2" location="packages/twenty-front/src/modules/workflow/workflow-variables/utils/generate/generateRecordOutputSchema.ts:117">
P2: [gpt-5.2] Unconditionally writing `${relationName}Id` can overwrite an existing field with the same name (order-dependent), producing an unstable/incorrect schema. Guard against collisions and only add the synthetic id field when it doesn’t already exist.</violation>
</file>

<file name="packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts">

<violation number="1" location="packages/twenty-front/src/modules/workflow/workflow-variables/hooks/useStepsOutputSchema.ts:47">
P1: Inconsistent handling of backend-computed schemas for steps vs triggers. For steps with types like CODE, HTTP_REQUEST, AI_AGENT, WEBHOOK, ITERATOR, `computeStepOutputSchema` returns `undefined`, resulting in an empty output schema. The trigger handling correctly falls back to `trigger.settings?.outputSchema`. Steps should use the same pattern: `shouldComputeOnFrontend ? computeStepOutputSchema({...}) : step.settings?.outputSchema`.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 12, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:32026

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Copy Markdown
Contributor

@martmull martmull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey nice changes. I see that some code has been duplicated I think it worth factorize directly using twenty-shared when possible

}): OutputSchemaV2 | undefined => {
const stepType = step.type;

if (PERSISTED_OUTPUT_SCHEMA_TYPES.includes(stepType)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use utils shouldComputeOutput...

Comment on lines 3 to 33
@@ -25,6 +27,7 @@ export type FieldOutputSchemaV2 = RecordFieldLeaf | RecordFieldNode;

export type RecordOutputSchemaV2 = {
object: {
icon?: string;
label: string;
objectMetadataId: string;
isRelationField?: boolean;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to factorize all those types in twenty-shared (in another PR obviously)

Comment on lines +1 to +34
import { FieldMetadataType } from 'twenty-shared/types';

export type FakeValueTypes =
| string
| number
| boolean
| Date
| FakeValueTypes[]
| FieldMetadataType
| { [key: string]: FakeValueTypes }
| null;

type TypeClassification = 'Primitive' | 'FieldMetadataType';

const generatePrimitiveValue = (valueType: string): FakeValueTypes => {
if (valueType === 'string') {
return 'My text';
} else if (valueType === 'number') {
return 20;
} else if (valueType === 'boolean') {
return true;
} else if (valueType === 'Date') {
return new Date();
} else if (valueType.endsWith('[]')) {
const elementType = valueType.replace('[]', '');

return Array.from({ length: 3 }, () => generateFakeValue(elementType));
} else if (valueType.startsWith('{') && valueType.endsWith('}')) {
const objData: Record<string, FakeValueTypes> = {};

const properties = valueType
.slice(1, -1)
.split(';')
.map((property) => property.trim())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move that to twenty-shared instead of copy paste

import { type FindRecordsOutputSchema } from '@/workflow/workflow-variables/types/FindRecordsOutputSchema';
import { generateRecordOutputSchema } from '@/workflow/workflow-variables/utils/generate/generateRecordOutputSchema';

export const generateFindRecordsOutputSchema = (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Comment on lines +17 to +22
const camelToTitleCase = (camelCaseText: string): string =>
capitalize(
camelCaseText
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase()),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same go to twenty-shared

Comment on lines +52 to +62
const stepOutputSchema: StepOutputSchemaV2 = {
id: step.id,
name: step.name,
type: step.type,
icon: getActionIcon(step.type),
outputSchema: (outputSchema ?? {}) as OutputSchemaV2,
};

set(stepsOutputSchemaFamilyState(stepKey), stepOutputSchema);
set(shouldRecomputeOutputSchemaFamilyState(stepKey), false);
});

This comment was marked as outdated.

@thomtrp thomtrp force-pushed the tt-move-step-output-schema-computation-to-frontend branch from 21e1e2a to 3ff213d Compare December 15, 2025 08:03
Comment on lines +47 to +50
const outputSchema = computeStepOutputSchema({
step,
objectMetadataItems,
});

This comment was marked as outdated.

@thomtrp thomtrp force-pushed the tt-move-step-output-schema-computation-to-frontend branch from 3ff213d to de31b8b Compare December 15, 2025 08:27
@thomtrp thomtrp force-pushed the tt-move-step-output-schema-computation-to-frontend branch 3 times, most recently from 12b1d60 to 0d6d579 Compare December 15, 2025 14:35
@thomtrp thomtrp force-pushed the tt-move-step-output-schema-computation-to-frontend branch from 0d6d579 to c7817cd Compare December 15, 2025 15:00
@thomtrp thomtrp merged commit 95e0793 into main Dec 15, 2025
71 checks passed
@thomtrp thomtrp deleted the tt-move-step-output-schema-computation-to-frontend branch December 15, 2025 15:20
@twenty-eng-sync
Copy link
Copy Markdown

Hey @thomtrp! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refacto - Move step output schema logic at runtime

2 participants