Current implementation includes some work on capabilities front that ensures privacy of the private APIs. This is nice but it also adds some magic + boilerplate + it is misleading. Here is example:
const { Trair } = require('traits');
const Foo = Trait.comose({
constructor: function FooTrait() {
.....
},
_privateProperty: 'secret',
publicProperty: 'Helo World'
});
If you want to export Foo
most likely you will write:
exports.Foo = Foo;
But it is actually wrong since then someone may get access to the privates:
const MyFoo = require('foo').Foo.compose({ get privateAPI() this });
MyFoo().privateAPI._privateProperty // -> 'secret'
It can be prevented by:
exports.Foo = function() Foo.apply(null, arguments);
But this also can trick developers since then:
const { Foo } require('foo');
Foo() instanceof Foo // -> false
This also can be addressed by adding one more line:
exports.Foo.prototype = Foo.prototoype;
Simplified API:
const FooTrait = Trait.compose({
_privateProperty: 'secret',
publicProperty: 'Helo World'
});
function Foo() FooTrait.create({ constructor: Foo })
exports.Foo = Foo;
Current API is also adds overhead in multiple trait compositions:
const Bar = Foo.resolve({ constructor: null }).compose({
constructor: function Bar() {
.....
},
......
});
exports.Bar = function() Bar.apply(null, arguments)
exports.Bar = Bar.prototype;
Simplified API:
const BarTrait = Trait.compose({
....
}, FooTrait);
function Bar() {
let bar = BarTrait.create({ constructor: Bar });
....
}
exports.Bar = Bar;
One important difference between new and old API is that capabilities are bounded to the traits and constructors can be shared without any wrappers. This also allows model where all the capabilities may be defined with in the constructor instead:
const BazTrait = Trait.compose({
_secret: Trait.required,
doSomethingWithSecret: function() {
......
}
});
function Baz() {
return BazTrait.create({
constructor: Baz,
_secret: Cc..... // some chrome privileged thing here
});
}
In the last example BazTrait
may be defined in a module with no capabilities
at all and can be shared with anybody. Baz
at the same time can be shared with
anybody since all the capabilities are in a jail of the lexical scope.
Simplified API also allows us to implement all the capability restrictions in the separate trait independent from trait implementation itself. Above example will be rewritten:
const { PublicAPI } = requrie('capabilities');
const BazTrait = Trait.compose({
_secret: Trait.required,
doSomethingWithSecret: function() {
......
}
}, PublicAPI);
function Baz() {
return BazTrait.create({
constructor: Baz,
_secret: Cc..... // some chrome privileged thing here
})._public;
}
Simplified API is more robust alternative, it's much easier to spot issues and it gives more granular control of capabilities. It does not mimics class based systems and it's absolutely transparent to the developer what happens when constructors are invoked. It also allows cases where constructors are not necessary.