SignatureKit
Signers

Canal de erros tipados

Toda falha do SignatureKit é um SignatureKitError no canal de erros do Effect — discrimine pelo code com Effect.catchTag/catchIf, sem throw.

Toda falha no core, no signer A1 e nos formatos é um SignatureKitError no canal de erros do Effect — não uma exceção lançada com throw. O tipo de retorno a carrega (Effect<SignatureArtifact, SignatureKitError>), então o tratamento é exaustivo e verificado pelo compilador: você discrimina pelo code, e tudo o que não tratar continua se propagando, tipado.

Canal de erros tipados

As falhas não são lançadas com throw: elas vivem no canal de erros do Effect como um SignatureKitError com um code literal. O compilador obriga você a tratá-las — ou a deixá-las se propagar, tipadas — em vez de descobri-las em tempo de execução.

O formato do erro

SignatureKitError é uma classe com um _tag fixo igual a "SignatureKitError". Seus campos:

  • code — um dos 21 literais "signature-kit.*" do catálogo.
  • retryable — um booleano decidido no ponto da falha, não fixo por code. O mesmo code pode chegar com retryable: true em um ponto de chamada e false em outro.
  • reason? — uma mensagem contextual opcional para logs e telemetria. A cópia de UI deve usar code, não interpretar esse texto.
  • operation? — uma string simples com o nome da operação em que a falha ocorreu, útil para logging e telemetria.
  • schemaName? — o nome do schema que falhou ao decodificar, quando a falha veio de Schema.decodeUnknownEffect.
  • issueMessage? — o texto formatado do issue de decode (String(issue)) dessa falha de schema.
  • provider? — o nome do provedor remoto, em falhas de API de provedor (Clicksign, Assinafy, ZapSign, DocuSeal, Documenso).
  • status? — o status HTTP upstream, quando a falha veio de uma resposta HTTP de provedor.
  • retryAfterEpochSeconds? — uma dica em epoch-seconds de quando tentar novamente, quando o upstream forneceu uma (por exemplo, uma resposta de rate limit).
  • get message() — a mensagem padrão em inglês para o code, ou reason nos codes marcados como editable.
error-shape.ts
import type { Effect } from "effect"
import type { SignatureArtifact, SignatureKitError, SignatureKitErrorCode } from "@signature-kit/signatures"

// The error channel is typed: each failure is a SignatureKitError.
declare const sign: Effect.Effect<SignatureArtifact, SignatureKitError>

// _tag: "SignatureKitError"
class SignatureKitError {
  readonly code: SignatureKitErrorCode   // 21 "signature-kit.*" literals
  readonly retryable: boolean         // decided at the point of failure
  readonly reason?: string            // contextual message for logs
  readonly operation?: string
  readonly schemaName?: string
  readonly issueMessage?: string
  readonly provider?: string
  readonly status?: number
  readonly retryAfterEpochSeconds?: number
  get message(): string               // default per code, or reason for editable codes
}

Não trate retryable como uma propriedade do code. Sempre leia error.retryable do valor recebido — só aquele ponto de chamada sabe se a falha vale uma nova tentativa.

Tratamento por code

A união de erros tem uma única tag, então Effect.catchTag("SignatureKitError", ...) entra no canal e você discrimina pelo error.code. Um switch sobre code é exaustivo: o compilador exige todos os 21 literais.

handle-error.ts
import { Effect } from "effect"
import { a1SignaturesLayer } from "@signature-kit/a1/signer"
import { signatures } from "@signature-kit/signatures"

const program = Effect.gen(function* () {
  return yield* signatures.sign({ content, algorithm: "rsa-sha256" })
}).pipe(Effect.provide(a1SignaturesLayer({ pfx, password })))

