JavaScript Integration for Dynamic Applications
mORMot provides JavaScript scripting capabilities for applications that need dynamic behavior, user-defined workflows, or hot-reloadable business logic. This chapter covers the scripting architecture and available engines.
| Scenario | Benefit |
|---|---|
| User-defined workflows | End-users customize behavior without recompilation |
| Business rules | Domain experts define evolving logic |
| Customization | Single executable serves multiple clients |
| Reporting | Dynamic report definitions |
| Plugin systems | Third-party extensions |
| Rapid prototyping | Quick iteration without compilation |
mORMot chose JavaScript for scripting because:
┌─────────────────────────────────────────────────────────────────┐
│ Application Code │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ mormot.script.core.pas │
│ (Abstract Engine Management Layer) │
│ │
│ ┌─────────────────────┐ ┌──────────────────────────────┐ │
│ │ TThreadSafeManager │ │ TThreadSafeEngine (abstract) │ │
│ │ │ │ │ │
│ │ • Engine pooling │ │ • Per-thread execution │ │
│ │ • Thread safety │ │ • Context isolation │ │
│ │ • Hot reload │ │ • FPU state protection │ │
│ │ • Remote debugging │ │ • Lifecycle hooks │ │
│ └─────────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ Engine Implementations │
│ │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ mormot.lib.quickjs │ │ (SpiderMonkey planned) │ │
│ │ │ │ │ │
│ │ • ES2020 support │ │ • JIT compilation │ │
│ │ • Small footprint │ │ • High performance │ │
│ │ • Static linking │ │ • node.js compatible │ │
│ └──────────────────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Engine | Use Case | Status |
|---|---|---|
| QuickJS | Client-side, embedded | Available (via mormot.lib.quickjs) |
| SpiderMonkey | Server-side, high-performance | Planned |
QuickJS is a small, embeddable JavaScript engine by Fabrice Bellard:
The mormot.lib.quickjs.pas unit provides direct QuickJS bindings:
uses
mormot.lib.quickjs;
var
Runtime: JSRuntime;
Context: JSContext;
Value: JSValue;
Script: RawUtf8;
begin
// Create runtime and context
Runtime := JS_NewRuntime;
Context := JS_NewContext(Runtime);
try
// Execute JavaScript
Script := 'function add(a, b) { return a + b; } add(2, 3);';
Value := JS_Eval(Context, pointer(Script), length(Script),
'script.js', JS_EVAL_TYPE_GLOBAL);
// Check result
if JS_IsNumber(Value) then
Writeln('Result: ', JS_ToFloat64(Context, Value));
JS_FreeValue(Context, Value);
finally
JS_FreeContext(Context);
JS_FreeRuntime(Runtime);
end;
end;
QuickJS requires the LIBQUICKJSSTATIC conditional:
// In project options or .inc file:
{$define LIBQUICKJSSTATIC}
Static libraries are located in:
/mnt/w/mORMot2/static/
libquickjs.a // Linux/macOS
libquickjs.obj // Windows
The TThreadSafeManager class provides thread-safe engine pooling:
uses
mormot.script.core;
type
// Your engine implementation (inherits from TThreadSafeEngine)
TMyQuickJSEngine = class(TThreadSafeEngine)
protected
procedure AfterCreate; override;
procedure DoBeginRequest; override;
procedure DoEndRequest; override;
end;
var
Manager: TThreadSafeManager;
begin
Manager := TThreadSafeManager.Create(TMyQuickJSEngine);
try
// Configure expiration (recommended for long-running servers)
Manager.EngineExpireTimeOutMinutes := 240; // 4 hours
// Use in request handlers...
finally
Manager.Free;
end;
end;
procedure HandleRequest;
var
Engine: TThreadSafeEngine;
begin
// Get or create engine for current thread
Engine := Manager.ThreadSafeEngine;
// Execute within protected context
Engine.ThreadSafeCall(
procedure(E: TThreadSafeEngine)
begin
// FPU state is protected here
// Execute JavaScript safely
end);
end;
┌────────────────────────────────────────────────────────────────┐
│ Thread Safety Model │
├────────────────────────────────────────────────────────────────┤
│ │
│ Thread 1 ──────► Engine 1 (exclusive) │
│ │
│ Thread 2 ──────► Engine 2 (exclusive) │
│ │
│ Thread 3 ──────► Engine 3 (exclusive) │
│ │
│ ▲ │
│ │ │
│ ┌────────────────┴────────────────┐ │
│ │ TThreadSafeManager │ │
│ │ (thread-safe pool management) │ │
│ └─────────────────────────────────┘ │
│ │
│ Key Rules: │
│ • One engine per thread (never shared) │
│ • Manager handles allocation/deallocation │
│ • Automatic expiration prevents memory leaks │
│ │
└────────────────────────────────────────────────────────────────┘
// Application detects script changes
if ScriptFilesModified then
begin
// Increment version - engines will recreate on next access
Manager.ContentVersion := Manager.ContentVersion + 1;
end;
// In request handler
Engine := Manager.ThreadSafeEngine;
if Engine.ContentVersion <> Manager.ContentVersion then
begin
// Engine was recreated - reload scripts
LoadScriptsIntoEngine(Engine);
Engine.ContentVersion := Manager.ContentVersion;
end;
For long-running servers, prevent JavaScript memory leaks:
// Recreate engines every 4 hours
Manager.EngineExpireTimeOutMinutes := 240;
// Mark critical engines as permanent
MainEngine.NeverExpire := true;
mORMot implements the Firefox Remote Debugging Protocol:
// Start debugger on port 6000
Manager.StartDebugger('6000');
// Optional: Break on first line
Manager.PauseDebuggerOnFirstStep := true;
1. Open Firefox
2. Navigate to about:debugging
3. Click "This Firefox" → "Settings"
4. Enable Remote Debugging
5. Connect to localhost:6000
Note: This uses Firefox DevTools protocol, NOT Chrome DevTools.
// Register native function to access ORM
procedure RegisterOrmAccess(Engine: TThreadSafeEngine);
begin
// Example: Expose Customer retrieval
Engine.RegisterFunction('getCustomer',
function(Args: TJSArgs): TJSValue
var
ID: TID;
Customer: TOrmCustomer;
begin
ID := Args[0].AsInt64;
Customer := TOrmCustomer.Create;
try
if Server.Orm.Retrieve(ID, Customer) then
Result := CustomerToJS(Customer)
else
Result := JS_NULL;
finally
Customer.Free;
end;
end);
end;
// JavaScript can call interface-based services
Engine.RegisterFunction('callService',
function(Args: TJSArgs): TJSValue
var
ServiceName, MethodName: RawUtf8;
Params: TDocVariantData;
begin
ServiceName := Args[0].AsString;
MethodName := Args[1].AsString;
Params := Args[2].AsVariant;
// Execute service and return result as JSON
Result := ExecuteServiceMethod(ServiceName, MethodName, Params);
end);
// Define validation rules in JavaScript
const
VALIDATION_SCRIPT = '''
function validateOrder(order) {
if (order.total > 10000 && !order.managerApproval) {
return { valid: false, error: "Manager approval required" };
}
if (order.items.length === 0) {
return { valid: false, error: "Order must have items" };
}
return { valid: true };
}
''';
// Use from Delphi
function ValidateOrder(const Order: TOrder): boolean;
var
Engine: TThreadSafeEngine;
Result: Variant;
begin
Engine := Manager.ThreadSafeEngine;
Result := Engine.Call('validateOrder', [OrderToVariant(Order)]);
ValidateOrder := Result.valid;
if not ValidateOrder then
raise EValidation.Create(Result.error);
end;
mORMot provides a custom variant type for seamless JavaScript access:
var
JSObj: Variant;
begin
// Create JavaScript object
JSObj := Engine.NewObject;
// Late-binding property access (like JavaScript)
JSObj.name := 'John';
JSObj.age := 30;
JSObj.greet := Engine.Function('return "Hello, " + this.name');
// Call method
Writeln(JSObj.greet()); // "Hello, John"
end;
var
JSArray: Variant;
begin
JSArray := Engine.NewArray;
// Push elements
JSArray.push(1);
JSArray.push(2);
JSArray.push(3);
// Access by index
Writeln(JSArray[0]); // 1
// Array methods
JSArray.sort();
JSArray.reverse();
end;
| Unit | Purpose |
|---|---|
mormot.script.core |
Abstract engine management, thread pooling |
mormot.script.quickjs |
QuickJS high-level wrapper (in development) |
mormot.lib.quickjs |
Low-level QuickJS API bindings |
mormot.lib.static |
Static library loading infrastructure |
TThreadSafeEngine and TThreadSafeManagermormot.lib.quickjs)mormot.script.quickjs high-level wrapper| Use Scripting For | Use Compiled Code For |
|---|---|
| User-customizable logic | Performance-critical paths |
| Frequently changing rules | Core business logic |
| Plugin/extension systems | Security-sensitive operations |
| Rapid prototyping | Database access layer |
| Report templates | Infrastructure code |
// Limit script capabilities
Engine.DisableFileAccess := true;
Engine.DisableNetworkAccess := true;
Engine.MaxExecutionTimeMs := 5000; // 5 second timeout
Engine.MaxMemoryBytes := 64 * 1024 * 1024; // 64MB limit
try
Result := Engine.Eval(Script);
except
on E: EJSException do
begin
Log.Error('JavaScript error at line %d: %s',
[E.LineNumber, E.Message]);
// E.StackTrace contains full JS stack
end;
end;
| Need | Solution |
|---|---|
| Embed JavaScript | mormot.lib.quickjs + LIBQUICKJSSTATIC |
| Thread-safe execution | TThreadSafeManager |
| Engine pooling | Manager.ThreadSafeEngine |
| Hot reload | Manager.ContentVersion |
| Remote debugging | Manager.StartDebugger('6000') |
| Memory management | EngineExpireTimeOutMinutes |
| QuickJS | SpiderMonkey (when available) |
|---|---|
| Client-side scripts | Server-side heavy processing |
| Small footprint needed | JIT performance required |
| Static linking preferred | node.js compatibility needed |
| ES2020 sufficient | Latest ECMAScript features |
Note: The scripting layer in mORMot2 is actively evolving. Check the source code and forum for the latest implementation status.
Next: Chapter 23 covers Asymmetric Encryption (ECC) for secure communications.
| Previous | Index | Next |
|---|---|---|
| Chapter 21: Security | Index | Chapter 23: Asymmetric Encryption |