Scripting abilities of mORMot

As a Delphi framework, mORMot premium language support is for the object pascal language. But it could be convenient to have some part of your software not fixed within the executable. In fact, once the application is compiled, execution flow is written in stone: you can't change it, unless you modify the Delphi source and compile it again. Since mORMot is Open Source, you can ship the whole source code to your customers or services with no restriction, and diffuse your own code as pre-compiled .dcu files, but your end-user will need to have a Delphi IDE installed (and paid), and know the Delphi language.

This is when scripting does come on the scene.
For instance, scripting may allow to customize an application behavior for an end-user (i.e. for reporting), or let a domain expert define evolving appropriate business rules - following Domain Driven Design.

If your business model is to publish a core domain expertise (e.g. accounting, peripheral driving, database model, domain objects, communication, AJAX clients...) among several clients, you will sooner or later need to adapt your application to one or several of your customers. There is no "one exe to rule them all". Maintaining several executables could become a "branch-hell". Scripting is welcome here: speed and memory critical functionality (in which mORMot excels) will be hard-coded within the main executable, then everything else could be defined in script.

There are plenty of script languages available.
We considered DelphiWebScript which is well maintained and expressive (it is the code of our beloved SmartMobileStudio), but is not very commonly used. We still want to include it in the close future.
Then LUA defines a light and versatile general-purpose language, dedicated to be embedded in any application. Sounds like a viable solution: if you can help with it, your contribution is welcome!
We did also take into consideration Python and Ruby but both are now far from light, and are not meant to be embedded, since they are general-purpose languages, with a huge set of full-featured packages.

Then, there is JavaScript:

  • This is the World Wide Web assembler. Every programmer in one way or another knows JavaScript.
  • JavaScript can be a very powerful language - see Crockford's book "JavaScript - The Good Parts";
  • There are a huge number of libraries written in JavaScript: template engines (jade, mustache...), SOAP and LDAP clients, and many others (including all node.js libraries of course);
  • It was the base for some strongly-typed syntax extensions, like CoffeScript, TypeScript, Dart;
  • In case of AJAX / Rich Internet Application we can directly share part of logic between client and server (validation, template rendering...) without any middle-ware;
  • One long time mORMot's user (Pavel, aka mpv) already integrated SpiderMonkey to mORMot's core. His solution is used on production to serve billion of requests per day, with success. We officially integrated his units.
    Thanks a lot, Pavel!

As a consequence, mORMot introduced direct JavaScript support via SpiderMonkey.
It allows to:

  • Execute Delphi code from JavaScript - including our ORM or SOA methods, or even reporting;
  • Consume JavaScript code from Delphi (e.g. to define and customize any service or rule, or use some existing .js library);
  • Expose JavaScript objects and functions via a TSMVariant custom variant type: it allows to access any JavaScript object properties or call any of its functions via late-binding, from your Delphi code, just as if it was written in native Object-Pascal;
  • Follow a classic synchronous blocking pattern, rooted on mORMot's multi-thread efficient model, easy to write and maintain;
  • Handle JavaScript or Delphi objects as UTF-8 JSON, ready to be published or consumed via mORMot's RESTful Client-Server remote access.

SpiderMonkey integration

A powerful JavaScript engine

SpiderMonkey, the Mozilla JavaScript engine, can be embedded in your mORMot application. It could be used on client side, within a Delphi application (e.g. for reporting), but the main interest of it may be on the server side.

The word JavaScript may bring to mind features such as event handlers (like onclick), DOM objects, window.open, and XMLHttpRequest.
But all of these features are actually not provided by the SpiderMonkey engine itself.

SpiderMonkey provides a few core JavaScript data types—numbers, strings, Arrays, Objects, and so on—and a few methods, such as Array.push. It also makes it easy for each application to expose some of its own objects and functions to JavaScript code. Browsers expose DOM objects. Your application will expose objects that are relevant for the kind of scripts you want to write. It is up to the application developer to decide what objects and methods are exposed to scripts.

Direct access to the SpiderMonkey API

The SynSMAPI.pas unit is a tuned conversion of the SpiderMonkey API, providing full ECMAScript 5 support and JIT.
You could take a look at the full description of this low-level API.

But the SynSM.pas unit will encapsulate most of it into higher level Delphi classes and structures (including a custom variant type), so you probably won't need to use SynSMAPI.pas directly in your code:

Type Description
TSMEngineManager main access point to the SpiderMonkey per-thread scripting engines
TSMEngine implements a Thread-Safe JavaScript engine instance
TSMObject wrap a JavaScript object and its execution context
TSMValue wrap a JavaScript value, and interfaces it with Delphi types
TSMVariant /
TSMVariantData
define a custom variant type, for direct access to any JavaScript object, with late-binding

