3

I'm trying to get a dynamic service locator written in JavaScript using Harmony Proxies (Node.js). Basically you would create a new container:

 var container = new Container();

You would then be able to set and get values like a traditional service locator:

 container.set('FM', {});
 container.get('FM');
 container.get('FM', function(FM) {

 });

You can even have namespaces that acts like sub-objects:

 container.set('FM.Object', {});

The problem is with the dynamic aliases.

 var App = container.alias('App');

When creating an alias, it creates a new proxy object where you can manipulate it like a traditional object, but it's an alias to get() and set() methods.

Instead of:

 container.get('App.Hello'); 

With an alias, you would use:

 App.Hello

The problem is with deep namespaces. Let's say you're trying to access App.Hello.World.Controller, because it goes through the proxy one namespace at a time and not all at once (like that full namespace). How would I know if the user is calling App.Hello to retrieve the value stored in the service locator, or if the user wants to continue accessing deeper namespaces? You can't (From what I've tried).

What other way could you accomplish this?

Having the syntax App.Hello.World which would return either a proxy, if it's going to access a deeper namespace, or the value stored in the service locator.

  var App = container.alias('App');
  App.Controller.Home.Method.Index
  // It would go through:
  App -> Proxy
  App.Controller -> Proxy or Value?
  App.Controller.Home -> Proxy or Value?
  App.Controller.Home.Method -> Proxy or Value?
  App.Controller.Home.Method.Index -> Proxy or Value?

Right now, I've assembled a convention for dealing with the two options. You would use singular names for retrieving the value and plural for returning a proxy. This was just a quick "hack" as it's not very effective.

(If you need clarification or more info just let me know)

Daniel
  • 1,692
  • 2
  • 13
  • 19
  • I have a question: What kind of proxies are you using? I'm not up-to-date on what V8/Node has implemented (and the ES committee hasn't fully decided on which proxies will be in ES6). There are two main styles of proxies which have been discussed for Harmony (both have been implemented in Firefox): non-direct `Proxy.create(handler, proto)` and direct `new Proxy(target, handler)`. – Nathan Wall Jan 23 '13 at 02:50
  • @NathanWall The later one. That's the V8 syntax atm. – Daniel Jan 23 '13 at 02:52

1 Answers1

5

Overview of the Problem

Let me see if I understand you correctly. It sounds like you want properties accessed "together" to be considered part of a single key for controller.get. Take the following example:

controller.set('App.Foo.Bar', { Zap: 1 });
controller.set('App.Foo.Bar.Zap', 2);

var App = controller.alias('App');

var Bar = App.Foo.Bar;
var Zap = Bar.Zap;

var Zap2 = App.Foo.Bar.Zap;

console.log(Zap, Zap2);
// should log: 1, 2

If the above behavior is what you want, then it's not possible (with that syntax). JavaScript just doesn't distinguish between App.Foo.Bar.Zap and var Bar = App.Foo.Bar; Bar.Zap. There's nothing in the language that would allow you to differentiate between properties that were accessed "together" (var Bar = App.Foo.Bar) and one that was accessed "separately" (Bar.Zap).


Proposal

You can, however, get close by modifying your syntax. I thought about a few different possibilities, and here's the best I could come up with:

controller.set('App.Foo.Bar', { Zap: 1 });
controller.set('App.Foo.Bar.Zap', 2);

var App = controller.alias('App');

// Use parens to note that we are ending the key name
// and want it to retrieve a value.
var Bar = App.Foo.Bar();
var Zap = Bar.Zap;

var Zap2 = App.Foo.Bar.Zap();

console.log(Zap, Zap2);
// should log: 1, 2

If you add parentheses after a property retrieval, as above, then you can use this syntax as a distinguishing note to your proxy that that means to retrieve the value from the controller.


Implementation

Here's the code that would make that possible:

var Container = (function() {

    return function Container() {

        var map = { };

        this.get = function get(key) {
            return map[key];
        };

        this.set = function set(key, value) {
            map[key] = value;
        };

        this.alias = function alias(name) {
            return createAlias(this, name);
        };

    };

    function createAlias(container, aliasName) {
        return new Proxy(
            function() {
                return container.get(aliasName);
            },
            {
                get: function(target, name) {
                    return createAlias(container, aliasName + '.' + name);
                },
                set: function(target, name, value) {
                    container.set(aliasName + '.' + name, value);
                }
            }
        );
    }

})();

You can set with = and retrieve with ():

var container = new Container();
container.set('App.Controller.Home.Method.Index', 5);

var App = container.alias('App');

App.Controller.Home.Method = 7;

console.log(
    App.Controller.Home.Method(),      // => 7
    App.Controller.Home.Method.Index() // => 5
);

Final Note

I'm sure you're aware that Proxies are experimental, but I just want to emphasize that this particular feature of ES6 is still quite in flux. It could be that the current direct proxies proposal ends up being included in the language, but it's also very possible that proxies in ES will change significantly over the next year. (There have been vigorous discussions on the ECMAScript mailing list over the past month regarding the proxies proposal.) At least with Node you can just keep using an old version of Node if proxies change dramatically (while you work on updating an old implementation).

Hope this helps!

Nathan Wall
  • 10,530
  • 4
  • 24
  • 47
  • Wow, that's perfect. Indeed that was the problem and the solution or proposed syntax would work perfectly, thanks. I'm only using proxies within Node.js and I'll definitely keep watching on the discussion. – Daniel Jan 23 '13 at 16:11
  • Did you try the implementation? It doesn't work as expected in Node.js, even with the proper syntax change. – Daniel Jan 24 '13 at 03:37
  • Hey, Daniel. I tried it in Firefox Nightly (version 21), and it worked great there. I don't have time to test in Node at the moment, but let me know what version you're using and I'll test it out tomorrow morning. – Nathan Wall Jan 24 '13 at 03:41
  • NP. I'll take a look at the specification again. It seems Chrome's API (I believe the correct one atm) doesn't trigger the prototype within the proxy. I'll try and see what's going on. I'm on the v0.8.15. Thanks Again. – Daniel Jan 24 '13 at 03:56
  • I installed Node v0.8.15, and with the `--harmony-proxies` flag, it appears to support old-style, non-direct proxies (`Proxy.create`) rather than direct proxies (`new Proxy`). Are you using some external library that provides direct proxies or are you using the `--harmony-proxies` flag? I thought you said earlier that you had direct proxies. Were you just mistaken? – Nathan Wall Jan 24 '13 at 12:34
  • I also tested with the latest Node v0.8.18 and it still seems to only support non-direct proxies. Please correct me if I'm wrong. – Nathan Wall Jan 24 '13 at 12:39
  • I'm now using the library https://github.com/tvcutsem/harmony-reflect which allows direct-proxies in Node.js. Now everything works as expected. Thanks. – Daniel Jan 24 '13 at 16:52