// The error union is a single tag — discriminate by code, not by class.
const handled = program.pipe(
  Effect.catchTag("SignatureKitError", (error) => {
    switch (error.code) {
      case "signature-kit.WRONG_PASSWORD":
        return Effect.fail("Incorrect certificate password — ask for the password again.")
      case "signature-kit.SIGN_FAILED":
        // retryable is decided at the point of failure, not fixed per code.
        return error.retryable ? program : Effect.fail(error.message)
      default:
        return Effect.fail(error.message)
    }
  }),
)

Para interceptar uma falha específica e deixar o restante se propagar, use Effect.catchIf com um predicado sobre _tag e code:

catch-if.ts
import { Effect } from "effect"
import type { SignatureKitError } from "@signature-kit/signatures"

// Filter only the failure this call site knows how to handle; the rest propagate, typed.
const recovered = program.pipe(
  Effect.catchIf(
    (error): error is SignatureKitError =>
      error._tag === "SignatureKitError" && error.code === "signature-kit.WRONG_PASSWORD",
    (error) =>
      Effect.logWarning(`signing aborted at ${error.operation ?? "sign"}: ${error.message}`),
  ),
)

Em handlers de retry, decida com base na combinação de error.code + error.retryable. Por exemplo, refaça uma signature-kit.SIGN_FAILED apenas quando error.retryable for true.

Catálogo de erros

Todos os 21 codes de SignatureKitError e suas mensagens padrão. Os codes marcados como editable resolvem para reason ?? default em message. Cada linha é ancorável como #err-<CODE> (por exemplo #err-WRONG_PASSWORD), de modo que outras páginas façam deep link direto para um code.

CodeDefault message
signature-kit.EMPTY_FILECertificate file is empty (0 bytes).
signature-kit.INVALID_FORMATThe file is not a PKCS#12 (.pfx/.p12) certificate.· editable
signature-kit.INVALID_INPUTInvalid signing input.· editable
signature-kit.WRONG_PASSWORDWrong certificate password.
signature-kit.CERTIFICATE_EXPIREDCertificate expired.
signature-kit.CERTIFICATE_NOT_YET_VALIDCertificate is not valid yet.
signature-kit.MISSING_BR_IDENTIFIERCertificate does not contain a Brazilian CPF or CNPJ.
signature-kit.UNSUPPORTED_ALGORITHMThe certificate uses an unsupported encryption algorithm.· editable
signature-kit.NO_CERTIFICATEThe file does not contain a certificate.
signature-kit.NO_PRIVATE_KEYThe file does not contain a private key.
signature-kit.CORRUPTED_FILEThe file is corrupted or not a valid PKCS#12 certificate.
signature-kit.X509_PARSE_FAILEDX.509 parsing failed.· editable
signature-kit.PEM_EXTRACTION_FAILEDFailed to extract PEM material from the PFX.
signature-kit.KEY_IMPORT_FAILEDFailed to import the key into Web Crypto.· editable
signature-kit.DIGEST_FAILEDFailed to compute the certificate digest.
signature-kit.SIGN_FAILEDFailed to sign the content.· editable
signature-kit.VERIFY_FAILEDFailed to verify the signature.· editable
signature-kit.HTTPHTTP request failed.· editable
signature-kit.RESPONSE_SHAPEHTTP response shape was invalid.· editable
signature-kit.UNSUPPORTED_OPERATIONOperation is unsupported.· editable
signature-kit.UNKNOWNUnknown SignatureKit failure.· editable

Mensagens de exibição localizadas

Não localize inspecionando classes, campos .code soltos ou substrings de reason. Esse caminho quebra quando o texto diagnóstico muda:

nao-inspecione-reason.ts
export function getCertificateErrorMessage(error: { reason?: string }) {
  const reason = error.reason ?? ""
  if (reason.includes("not valid on the current date")) return "Certificado expirado"
  if (reason.includes("Brazilian CPF or CNPJ")) {
    return "Certificado não contém CPF ou CNPJ brasileiro"
  }

  return "Arquivo de certificado inválido"
}

