IMO, the best way to do this is to create a parent ("model") and subordinate modules like model.people, model.auth, and model.chat. The parent ("model") manages and coordinates shared state and configurations. The subordinate modules (model.people, model.auth, model.chat) should not call each other or otherwise depend on the other's existence.
Here is an example where the model coordinates 3 subordinate modules interface (model.people, model.chat, model.auth) and stores shared state in its stateMap. Recognize that each subordinate module will have its own encapsulated stateMap which will not pollute the parent state. While this example is certainly not complete, hopefully it helps point the way.
model = (function () {
var
__undef,
configMap = {};
stateMap = {
user_id : __undef,
chatee_id : __undef
},
logIn, addPerson, rmPerson, callPerson, hangUp;
logIn = function ( user_name, passwd_str ) {
user_id = model.auth( user_name, passwd_str );
if ( user_id ) {
model.people.addPerson( user_id ):
model.chat.announceUser( user_id );
stateMap.user_id = user_id;
}
};
// ... and more methods that coordinate subordinate modules
return {
logIn : logIn,
addPerson : addPerson,
rmPerson : rmPerson,
callPerson : callPerson,
hangUp : hangUp
};
}());
This stuff is not always easy. I try not to get too hung up about organization but keep it in mind. It's kind of like project management: the only "perfect" project plan I've ever seen was written after the project was complete. The same is true for code organization. In my first pass, I allow myself to be a little sloppy. For example, I might violate the no-peer-cross-talk rule as a quick hack - but I will add a big fat TODO to ensure it doesn't make it to production code.
Once our code gets settled, I then give it another pass and clean up the loose ends - like the aforementioned big fat TODOs. The result is usually well organized, easy to understand, and maintainable code. If not, I rinse and repeat.
I hope that help!