> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Visión general del Fetcher Engine

> El Fetcher Engine es un módulo Go importable que ejecuta la extracción de datos dentro del proceso: el modelo de tres capas, la frontera de importación y el modo Direct frente al modo Store.

El **Fetcher Engine** es el núcleo de extracción de Fetcher, empaquetado como un módulo Go que puedes importar. Aplicaciones anfitrionas como Matcher y Reporter ejecutan el Engine en su propio proceso, en lugar de operar un despliegue separado de Fetcher. El [Manager y el Worker](/es/fetcher/what-is-fetcher) standalone son ellos mismos hosts sobre el mismo Engine.

El Engine es dueño de las reglas de la extracción: ciclo de vida de las conexiones, descubrimiento y validación de esquemas, planificación de consultas, ejecución de la extracción, contratos de resultado y de error, límites y seguridad por tenant. No es dueño de ninguna infraestructura.

<Note>
  El Engine es un **módulo Go distinto** de los servicios de Fetcher. Su ruta de módulo es `github.com/LerianStudio/fetcher/pkg/engine`, y la de los servicios es `github.com/LerianStudio/fetcher/v2`. El Engine tiene su propia línea de versiones, con tags prefijados por ruta (`pkg/engine/vX.Y.Z`). Importar el Engine no arrastra ninguna dependencia de los servicios.

  Fetcher es source-available bajo la Elastic License 2.0, y el Engine lleva la misma licencia. Puedes leer las reglas de extracción que embebes.
</Note>

## El modelo de tres capas

***

| Capa                              | Paquete                              | De qué es dueña                                                                                                                                       |
| --------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Núcleo del Engine**             | `pkg/engine`                         | Las *reglas* de la extracción. Depende solo de las interfaces de puerto que provee el host, nunca de infraestructura.                                 |
| **Adaptadores de compatibilidad** | `pkg/enginecompat/*`                 | Puentes entre los puertos del Engine y la infraestructura real de Fetcher — MongoDB, Redis y los drivers de datasource.                               |
| **Aplicación anfitriona**         | Manager, Worker o tu propio servicio | La cáscara operativa: autenticación, aplicación de licencia, rutas HTTP, colas, almacenes de estado, storage, telemetría y ciclo de vida del proceso. |

El principio rector: **el Engine es dueño de lo que hace que Fetcher sea *Fetcher*, y las aplicaciones anfitrionas son dueñas de *cómo* corre Fetcher.**

Por eso, cada producto que embebe el Engine comparte un único dueño canónico del comportamiento de datasources y de extracción. Un cambio de regla en el núcleo llega a todos los hosts en el siguiente salto de módulo, y ningún host lo reimplementa.

## La frontera de importación

***

El módulo del Engine declara **cero dependencias de terceros**. Su `go.mod` no tiene bloque `require`, y un test lo mantiene así en cada compilación.

Dos guardas corren dentro del módulo en cada `go test ./...`, y el workflow de CI del módulo las ejecuta con el workspace de Go desactivado:

* **La lista de permitidos.** Cada dependencia transitiva de `pkg/engine` debe ser un paquete de la biblioteca estándar de Go o un paquete local del módulo del Engine. Cualquier otra familia de imports falla la compilación, incluso una familia que ninguna lista de denegados nombre.
* **Una lista de denegados explícita.** Clases enumeradas fallan además de la lista de permitidos. Cubren frameworks HTTP, brokers de mensajes, drivers de bases de datos, SDKs de object storage, middleware de runtime multi-tenant, las bibliotecas de autenticación y de licencia, y envolturas de la biblioteca estándar como `database/sql`, `net/http` y `os/exec`.

Un paso de CI aparte busca una línea `require` en el `go.mod` del Engine y falla el job cuando la encuentra.

### Por qué esto importa cuando embebes

* **Sin conflictos de dependencias.** El Engine no puede meter un driver, un cliente de broker o un framework HTTP en el grafo de tu módulo. Tu host mantiene el control total de sus propias versiones.
* **Sin E/S oculta.** El núcleo no puede abrir un socket, un archivo ni una base de datos por su cuenta. Cada byte que entra y sale cruza un puerto que tú proveíste.
* **Un punto de sustitución estable.** Ejecuta todo el Engine contra la implementación en memoria en tus tests. Cambia a los adaptadores reales en producción, sin tocar el código que lo llama.

## Modo Direct y modo Store

***

Un plan de extracción lleva un modo con tres valores: `direct`, `store` y el valor cero `auto`.

El Engine resuelve `auto` a partir de los puertos que conectaste. Con un `ResultSink` configurado elige el modo Store, y sin él elige el modo Direct. Así, el modo es una consecuencia de tu composición, no un interruptor aparte. Una petición explícita de `store` sin sink falla de entrada con un error de validación, antes de que el Engine toque ningún datasource.

Los dos modos devuelven formas distintas. Exactamente una rama del resultado es no nula, y el JSON omite por completo la rama sin usar.

### Modo Direct

El Engine ejecuta los pasos del plan y fusiona las filas en un solo mapa. Las claves del mapa son el nombre de la configuración del datasource y luego la tabla calificada. El Engine serializa ese mapa una vez como JSON indentado y devuelve los bytes inline.

```json theme={null}
{
  "pg-main": {
    "public.users": [
      {
        "email": "a@example.com",
        "id": 1
      }
    ]
  }
}
```

