Skip to content

ARCHITECTURE

Build the adapters. Skip the foundation.

Hexagonal architecture with open-world capability ports. A framework-free domain. A plugin SDK that compiles without the host. Real integration tests against a real Postgres and a real PrestaShop.

HOW DATA FLOWS

Orders, both directions.

Everything OpenLinker does comes down to two ports: one side reads events from the source platform, the other side writes them to the destination platform. The core sits in between — identifier mapping, retries, dedup, projections.

Marketplace (Allegro)                           Shop (PrestaShop)
   │                                                   ▲
   │ 1. order event                                    │ 4. create order,
   ▼                                                   │    update status
[OrderSource]                              [OrderProcessorManager]
   │                                                   ▲
   │ 2. hydrate                                        │ 3. map customer,
   │    full order                                     │    resolve products
   ▼                                                   │
              OpenLinker core ─────────────────────────┘
              identifier mapping · retries · dedup · projections

CAPABILITY PORTS

Open contracts, not closed classes.

Capability, EntityType, and PlatformType are open strings at the registry boundary. You can add ShippingProvider, PricingAuthority, or anything else — without a PR to the core. The well-known set is a hint, not a gate.

Every capability is a domain port — an abstraction over what a system can do. An adapter implements only the ports each client actually needs. The rest doesn't exist for it.

FRAMEWORK-FREE DOMAIN

Zero NestJS or TypeORM imports in the domain layer.

libs/core/src/**/domain/ has zero infrastructure imports. Enforced at runtime via package.json#exports — if someone tries to import NestJS into the domain, the build fails. The domain lives its own life; the framework is an implementation detail.

// libs/core/package.json
{
  "name": "@openlinker/core",
  "exports": {
    "./domain/*":   "./src/**/domain/*.ts",
    "./application/*": "./src/**/application/*.ts"
    // infrastructure intentionally not exported
  }
}

// libs/core/src/orders/domain/Order.ts
export class Order {
  // no NestJS, no TypeORM, no infrastructure imports.
  // pure TypeScript over the domain model.
}

PLUGIN SDK

Plugins compile against the SDK, not against the host.

External adapters compile against @openlinker/plugin-sdk. The SDK ships the AdapterPlugin contract, a HostServices bag, and a typed dispatchCapability<T> helper. A plugin knows nothing about NestJS, TypeORM, or a specific OpenLinker deployment host. Which means: an agency writing an adapter for one client doesn't have to fork the core, and doesn't have to migrate it across NestJS versions.

// libs/integrations/my-marketplace/src/index.ts
import { definePlugin, OrderSource } from '@openlinker/plugin-sdk';

export default definePlugin({
  name: 'my-marketplace',
  capabilities: {
    OrderSource: class implements OrderSource {
      async read(cursor) { /* ... */ }
    },
  },
});

REAL TESTS

The test-kit is exported. Real database, real PrestaShop.

The Testcontainers harness ships as @openlinker/test-kit. Adapter tests run a real Postgres, a real Redis, and a real PrestaShop instance in containers. No mocking — what passes in tests works in production. External adapters inherit the same test infrastructure.

// libs/integrations/my-marketplace/test/orders.spec.ts
import { stack } from '@openlinker/test-kit';

test('imports orders from cursor', async () => {
  const { postgres, redis, prestashop } = await stack.up();
  // real containers, real APIs, no mocks.
  // ...
});

A NEW INTEGRATION

Five steps to your first deploy.

  1. 01

    Scaffold

    pnpm create-adapter <platform> — the generator creates the plugin structure with manifest, tests, and typed ports.

  2. 02

    Pick roles

    Is the platform an order source? A marketplace? A shipping provider? You pick the ports you want to implement.

  3. 03

    Implement

    Only the ports the platform supports. You don't have to implement them all — some shipping providers are labels only, some marketplaces are orders only.

  4. 04

    Register

    Add the adapter to the registry. Everything is open strings — your name, your types, your categories.

  5. 05

    Test

    pnpm test runs your tests against real containers from the test-kit. If your tests pass, it works in the running app.

See the code. Clone the repo.

The architecture is public. Apache 2.0. All ADRs in docs/architecture/adrs/.