Skip to content

Pipes

Pipe는 Guard를 통과한 IPC 요청의 인자를 변환하거나 검증하는 파이프라인이다. 핸들러가 실행되기 전에 역직렬화, 유효성 검사 등을 수행한다.

파이프라인 흐름

요청 → Guard(canActivate) → Pipe(transform) → Handler → 응답
         ↓ (실패)            ↓ (에러)          ↓ (에러)
    ExceptionHandler    ExceptionHandler   ExceptionHandler
         ↓                   ↓                  ↓
       응답                 응답                응답
  1. Guard를 모두 통과한다
  2. contract에 내장된 schema가 있으면 프레임워크가 먼저 강제한다 (schema.parse(args[0]))
  3. 글로벌 Pipe를 순서대로 실행한다
  4. 핸들러별 Pipe(@UsePipes)를 순서대로 실행한다
  5. 변환된 인자로 핸들러를 실행한다

Pipe에서 throw하면 ExceptionHandler가 처리한다.

IpcPipe 인터페이스

ts
interface IpcPipe {
  transform(args: unknown[], ctx: IpcContext): unknown[] | Promise<unknown[]>;
}
매개변수타입설명
argsunknown[]핸들러에 전달될 인자 배열
ctxIpcContextIPC 요청 컨텍스트 (채널, 윈도우 등)

반환값은 다음 Pipe 또는 핸들러에 전달할 변환된 인자 배열이다.

NOTE

스키마 검증은 Pipe를 직접 만들 필요가 없다 — handle(channel, { schema })로 contract에 내장하면 프레임워크가 모든 파이프보다 먼저 강제한다. Contract 참조. Pipe는 스키마로 표현할 수 없는 변환·검증에 사용한다.

커스텀 Pipe 작성

특정 필드를 검증하는 Pipe 예시:

ts
import type { IpcPipe, IpcContext } from '@repo/electron-ipc';

class CwdPipe implements IpcPipe {
  transform(args: unknown[], _ctx: IpcContext) {
    const params = args[0];
    if (params && typeof params === 'object' && 'cwd' in params) {
      const cwd = (params as { cwd: unknown }).cwd;
      if (typeof cwd === 'string' && cwd.length > 0) {
        validateCwd(cwd);
      }
    }
    return args;
  }
}

적용 방법

글로벌 Pipe

createApp()pipes 배열로 전달한다. 모든 IPC 요청에 적용된다.

ts
createApp({
  guards: [new SenderGuard()],
  pipes: [new NormalizePipe()],
  exceptionHandler: new ErrorHandler(),
  modules: [WindowModule, MyModule],
});

핸들러별 Pipe

@UsePipes() 데코레이터로 특정 핸들러에만 Pipe를 적용한다. 글로벌 Pipe 이후에 실행된다.

ts
import { Controller, UsePipes } from '@repo/electron-ipc';

@Controller(myContract)
class MyController {
  @UsePipes(new CwdPipe())
  startSession(params: SessionParams) {
    // CwdPipe를 통과한 후 실행된다
    return this.service.start(params);
  }

  listSessions() {
    // CwdPipe가 적용되지 않는다
    return this.service.list();
  }
}

실행 순서

글로벌 Pipe → 핸들러별 Pipe 순서로 실행된다. 각 Pipe의 반환값이 다음 Pipe의 입력이 된다.

ts
// contract schema: 있으면 항상 먼저
// 글로벌: [NormalizePipe]
// 핸들러별: [CwdPipe]

// 실행 순서:
// 1. schema.parse(args[0])              → parsedArgs (contract에 schema가 있을 때)
// 2. NormalizePipe.transform(parsedArgs, ctx)
// 3. CwdPipe.transform(..., ctx)
// 4. handler(...)

효과

Pipe를 도입하면 핸들러에서 역직렬화와 검증 보일러플레이트가 제거된다:

ts
// Before — 핸들러가 파싱과 검증을 직접 수행
startSession(params: unknown) {
  const parsed = SessionSchema.parse(params);
  validateCwd(parsed.cwd);
  return this.service.start(parsed);
}

// After — 스키마는 contract가 강제하고, 나머지 검증만 Pipe가 처리한다
// contract: handle<[SessionParams], void>('session:start', { schema: SessionSchema })
@UsePipes(new CwdPipe())
startSession(params: SessionParams) {
  return this.service.start(params);
}

다음 단계