I cannot share the code we use directly but we use Object.defineProperty and override the getter and setter for it, tunneling the new setted value through a behavior subject that gets returned. The getter returns the value by synchronous access to the subject.
Inputs into makeObservable are the component class (this) and the requested property name (generics with keyof).
Patching @Output and @Input is supremely painful - I really wish there was some sort of shorthand for that.