We will see know how to work with all those classes.

Execution scheme

The SpiderMonkey JavaScript engine compiles and executes scripts containing JavaScript statements and functions. The engine handles memory allocation for the objects needed to execute scripts, and it cleans up—garbage collects—objects it no longer needs.

In order to run any JavaScript code in SpiderMonkey, an application must have three key elements:

  1. A JSRuntime,
  2. A JSContext,
  3. And a global JSObject.

A JSRuntime, or runtime, is the space in which the JavaScript variables, objects, scripts, and contexts used by your application are allocated. Every JSContext and every object in an application lives within a JSRuntime. They cannot travel to other runtimes or be shared across runtimes.

A JSContext, or context, is like a little machine that can do many things involving JavaScript code and objects. It can compile and execute scripts, get and set object properties, call JavaScript functions, convert JavaScript data from one type to another, create objects, and so on.

Lastly, the global JSObject is a JavaScript object which contains all the classes, functions, and variables that are available for JavaScript code to use. Whenever a web browser code does something like window.open("http://www.mozilla.org/"), it is accessing a global property, in this case window. SpiderMonkey applications have full control over what global properties scripts can see.

Every SpiderMonkey instance starts out every execution context by creating its JSRunTime, JSContext instances, and a global JSObject. It populates this global object with the standard JavaScript classes, like Array and Object. Then application initialization code will add whatever custom classes, functions, and variables (like window) the application wants to provide; it may be, for a mORMot server application, ORM access or SOA services consumption and/or implementation.

Each time the application runs a JavaScript script (using, for example, JS_EvaluateScript), it provides the global object for that script to use. As the script runs, it can create global functions and variables of its own. All of these functions, classes, and variables are stored as properties of the global object.

Creating your execution context

The main point about those three key elements is that, in the current implementation pattern of SpiderMonkey, runtime, context or global objects are not thread-safe.

Therefore, in the mORMot's use of this library, each thread will have its own instance of each.

In the SynSM.pas unit, a TSMEngine class has been defined to give access to all those linked elements:

  TSMEngine = class
  ...
    /// access to the associated global object as a TSMVariant custom variant
    // - allows direct property and method executions in Delphi code, via
    // late-binding
    property Global: variant read FGlobal;
    /// access to the associated global object as a TSMObject wrapper
    // - you can use it to register a method
    property GlobalObject: TSMObject read FGlobalObject;
    /// access to the associated global object as low-level PJSObject
    property GlobalObj: PJSObject read FGlobalObject.fobj;
    /// access to the associated execution context
    property cx: PJSContext read fCx;
    /// access to the associated execution runtime
    property rt: PJSRuntime read frt;
  ...

Our implementation will define one Runtime, one Context, and one global object per thread, i.e. one TSMEngine class instance per thread.

A JSRuntime, or runtime, is created for each TSMEngine instance. In practice, you won't need access to this value, but rely either on a JSContext or directly a TSMEngine.

A JSContext, or context, will be the main entry point of all SpiderMonkey API, which expect this context to be supplied as parameter. In mORMot, you can retrieve the running TSMEngine from its context by using the function TSMObject.Engine: TSMEngine - in fact, the engine instance is stored in the private data slot of each JSContext.

Lastly, the TSMEngine's global object contains all the classes, functions, and variables that are available for JavaScript code to use. For a mORMot server application, ORM access or SOA services consumption and/or implementation, as stated above.

You can note that there are several ways to access this global object instance, from high-level to low-level JavaScript object types. The TSMEngine.Global property above is in fact a variant. Our SynSM.pas unit defines in fact a custom variant type, identified as the TSMVariant class, able to access any JavaScript object via late-binding, for both variables and functions:

  engine.Global.MyVariable := 1.0594631;
  engine.Global.MyFunction(1,'text');

Most web applications only need one runtime, since they are running in a single thread - and (ab)use of callbacks for non-blocking execution. But in mORMot, you will have one TMSEngine instance per thread, using the TSMEngineManager.ThreadSafeEngine method. Then all execution may be blocking, without any noticeable performance issue, since the whole mORMot threading design was defined to maximize execution resources.

Blocking threading model

This threading model is the big difference with other server-side scripting implementation schemes, e.g. the well-known node.js solution.

Multi-threading is not evil, when properly used. And thanks to the mORMot's design, you won't be afraid of writing blocking JavaScript code, without any callbacks. In practice, those callbacks are what makes most JavaScript code difficult to maintain.

