# @n8n/engine — structure & modularity intent ## The blueprint we're following We structure this package after the **Durable Scheduler modularity blueprint** ([Notion](https://app.notion.com/p/n8n/The-Durable-Scheduler-a-modularity-blueprint-39f5b6e0c94f8193a5fecca8306c4424), worked example: `packages/@n8n/scheduler`, enforced by its `src/__tests__/dependency-purity.test.ts`). The core idea: a **pure core that decides, with every effect handed in as an injected interface**. Dependency arrows point one way — consumers depend on the engine; the engine core reaches for nothing. ## Reality now: loose, but seam-aware We are deliberately **loose about composition at this stage**. This one package currently holds the engine core *and* its serving/persistence infrastructure. We expect things to shuffle, so we're not paying the full ports-and-adapters tax yet. What we **do** commit to is keeping the internal boundaries clean, so serving infra and persistence can later be lifted into their own packages (e.g. a deployable engine worker) without touching core logic. ## Layers (today, all in this package) - **Core (decides)** — `graph/`, `execution/`, `admittance/`. Pure orchestration/policy. Must not open connections, bind an HTTP server, or read the environment. Everything external arrives via constructor args / a deps bag (the `createScheduler(deps)` pattern). - **Core interfaces** — interfaces the core depends on, each defined in its own core module beside a default/reference use: `AdmittanceService` (`admittance/`), `WorkQueue` (`queue/`), `ExecutionStore` (`execution/`). Adapters implement them; the core never imports the interface from an adapter. Handed in at construction. - **Adapters (do)** — effectful implementations: `database/` (TypeORM entities, migrations, the Postgres `DataSource`, and `TypeOrmExecutionStore`), `queue/` (in-memory default). The Postgres/ORM coupling lives *here only*. - **Serving infra** — `server/` (express), `serve.ts` (standalone entrypoint), Dockerfile. First candidate to be extracted later. - **Composition roots** — `serve.ts` (standalone) and, in integrated mode, `packages/cli`. These construct the concrete adapters (the `DataSource`, etc.) and hand them in. **Construction lives here, not in the core.** ## Rules that keep the seams extractable - Core modules (`graph`, `execution`, `admittance`) don't import `express`, `pg`, or `@n8n/typeorm`, don't construct a `DataSource`, and never import from `database/`. Persistence, queue, and HTTP are injected. The dependency arrow is one-way: `database/` imports the interface + domain types from the core, not the reverse. - `@n8n/typeorm` / `pg` stay confined to `database/`. - `@n8n/config` + `@n8n/di` are used only at the serving/composition layer (for `EngineConfig`), never in core logic. (The blueprint flags `@n8n/config` as debatable precisely because it pulls the DI runtime in — keep it out of core.) - We go one step stricter than the scheduler's allowlist: **no `n8n-workflow` dependency at all, not even type-only** (per the Engine 2.0 design — the core must stay free of v1 concepts). Shared JSON types are redefined locally in `common/`. - Arrows point inward: `cli`/`serve` depend on the engine; the engine never imports `cli`. - When serving infra is extracted, add a `dependency-purity.test.ts` (as `@n8n/scheduler` does) to enforce the allowlist. Until then, this doc is the intent. ## Crash recovery: reconciliation, not transactions Handlers advance an execution through several separate writes — claim the execution, insert step rows, publish the next message — and we deliberately do **not** make that sequence atomic. Crashing partway through can leave partial state, such as an execution stuck `running` with no queued step. The intended answer is a reconciliation layer that detects crashed or stalled executions and drives recovery (CAT-2938), not transactions spanning stores and queues. So when you find a partial-write window: make the resulting state legible to reconciliation, and don't reach for a cross-store transaction. It's a recurring review question — this is the standing answer. ## Known deviations — the seams to clean up on decomposition - `DataSource` construction currently lives in `database/`; it is really a composition-root concern.