Use @signature-kit/i18n na fronteira da aplicação. Passe apenas os catálogos que aquele código pode receber; o resolver verifica _tag e code de forma estrutural e cai para inglês quando a locale pedida não existe:

mensagem-de-erro-localizada.ts
import { errorMessage } from "@signature-kit/i18n"
import { signatureKitErrorMessages } from "@signature-kit/signatures"
import { cryptoErrorMessages } from "@signature-kit/crypto/config"

const message = errorMessage(error, {
  locale: "pt-BR",
  catalogs: [signatureKitErrorMessages, cryptoErrorMessages],
  overrides: {
    "pt-BR": {
      "signature-kit.WRONG_PASSWORD": "Senha do certificado incorreta",
    },
  },
})

Erros de formato e de provedor

Além do SignatureKitError, os módulos de formato adicionam suas próprias famílias tipadas ao canal de erros. APIs de provedores não adicionam: inputs inválidos de request upstream, falhas HTTP, falhas de formato de resposta e operações não suportadas continuam como SignatureKitError.

XmlError

Codes xml.*. signXml retorna XmlError | SignatureKitError; verifyXml retorna apenas XmlError (sem o serviço Signatures).

PdfError

Codes pdf.*. signPdf retorna PdfError | CmsError | SignatureKitError. Por exemplo, hashAlgorithm: "sha384" falha como pdf.SIGN_FAILED — o backend de signer atual não tem contraparte rsa-sha384; "sha1" é aceito (mapeado para rsa-sha1).

APIs de provedores

Sem família paralela. Entrada inválida, HTTP remoto, formato de resposta e operação não suportada falham como SignatureKitError com provider, operation, schemaName e status quando esses metadados existem.
families.ts
import { Effect } from "effect"
import { signXml } from "@signature-kit/xml/sign"
import { xmlRuntimeLayer } from "@signature-kit/xml/runtime"
import { signPdf } from "@signature-kit/pdf/sign"
import { a1SignaturesLayer } from "@signature-kit/a1/signer"

const layer = a1SignaturesLayer({ pfx, password })

// signXml -> XmlError | SignatureKitError    (xml.* codes)
const xml = signXml({ xml: source, referenceId: "nfe-1" }).pipe(
  Effect.provide(layer),
  Effect.provide(xmlRuntimeLayer),
  Effect.catchTags({
    XmlError: (error) => Effect.fail(`Invalid XML: ${error.code}`),
    SignatureKitError: (error) => Effect.fail(`Signature: ${error.code}`),
  }),
)

// signPdf -> PdfError | CmsError | SignatureKitError    (pdf.* codes)
const pdf = signPdf({ pdf: bytes, policy: "pades-icp-brasil" }).pipe(
  Effect.provide(layer),
  Effect.catchTag("PdfError", (error) => Effect.fail(`PDF: ${error.code}`)),
)

Nas APIs de provedores, as falhas de entrada, de HTTP e de formato de resposta chegam como SignatureKitError. Discrimine pelo error.code da mesma forma:

remote-signer-error.ts
import { Effect } from "effect"
import { ClicksignSignatureRequest } from "@signature-kit/clicksign"

const request = ClicksignSignatureRequest("contract", {
  title: "Contract",
  documents: documentProps,
  recipients,
}).pipe(
  Effect.catchTag("SignatureKitError", (error) => {
    switch (error.code) {
      case "signature-kit.HTTP":
        return Effect.fail(error.reason ?? "Remote HTTP failure.")
      case "signature-kit.INVALID_INPUT":
      case "signature-kit.RESPONSE_SHAPE":
      case "signature-kit.UNSUPPORTED_OPERATION":
        return Effect.fail(error.message)
      default:
        return Effect.fail(error.message)
    }
  }),
)

Os formatos e os signers são pacotes separados — instale apenas o que cada caminho usa.

npm install @signature-kit/xml @signature-kit/pdf @signature-kit/http @signature-kit/clicksign

Como está este guia?

Nesta página