On the client side, i.e. in a web browser, the JavaScript engine only uses one thread per web page, then uses callbacks to defer execution of long-running methods (like a remote HTTP request).
If fact, this is one well identified performance issue of modern AJAX applications. For instance, it is not possible to perform some intensive calculation in JavaScript, without breaking the web application responsiveness: you have to split your computation task in small tasks, then let the JavaScript code pause, until a next piece of computation could be triggered... On server side, node.js allows to define Fibers and Futures - but this is not available on web clients. Some browsers did only start to uncouple the JavaScript execution thread from the HTML rendering thread - and even this is hard to fix... we reached here the limit of a technology rooted in the 80's...

On the server side, node.js did follow this pattern, which did make sense (it allows to share code with the client side, with some name-space tricks), but it is also IMHO a big waste of resources. Why should we stick to an implementation pattern inherited from the 80's computing model, when all CPUs were mono core, and threads were not available?

The main problem when working with one single thread, is that your code shall be asynchronous. Soon or later, you will face a syndrome known as "Callback Hell". In short, you are nesting anonymous functions, and define callbacks. The main issue, in addition to lower readability and being potentially sunk into function() nesting, is that you just lost the JavaScript exception model. In fact, each callback function has to explicitly check for the error (returned as a parameter in the callback function), and handle it.

Of course, you can use so-called Promises and some nice libraries - mainly async.js.
But even those libraries add complexity, and make code more difficult to write. For instance, consider the following non-blocking/asynchronous code:

getTweetsFor("domenic") // promise-returning function
  .then(function (tweets) {
    var shortUrls = parseTweetsForUrls(tweets);
    var mostRecentShortUrl = shortUrls[0];
    return expandUrlUsingTwitterApi(mostRecentShortUrl); // promise-returning function
  })
  .then(httpGet) // promise-returning function
  .then(
    function (responseBody) {
      console.log("Most recent link text:", responseBody);
    },
    function (error) {
      console.error("Error with the twitterverse:", error);
    }
  );

Taken from this web site.

This kind of code will be perfectly readable for a JavaScript daily user, or someone fluent with functional languages.

But the following blocking/synchronous code may sound much more familiar, safer and less verbose, to most Delphi / Java / C# programmer:

try {
  var tweets = getTweetsFor("domenic"); // blocking
  var shortUrls = parseTweetsForUrls(tweets);
  var mostRecentShortUrl = shortUrls[0];
  var responseBody = httpGet(expandUrlUsingTwitterApi(mostRecentShortUrl)); // blocking x 2
  console.log("Most recent link text:", responseBody);
} catch (error) {
  console.error("Error with the twitterverse: ", error);
}

Thanks to the blocking pattern, it becomes obvious that code readability and maintainability is as high as possible, and error detection is handled nicely via JavaScript exceptions, and a global try .. catch.

Last but not least, debugging blocking code is easy and straightforward, since the execution will be linear, following the code flow.

Upcoming ECMAScript 6 should go even further thanks to the yield keyword and some task generators - see taskjs - so that asynchronous code may become closer to the synchronous pattern. But even with yield, your code won't be as clean as with plain blocking style.

In mORMot, we did choose to follow an alternate path, i.e. write blocking synchronous code. Sample above shows how easier it is to work with. If you use it to define some huge business logic, or let a domain expert write the code, blocking syntax is much more straightforward.

Of course, mORMot allows you to use callbacks and functional programming pattern in your JavaScript code, if needed. But by default, you are allowed to write KISS blocking code.

Interaction with existing code

Within mORMot units, you can mix Delphi and JavaScript code by two ways:

  • Either define your own functions in Delphi code, and execute them from JavaScript
  • Or define your own functions in JavaScript code (including any third-party library), and execute them from Delphi.

Like for other part of our framework, performance and integration has been tuned, to follow our KISS way.

You can take a look at "22 - JavaScript HTTPApi web server\JSHttpApiServer.dpr" sample for reference code.

Proper engine initialization

As was previously stated, the main point to interface the JavaScript engine is to register all methods when the TSMEngine instance is initialized.

For this, you set the corresponding OnNewEngine callback event to the main TSMEngineManager instance.
See for instance, in the sample code:

constructor TTestServer.Create(const Path: TFileName);
begin
  ...
  fSMManager := TSMEngineManager.Create;
  fSMManager.OnNewEngine := DoOnNewEngine;
  ...

In DoOnNewEngine, you will initialize every newly created TSMEngine instance, to register all needed Delphi methods and prepare access to JavaScript via the runtime's global JSObject.

