migrate-sdk
Learn

Errors and intentional skips

Distinguish live failures, durable item errors, diagnostics, and successful skip results.

A Migration Item Error is a normalized failure record stored in Migration Item State. It explains why one item failed after the execution has ended. It is distinct from the live Effect error that interrupted processing and from diagnostic context recorded alongside that failure.

The failure boundary determines what the runtime can preserve and whether execution can continue. An item-level failure can become a durable outcome; a failure to persist that outcome cannot safely be treated the same way.

Live typed failures

Effect represents expected failures in the error parameter of Effect<Success, Error, Requirements>. A tagged error allows process code to distinguish failure categories and inspect their properties before choosing recovery.

The SDK exposes several boundary-specific error types:

Error typeBoundary it describes
SourceErrorReading, locating, or normalizing source data.
DestinationErrorDestination-side work performed by a process or rollback.
MigrationStoreErrorReading or writing durable migration state.
MigrationReferenceLookupErrorResolving another definition's saved result or creating its reference stub.
ProcessBatchContractErrorA batch callback does not supply exactly one settlement per item.

Planning, runtime, and rollback preflight have additional typed errors. Their purpose is to identify an invalid operation or unavailable capability before it can safely proceed.

User-authored helpers can expose more specific tagged errors. Preserve the fields needed for decisions such as retry eligibility, HTTP status handling, or a recoverable conflict. Map those errors to an SDK boundary error when required, retaining their cause for live inspection.

Programming defects and interruptions are separate from expected typed failures. Ordinary Effect.retry does not retry them. Do not convert every unexpected defect into a transient provider failure.

Durable Migration Item Errors

The runtime normalizes an item failure into these fields:

FieldMeaning
kindThe runtime boundary: source, tracking, process, or destination.
errorTagThe live error's tag or a boundary-specific validation tag.
messageA durable human-readable explanation.
detailsOptional Migration Item Error Details, each with a message and optional field path.

Raw causes, thrown objects, and complete provider responses are not copied into this record. Put essential failure information in the message; save extra context explicitly as a journal diagnostic. Arbitrary properties on a custom error are not automatically preserved as error details.

The runtime boundary and error class are separate. For example, a DestinationError returned through a process is currently saved with kind: "process" and errorTag: "DestinationError".

Failure lifecycle

Failure pointRuntime behavior
Source cursor readA SourceError can stop traversal before a usable item is available.
Source identity lookupAn existing targeted item can retain failed state when its current source data cannot be read.
Source payload decodingSaves a failed item with SourcePayloadSchemaError and field details.
Process executionNormalizes ordinary typed failures into failed item state; other admitted items can continue.
Tracking validationSaves TrackingRecordContractError for a missing, duplicate, or invalid staged record.
Migration Store accessMigrationStoreError escapes item recovery because outcome persistence is unavailable.

A failed item can retain destination evidence from partial work. Failure does not undo writes. Read Tracking for preservation rules and Rollback for compensation.

A completed source pass may contain failed items. Completion describes coverage, while item state describes processing outcomes.

Destination Journal Diagnostics

A Destination Journal Diagnostic records explicitly marked context inside the Pipeline Execution Scope. It contains severity (info, warning, or error), a message, and optional JSON-object details. These details differ from validation-oriented Migration Item Error Details.

Tracking.logDiagnostic appends that entry and emits an Effect log. The journal append is not suppressed by the configured minimum log level. Ordinary Effect.log calls remain observability output and do not become durable item evidence.

Diagnostics can accompany success, failure, or skip. They explain conditions but do not establish that a destination change occurred. Operator-facing Migration Messages project saved item errors, state reasons, and journal diagnostics for later inspection.

Skip an item on purpose

Skip Item is a successful Process Pipeline result with a reason. skipItem(reason) constructs it; returning it records skipped item state without requiring a newly staged tracking record.

skip-result.ts
import { skipItem } from "migrate-sdk";

export const excludedByPolicy = skipItem("Excluded by migration policy");

The enclosing process must explicitly return the skip. A nested helper's result does not automatically exit its caller. Returning a skip before destination work prevents that work; returning it afterward does not compensate completed writes. Actual failures use the error channel.

Use a skip for an intentional policy outcome, an error for work that failed, and a diagnostic for context. These distinctions drive retry selection and operational reporting.

Give API failures a useful type

The API cookbook applies these boundaries to transport code. The following optional helper shows a more detailed classification: transient request failure, invalid JSON, and schema-invalid payload remain different error types.

Implementation example: classified API failures

The endpoint contract is illustrative: a user-supplied GET /authors/:id returns { id, name }. The module performs no requests when imported. Adapt transient classification to the provider's documented behavior, then apply a bounded retry strategy.

recovery-api-errors.ts
import { , ,  } from "effect";

export class  extends .("ApiRequestError")<{
  readonly : string;
  readonly : boolean;
  readonly ?: number;
  readonly ?: number;
}> {}

export class  extends .("InvalidApiResponse")<{
  readonly : string;
}> {}

const  = (: string | null): number | undefined => {
  if ( === null) {
    return;
  }
  const  = ();
  if (.() &&  >= 0) {
    return  * 1000;
  }
  const  = .();
  return .() ? .(0,  - .()) : ;
};

const  = (: unknown): boolean => {
  if (!( instanceof )) {
    return false;
  }
  if (. === "TimeoutError") {
    return true;
  }
  const  = .;
  return (
    typeof  === "object" &&
     !== null &&
    "code" in  &&
    ["ECONNRESET", "ETIMEDOUT", "EAI_AGAIN"].((.))
  );
};

// Contract: GET {baseUrl}/authors/{id} returns { id: string, name: string }.
// Supply your own API URL. This module makes no requests when imported.
const  = (: string, : string) =>
  .(function* () {
    const  = yield* .({
      : () =>
        (`${}/authors/${()}`, {
          : .([, .(10_000)]),
        }),
      : () =>
        new ({
          : "Author request did not complete",
          : (),
        }),
    });

    if (!.) {
      const  = (
        ..("retry-after")
      );
      yield* .(
        () => .?.() ?? .()
      ).(.);
      return yield* .(
        new ({
          : `Author request returned HTTP ${.}`,
          : .,
          : [408, 429, 502, 503, 504].(.),
          ...( ===  ? {} : {  }),
        })
      );
    }

    const  = yield* .({
      : () => .(),
      : () =>
        new ({
          : "Author response body did not complete",
          : (),
        }),
    });

    return yield* .({
      : (): unknown => .(),
      : () =>
        new ({
          : "Author response was not valid JSON",
        }),
    });
  });

export const  = .({
  : .,
  : .,
});

export const  = (: string, : string) =>
  (, ).(
    .(.())
  );

On this page