El resultado inline lleva estos metadatos:

| Campo           | Valor en modo Direct                                      |
| --------------- | --------------------------------------------------------- |
| `data`          | El payload serializado de arriba                          |
| `format`        | `json`                                                    |
| `rowCount`      | Total de filas en todas las tablas                        |
| `plaintextSize` | Tamaño en bytes del payload                               |
| `integrity`     | Algoritmo `SHA-256` más el digest hexadecimal del payload |
| `protection`    | `encrypted: false`, aplicado por `engine`                 |

La salida del modo Direct es determinista. El serializador ordena las claves del mapa. Por eso, la misma entrada te da un JSON idéntico byte a byte y el mismo digest, sin importar en qué orden terminaron los pasos paralelos.

Tu host es dueño de todo lo que viene después. El Worker de Fetcher, por ejemplo, firma el texto plano con HMAC-SHA256 y lo cifra antes de guardar los bytes.

### Modo Store

El Engine abre un stream en tu `ResultSink` y escribe el resultado de forma incremental, en memoria constante. Nunca retiene el resultado completo. Un único escritor drena las goroutines de extracción estrictamente en orden ascendente de paso del plan, y el digest de integridad cubre exactamente los bytes escritos.

La forma en el cable es NDJSON — un objeto JSON por línea, terminada en salto de línea, sin array que lo envuelva:

```json theme={null}
{"config":"pg-main","table":"public.users","row":{"id":1,"email":"a@example.com"}}
```

La llamada devuelve una referencia en lugar de bytes:

| Campo                      | Significado                                                                                                                                                                                                                    |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `path`                     | La ubicación **lógica** en tu storage. La referencia no expone ningún tipo de backend físico — tu adaptador resuelve la ruta.                                                                                                  |
| `format`                   | Formato de salida de los bytes persistidos                                                                                                                                                                                     |
| `rowCount`                 | Total de filas en el resultado persistido                                                                                                                                                                                      |
| `sizeBytes`                | Tamaño serializado escrito                                                                                                                                                                                                     |
| `integrity` y `protection` | Lo que reportó tu sink. Cuando el sink no reporta integridad, el Engine estampa el digest SHA-256 que calculó sobre los bytes transmitidos. El Engine solo valida que `protection.appliedBy` sea `engine`, `adapter` o `host`. |

Ante un aborto — un error de escritura, un límite de tamaño superado o un contexto cancelado — el Engine abandona el escritor y nunca llama a `Close`. Por eso un resultado parcial nunca se convierte en una referencia devuelta. Trata un escritor sin cerrar como una escritura descartada.

## Contratos de fallo y de resultado

***

* **Fallo inmediato entre datasources.** El primer paso que falla detiene la corrida. El Engine nunca devuelve un resultado parcial.
* **Errores redactados.** Un error de driver puede llevar dentro un DSN, una credencial o detalles internos del driver, así que el Engine lo descarta y devuelve un mensaje fijo en su lugar.
* **Once categorías de error.** `validation`, `not_found`, `unauthorized`, `forbidden`, `limit_exceeded`, `conflict`, `unavailable`, `connect`, `timeout`, `canceled` e `internal`. Tu host las mapea a sus propios códigos de transporte. `connect` se mantiene distinto de `unavailable`, y `timeout` se mantiene distinto de `canceled`.
* **Cinco estados de ejecución.** `pending`, `running`, `completed`, `failed` y `canceled`. Los tres últimos son terminales. Una cancelación del host registra `canceled`, y un plazo excedido registra `failed`.
* **Conectores cerrados.** Cada conector que abre el Engine se vuelve a cerrar, en el camino de éxito y en cada camino de fallo.

## Límites y alcance por tenant

***

El Engine nunca corre sin límites. Cuando no aportas ninguno, aplica estos valores por defecto:

| Límite                           | Por defecto |
| -------------------------------- | ----------- |
| Datasources por extracción       | 10          |
| Tablas por datasource            | 20          |
| Campos por tabla                 | 50          |
| Workers paralelos de datasource  | 4           |
| Timeout de extracción            | 5 minutos   |
| Tamaño del resultado serializado | 256 MiB     |

Una petición puede **bajar** cualquier límite, y nunca subirlo. Un override por encima del valor por defecto falla con un error de validación que nombra el campo infringido. Un override cero o negativo mantiene el valor por defecto.

El Engine aplica el techo de tamaño del resultado dos veces: una cota inferior barata por paso, que aborta temprano, y una comprobación autoritativa sobre el payload indentado final. Un resultado por encima del límite nunca te llega inline ni llega a tu sink.

El alcance por tenant es igual de estrecho. Cada operación lleva un tenant ID y nada más — el Engine no tiene concepto de organización ni de producto. Valida el tenant ID antes de cualquier acceso a recursos, y ahí rechaza un valor vacío o mal formado.

## Próximos pasos

***

<CardGroup cols={2}>
  <Card title="Embeber el Engine" icon="code" href="/es/fetcher/fetcher-embedding-the-engine">
    Impórtalo, provee los puertos y constrúyelo con un ejemplo ejecutable.
  </Card>

  <Card title="Referencia de puertos" icon="plug" href="/es/fetcher/fetcher-engine-ports">
    Cada puerto, si es obligatorio y qué pasa sin él.
  </Card>
</CardGroup>