Then each time you want to access the JavaScript engine, you will write for instance:

function TTestServer.Process(Ctxt: THttpServerRequest): cardinal;
var engine: TSMEngine;
...
   engine := fSMManager.ThreadSafeEngine;
...  // now you can use engine, e.g. engine.Global.someMethod()

Each thread of the HTTP server thread-pool will be initialized on the fly if needed, or the previously initialized instance will be quickly returned otherwise.

Once you have the TSMEngine instance corresponding to the current thread, you can launch actions on its global object, or tune its execution.
For instance, it could be a good idea to check for the JavaScript VM's garbage collection:

function TTestServer.Process(Ctxt: THttpServerRequest): cardinal;
...
   engine := fSMManager.ThreadSafeEngine;
   engine.MaybeGarbageCollect; // perform garbage collection if needed
...

We will now find out how to interact between JavaScript and Delphi code.

Calling Delphi code from JavaScript

In order to call some Delphi method from JavaScript, you will have to register the method.
As just stated, it is done by setting a callback within TSMEngineManager.OnNewEngine initialization code. For instance:

procedure TTestServer.DoOnNewEngine(const Engine: TSMEngine);
...
  // add native function to the engine
  Engine.RegisterMethod(Engine.GlobalObj,'loadFile',LoadFile,1);
end;

Here, the local LoadFile() method is implemented as such in native code:

function TTestServer.LoadFile(const This: variant; const Args: array of variant): variant;
begin
  if length(Args)<>1 then
    raise Exception.Create('Invalid number of args for loadFile(): required 1 (file path)');
  result := AnyTextFileToSynUnicode(Args[0]);
end;

As you can see, this is perfectly easy to follow.
Its purpose is to load a file content from JavaScript, by defining a new global function named loadFile().
Remember that the SpiderMonkey engine, by itself, does not know anything about file system, database or even DOM. Only basic objects were registered, like arrays. We have to explicitly register the functions needed by the JavaScript code.

In the above code snippet, we used the TSMEngineMethodEventVariant callback signature, marshaling variant values as parameters. This is the easiest method, with only a slight performance impact.

Such methods have the following features:

  • Arguments will be transmitted from JavaScript values as simple Delphi types (for numbers or text), or as our custom TSMVariant type for JavaScript objects, which allows late-binding;
  • The This: variant first parameter map the "callee" JavaScript object as a TSMVariant custom instance, so that you would be able to access the other object's methods or properties directly via late-binding;
  • You can benefit of the JavaScript feature of variable number of arguments when calling a function, since the input arguments is a dynamic array of variant;
  • All those registered methods are registered in a list maintained in the TSMEngine instance, so it could be pretty convenient to work with, in some cases;
  • You can still access to the low-level JSObject values of any the argument, if needed, since they can be trans-typed to a TSMVariantData instance (see below) - so you do not loose any information;
  • The Delphi native method will be protected by the mORMot wrapper, so that any exception raised within the process will be catch and transmitted as a JavaScript exception to the runtime;
  • There is also an hidden set of the FPU exception mask during execution of native code (more on it later on) - you should not bother on it here.

Now consider how you should have written the same loadFile() function via low-level API calls.

First, we register the callback:

procedure TTestServer.DoOnNewEngine(const Engine: TSMEngine);
...
  // add native function to the engine
 Engine.GlobalObject.DefineNativeMethod('loadFile', nsm_loadFile, 1);
end;

Then its implementation:

function nsm_loadFile(cx: PJSContext; argc: uintN; vp: Pjsval): JSBool; cdecl;
var in_argv: PjsvalVector;
    filePath: TFileName;
begin
  TSynFPUException.ForDelphiCode;
  try
    if argc<>1 then
      raise Exception.Create('Invalid number of args for loadFile(): required 1 (file path)');
    in_argv := JS_ARGV(cx,vp);
    filePath := JSVAL_TO_STRING(in_argv[0]).ToString(cx);
    JS_SET_RVAL(cx, vp, cx^.NewJSString(AnyTextFileToSynUnicode(filePath)).ToJSVal);
    Result := JS_TRUE;
  except
    on E: Exception do begin // all exceptions MUST be catched on Delphi side
      JS_SET_RVAL(cx, vp, JSVAL_VOID);
      JSError(cx, E);
      Result := JS_FALSE;
    end;
  end;
end;

