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.
- 01
Scaffold
pnpm create-adapter <platform> — the generator creates the plugin structure with manifest, tests, and typed ports.
- 02
Pick roles
Is the platform an order source? A marketplace? A shipping provider? You pick the ports you want to implement.
- 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.
- 04
Register
Add the adapter to the registry. Everything is open strings — your name, your types, your categories.
- 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/.