In breeze you need to code DAL yourself and it is repetitive and error-prone process which I hate. So I have decided to code a generator which will create DAL with typescript support for angularjs. Also to challenge myself, I decided to write the OData javascript DAL generator on node.js which let me publish it for public in the future.
Accessing To Metadata
Selecting the library (datajs)
To access metadata information from OData server, I searched libraries for node.js. Although there are some client libraries like https://github.com/machadogj/node-odata-cli, I could not found a complete one. The most complete OData client is datajs which is also used by JayData and Breeze in the background. There are one module to use datajs in node like https://www.npmjs.org/package/datajs but it is too old (not support V3). So I decided to create a new module to encapsulate latest version of datajs.Problems of using datajs from node
There are some challenges about using datajs library in node.js.
- It is designed to work on browsers.
- It does not use CommonJs api standarts.
Datajs library uses two browser services. They are:
- XMLHttpRequest for ajax requests
- DOMParser for XML operations
Fortunately node has two libraries for replacement. They are
To use them in node way, we can simply require them and access from assigned variable but datajs library uses them from window object. so we need to inject these libraries to datajs library.
datajs creates to objects to interact with OData service. These are:
- OData
- datajs
objects which are also injected to window object of the browser. So we need to find a way to provide browser services to datajs and consume OData and datajs objects from nodejs in node way.
Encapsulating datajs library as nodejs module
Datajs uses
(function (window, undefined) { // library content })(this);
pattern which let us provide a custom context for the library. Now we need to support browser services to library and get its services to nodejs application.
There are two ways to bridge services between node and library.
Problem is we inject XMLHttpRequest and DOMParser services after library executes. If library makes existence checks in the first run as configuration control instead of creating service objects when library is being used, our code will fail. Luckily datajs uses second aproach.
// Read and eval library
We can now use our new module with pleasure.
There are two ways to bridge services between node and library.
Using require()
First way is using classic require module from node. Require creates a new module.exports contexts as root context so we can inject browser service replacements into it.var DOMParser = require('xmldom').DOMParser; var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; var datajsExports = require('./lib/datajs-1.1.2.js'); datajsExports.XMLHttpRequest = XMLHttpRequest; datajsExports.DOMParser = DOMParser; module.exports.OData = datajsExports.OData; module.exports.datajs= datajsExports.datajs;Note the last two line where we bridge services created by library to our module.exports scope.
Problem is we inject XMLHttpRequest and DOMParser services after library executes. If library makes existence checks in the first run as configuration control instead of creating service objects when library is being used, our code will fail. Luckily datajs uses second aproach.
var createXmlHttpRequest = function () { ///Creates a XmlHttpRequest object. ///XmlHttpRequest object. if (window.XMLHttpRequest) { return new window.XMLHttpRequest(); } var exception; if (window.ActiveXObject) { try { return new window.ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (_) { try { return new window.ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e) { exception = e; } } } else { exception = { message: "XMLHttpRequest not supported" }; } throw exception; };
Using eval()
What can we do if a library checks existence of services at configuration phase? To solve this problem, we can emulate require() in our way. There is an excellent post on stackoverflow to load custom libraries by using eval(). Problem is eval() method has no parameter to provide execution context. But with a little trick we can provide a custom context for this in library.// Read and eval library
var fs = require('fs'); var DOMParser = require('xmldom').DOMParser; var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; var scope ={ XMLHttpRequest:XMLHttpRequest, DOMParser : DOMParser, execute:function(fileName) { filedata = fs.readFileSync(fileName,'utf8'); eval(filedata); } }; scope.execute(__dirname+'/lib/datajs-1.1.2.js'); // access to generated services from our custom scope module.exports.OData = scope.OData; module.exports.datajs = scope.datajs;Here we wrap eval() call in another function and call the function over a custom object. The custom object becomes the execution scope of execute function which will also be used by eval() call. Also new services created by library are attached to our custom object used as execution context. In this way we are able to inject our services before library function executes.
We can now use our new module with pleasure.