As you can see, this nsm_loadFile() function is much more difficult to follow:

  • Your code shall begin with a cryptic TSynFPUException.ForDelphiCode instruction, to protect the FPU exception flag during execution of native code (Delphi RTL expects its own set of FPU exception mask during execution, which does not match the FPU exception mask expected by SpiderMonkey);
  • You have to explicitly catch any Delphi exception which may raise, with a try...finally block, and marshal them back as JavaScript errors;
  • You need to do a lot of manual low-level conversions - via JS_ARGV() then e.g. JSVAL_TO_STRING() macros - to retrieve the actual values of the arguments;
  • And the returning function is to be marshaled by hand - see the JS_SET_RVAL() line.

Since the variant-based callback has only a slight performance impact (nothing measurable, when compared to the SpiderMonkey engine performance itself), and still have access to all the transmitted information, we strongly encourage you to use this safer and cleaner pattern, and do not define any native function via low-level API.

Note that there is an alternate JSON-based callback, which is not to be used in your end-user code, but will be used when marshaling to JSON is needed, e.g. when working with mORMot's ORM or SOA features.

TSMVariant custom type

As stated above, the SynSM.pas unit defines a TSMVariant custom variant type. It will be used by the unit to marshal any JSObject instance as variant.

Via the magic of late-binding, it will allow access of any JavaScript object property, or execute any of its functions. Only with a slightly performance penalty, but with much better code readability than with low-level access of the SpiderMonkey API.

The TSMVariantData memory structure can be used to map such a TSMVariant variant instance. In fact, the custom variant type will store not only the JSObject value, but also its execution context - i.e. JSContext - so is pretty convenient to work with.

For instance, you may be able to write code as such:

function TMyClass.MyFunction(const This: variant; const Args: array of variant): variant;
var global: variant;
begin
  TSMVariantData(This).GetGlobal(global);
  global.anotherFunction(Args[0],Args[1],'test');
  // same as:
  global := TSMVariantData(This).SMObject.Engine.Global;
  global.anotherFunction(Args[0],Args[1],'test');
  // but you may also write directly:
  with TSMVariantData(This).SMObject.Engine do
    Global.anotherFunction(Args[0],Args[1],'test');
  result := AnyTextFileToSynUnicode(Args[0]);
end;

Here, the This custom variant instance is trans-typed via TSMVariantData(This) to access its internal properties.

Calling JavaScript code from Delphi

In order to execute some JavaScript code from Delphi, you should first define the JavaScript functions to be executed.
This shall take place within TSMEngineManager.OnNewEngine initialization code:

procedure TTestServer.DoOnNewEngine(const Engine: TSMEngine);
var showDownRunner: SynUnicode;
begin
  // add external JavaScript library to engine (port of the Markdown library)
  Engine.Evaluate(fShowDownLib, 'showdown.js');
  // add the bootstrap function calling loadfile() then showdown's makeHtml()
  showDownRunner := AnyTextFileToSynUnicode(ExeVersion.ProgramFilePath+'showDownRunner.js');
  Engine.Evaluate(showDownRunner, 'showDownRunner.js');
  ...

This code first evaluates (i.e. "executes") a general-purpose JavaScript library contained in the showdown.js file, available in the sample executable folder. This is an open source library able to convert any Markdown markup into HTML. Plain standard JavaScript code.

Then we evaluate (i.e. "execute") a small piece of JavaScript code, to link the makeHtml() function of the just defined library with our loadFile() native function:

function showDownRunner(pathToFile){
  var src = loadFile(pathToFile);            // call Delphi native code
  var converter = new Showdown.converter();  // get the Showdown converted
  return converter.makeHtml(src);            // convert .md content into HTML via showdown.js
}

Now we have a new global function showDownRunner(pathToFile) at hand, ready to be executed by our Delphi code:

function TTestServer.Process(Ctxt: THttpServerRequest): cardinal;
var content: variant;
    FileName, FileExt: TFileName;
    engine: TSMEngine;
  ...
  if FileExt='.md' then begin
  ...
    engine := fSMManager.ThreadSafeEngine;
  ...
    content := engine.Global.showDownRunner(FileName);
  ...

As you can see, we access the function via late-binding. Above code is perfectly readable, and we call here a JavaScript function and a whole library as natural as if it was native code.

Without late-binding, we may have written, accessing not the Global TSMVariant instance, but the lower level GlobalObject: TSMObject property:

  ...
    content := engine.GlobalObject.Run('showDownRunner',[SynUnicode(FileName)]);
  ...

It is up to you to choose which kind of code you prefer, but late-binding is worth considering.

Next step on our side is to directly allow access to mORMot's ORM and SOA features, including interface-based services.
Feedback is welcome on our forum, as usual.