Add Client Api generation (#17961)

## Add API client generation to SDK dev mode and refactor orchestrator
into step-based pipeline

### Why

The SDK dev mode lacked typed API client generation, forcing developers
to work without auto-generated GraphQL types when building applications.
Additionally, the orchestrator was a monolithic class that mixed watcher
management, token handling, and sync logic — making it difficult to
extend with new steps like client generation.

### How

- **Refactored the orchestrator** into a step-based pipeline with
dedicated classes: `CheckServer`, `EnsureValidTokens`,
`ResolveApplication`, `BuildManifest`, `UploadFiles`,
`GenerateApiClient`, `SyncApplication`, and `StartWatchers`. Each step
has typed input/output/status, managed by a new `OrchestratorState`
class.
- **Added `GenerateApiClientOrchestratorStep`** that detects
object/field schema changes and regenerates a typed GraphQL client (via
`@genql/cli`) into `node_modules/twenty-sdk/generated` for seamless
imports.
- **Replaced `checkApplicationExist`** with `findOneApplication` on both
server resolver and SDK API service, returning the entity data instead
of a boolean.
- **Added application token pair mutations**
(`generateApplicationToken`, `renewApplicationToken`) to the API
service, with the server now returning `ApplicationTokenPairDTO`
containing both access and refresh tokens.
- **Restructured the dev UI** into `dev/ui/components/` with dedicated
panel, section, and event log components.
- **Simplified `AppDevCommand`** from ~180 lines of watcher management
down to ~40 lines that delegate entirely to the orchestrator.
This commit is contained in:
Charles Bochet
2026-02-17 18:45:52 +01:00
committed by GitHub
parent 0891886aa0
commit c0cc0689d6
72 changed files with 2419 additions and 1422 deletions
@@ -26,7 +26,7 @@ import { UploadApplicationFileInput } from 'src/engine/core-modules/application/
import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/dtos/workspace-migration.dto';
import { ApplicationSyncService } from 'src/engine/core-modules/application/services/application-sync.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { AuthToken } from 'src/engine/core-modules/auth/dto/auth-token.dto';
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/dtos/application-token-pair.dto';
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
@@ -58,13 +58,13 @@ export class ApplicationDevelopmentResolver {
private readonly fileStorageService: FileStorageService,
) {}
@Mutation(() => AuthToken)
@Mutation(() => ApplicationTokenPairDTO)
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
async generateApplicationToken(
@Args() { applicationId }: GenerateApplicationTokenInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<AuthToken> {
return this.applicationTokenService.generateApplicationAccessToken({
): Promise<ApplicationTokenPairDTO> {
return this.applicationTokenService.generateApplicationTokenPair({
workspaceId,
applicationId,
});
@@ -57,22 +57,6 @@ export class ApplicationResolver {
return this.applicationService.findManyApplications(workspaceId);
}
@Query(() => Boolean)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
async checkApplicationExist(
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
@Args('id', { type: () => UUIDScalarType, nullable: true }) id?: string,
@Args('universalIdentifier', { type: () => UUIDScalarType, nullable: true })
universalIdentifier?: string,
) {
return await this.applicationService.checkApplicationExist({
id,
universalIdentifier,
workspaceId,
});
}
@Query(() => ApplicationDTO)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
@@ -130,20 +130,6 @@ export class ApplicationService {
});
}
async checkApplicationExist({
id,
universalIdentifier,
workspaceId,
}: {
id?: string;
universalIdentifier?: string;
workspaceId: string;
}) {
return isDefined(
await this.findOneApplication({ id, universalIdentifier, workspaceId }),
);
}
async findOneApplication({
id,
universalIdentifier,