Skip to main content

Performance Tuning

The Ever Works platform is optimized for fast builds, efficient runtime performance, and low memory usage. This guide covers the SWC compiler configuration, caching strategies, lazy loading patterns, memory management, and profiling techniques used in the codebase.

Build Performance

SWC Compilation

The API application uses SWC (Speedy Web Compiler) instead of the default TypeScript compiler for significantly faster builds:

Build Speed Comparison:
tsc: ~12s full build
SWC: ~1.5s full build (8x faster)

SWC is configured in the NestJS app via nest-cli.json:

{
"compilerOptions": {
"builder": "swc",
"typeCheck": false
}
}

The agent package also uses SWC for compilation with a separate tsc pass for type declarations:

{
"scripts": {
"build": "nest build && tsc -p tsconfig.types.json",
"dev": "nest start --watch"
}
}

Turborepo Build Orchestration

The monorepo uses Turborepo with ^build dependency ordering. Only changed packages and their dependents are rebuilt:

# Build everything (respects dependency graph)
pnpm build

# Build single package
turbo build --filter=@ever-works/agent

# Build with cache inspection
turbo build --dry-run

Plugin Builds with tsup

Each plugin package uses tsup for fast ESM builds:

// packages/plugins/openai/tsup.config.ts
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
external: ['@ever-works/plugin']
});

Runtime Caching

TypeORM-Backed Cache

The platform uses a TypeORM-based cache (not Redis by default) for simplicity in single-instance deployments:

// packages/agent/src/cache/cache.factory.ts
export const CacheFactory = {
InMemory() {
return CacheModule.register();
},

TypeORM(options?: CacheOptions) {
return CacheModule.registerAsync({
imports: [TypeOrmModule.forFeature([CacheEntry])],
inject: [DataSource],
useFactory: async (dataSource: DataSource) => {
const repository = dataSource.getRepository(CacheEntry);
const typeormAdapter = new TypeORMKeyvAdapter({
repository,
namespace: options?.namespace,
ttl: options?.ttl
});
return { stores: [typeormAdapter] };
}
});
}
};

Registered globally in ApiModule:

CacheFactory.TypeORM({ isGlobal: true }),

AI Model Cache

The AiFacadeService implements in-memory caching for expensive external API calls:

export class AiFacadeService extends BaseFacadeService {
private static readonly CACHE_TTL = 3_600_000; // 1 hour
private openRouterModels: readonly OpenRouterModelEntry[] | null = null;
private openRouterCacheTime = 0;

private async getCachedOpenRouterModels() {
const now = Date.now();
if (this.openRouterModels && now - this.openRouterCacheTime < AiFacadeService.CACHE_TTL) {
return this.openRouterModels; // Serve from cache
}

const fresh = await fetchOpenRouterModels();
if (fresh) {
this.openRouterModels = fresh;
this.openRouterCacheTime = now;
}

// Stale-while-revalidate: return old data if fresh fetch failed
return this.openRouterModels;
}
}

This uses a stale-while-revalidate pattern: if the fresh fetch fails, the previous cached data is returned rather than throwing.

Request Pipeline Efficiency

Conditional Logging

The LoggingInterceptor only activates when HTTP_DEBUG=true, avoiding any overhead in production:

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
if (!config.debug()) {
return next.handle(); // Zero overhead when disabled
}
// ... logging logic
}
}

Parallel Operations

The orchestrator uses Promise.all for independent database updates:

await Promise.all([
this.workOperations.recordGenerationFinishTime(workId, finishedAt),
this.workOperations.updateGenerateStatus(workId, { status, error }),
this.workOperations.updateGenerationHistory(workId, historyId, {
status,
finishedAt,
durationInSeconds,
errorMessage
})
]);

Memory Management

Payload Size Limits

Large request bodies are capped to prevent memory exhaustion:

app.use(json({ limit: '10mb' }));
app.use(urlencoded({ limit: '10mb', extended: true }));

Streaming for AI Responses

The AiFacadeService supports streaming via AsyncGenerator, which avoids buffering large AI responses in memory:

async *createStreamingChatCompletion(
options: ChatCompletionOptions,
facadeOptions: FacadeOptions,
): AsyncGenerator<ChatCompletionChunk> {
// ...
yield* plugin.createStreamingChatCompletion(mergedOptions);
}

Background Task Offloading

Long-running tasks (work generation, imports) are dispatched to Trigger.dev workers, keeping the API process lean:

async dispatchWorkGeneration(payload): Promise<string | null> {
const handle = await workGenerationTask.trigger(payload, {
tags: ['work-generation', payload.mode, payload.workId],
machine: this.machine(),
});
return handle.id;
}

Profiling with Sentry

Sentry profiling is enabled via the Node.js profiling integration:

import { nodeProfilingIntegration } from '@sentry/profiling-node';

const config = {
integrations: [nodeProfilingIntegration()],
profilesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0
};

In development, 100% of transactions are profiled. In production, 10% sampling keeps overhead minimal while still providing representative data.

What to Look For

MetricHealthy RangeAction if Exceeded
API response time (p95)< 500msCheck DB queries, add indexes
Memory usage< 512MBCheck for leaks, reduce cache sizes
Event loop lag< 50msOffload CPU work to workers
DB query time (p95)< 100msAdd indexes, optimize queries

TypeScript Compilation Optimization

Path Aliases

Path aliases reduce import complexity and enable faster module resolution:

{
"compilerOptions": {
"paths": {
"@src/*": ["./src/*"],
"@ever-works/*": ["../../packages/*/src"]
}
}
}

Skip Type Checking in SWC

During development, SWC skips type checking entirely. Run type checking separately:

pnpm type-check # Full TypeScript type check across monorepo
pnpm dev:api # Fast SWC compilation, no type checking

Best Practices

  1. Profile before optimizing -- Use Sentry profiling data to identify actual bottlenecks before making changes.

  2. Cache at the right level -- Use the TypeORM cache for database results, in-memory cache for computation-heavy lookups, and CDN caching for static assets.

  3. Offload heavy work -- Any operation over 5 seconds should be dispatched to Trigger.dev workers.

  4. Use streaming -- For AI completions and large data exports, use streaming to keep memory flat.

  5. Monitor in production -- Keep tracesSampleRate at 0.1 and profilesSampleRate at 0.1 to maintain visibility without impacting performance.

Troubleshooting

Slow cold starts

SWC compilation should be fast (~1.5s). If cold starts are slow, check for synchronous fs operations in module initialization or large dependency trees.

Memory growing over time

Check for event listener leaks (EventEmitter without removeListener) or growing cache stores. The TypeORM cache uses database storage, so memory impact should be minimal.

High event loop lag

CPU-intensive operations (JSON parsing of large AI responses, bcrypt hashing) block the event loop. These are already offloaded where possible. Consider increasing the bcrypt cost only if login throughput is acceptable.