Ports and adapters
Vendor-neutral interfaces, standard-shaped contracts, DTCG token resolution, and the suite every adapter must pass.
A capability declares the ports it needs — feature flags, content, payments, identity — in its manifest, through CapabilityPortRequirement. @fabricorg/ports supplies the interface behind that name, checks the declaration against registered adapters, and gives every adapter the same suite to pass.
Nothing in the package imports a vendor SDK. A port is a shape; an adapter is anything that satisfies it. That rule is what keeps a vendor choice from becoming a constraint.
Where a standard exists, the port speaks it
FlagsPort is structurally compatible with an OpenFeature provider's evaluation surface, so an OpenFeature provider is a thin adapter rather than a translation layer. DesignTokensPort consumes W3C DTCG documents, so a design tool is one exporter among any rather than the source of truth.
Inventing parallel semantics where a standard already exists is how a platform acquires a vocabulary nobody else speaks.
A declaration is only real if something checks it
import { assertPortRequirementsSatisfied } from "@fabricorg/ports";
assertPortRequirementsSatisfied({ capability, adapters });Fails when a declared port has no registered adapter, when no adapter speaks a required standard, or when none offers a required version — naming every unsatisfied port rather than the first. Run it where a deployment is assembled, and a capability that needs a flags port cannot reach an environment that has none.
Version matching is exact. Range negotiation belongs to whatever installs the adapters, and half-implemented semver would be worse than none.
The adapter contract
import { flagsPortChecks, runPortContract } from "@fabricorg/ports";
const { passed, failures } = await runPortContract(adapter, flagsPortChecks());Every flags adapter must return the supplied default rather than throw on an unknown key, return each resolver's own type, evaluate repeatably for the same key and context, and tolerate an absent context. Every design-token adapter must resolve a theme to at least one token, follow every alias, and keep names unique.
runPortContract reports every failure rather than stopping at the first, so one run tells an adapter author everything to fix. A replacement adapter passing the same suite as the one it replaces is what makes swapping a vendor an adapter build plus a configuration change rather than a migration.
DTCG resolution
resolveDesignTokens flattens a DTCG document to dotted names, follows {alias} references to their concrete value, and inherits $type from the nearest ancestor group that declares one. A dangling alias and an alias cycle are both refused rather than silently producing an unusable theme.
resolveDesignTokens({
color: {
$type: "color",
base: { ink: { $value: "#16202A" } },
semantic: { text: { $value: "{color.base.ink}" } },
},
});
// → color.base.ink #16202A (color)
// color.semantic.text #16202A (color)Because themes are data, white-labelling a tenant is a token-set deployment rather than a release — and because the pipeline starts at a port, the design tool is only today's editor for that data.