Skip to content

Latest commit

 

History

History
523 lines (376 loc) · 21 KB

File metadata and controls

523 lines (376 loc) · 21 KB

← Build · Back to README · Build Notes →

Examples

Twenty self-contained Delphi applications demonstrating real-world use of DelphiLibNodeJS.

Each example is a Delphi console application with an accompanying JS script. All examples use the shared helper unit ExHelpers.pas.


Quick Start

# Build all examples
.\examples\BuildExamples.ps1

# Run any example
.\examples\02_bidirectional\Win64\Release\ExBidirectional.exe
.\examples\07_bridge\Win64\Release\ExBridge.exe
.\examples\17_generic_handler\Win64\Release\ExGenericHandler.exe

Each Win64\Release\ directory contains the .exe and accompanying .js/.json files. The DLL is located in build\dll\ and is resolved automatically via GetDllPath in ExHelpers.pas.


Prerequisites

Requirement Note
DelphiLibNodeJS.dll Build via scripts\BuildHostDll.ps1
RAD Studio 12+ Examples 1–19 (example 14 — DCC64 + WebView2)
npm in PATH Required only for examples 1, 5, 6, 8, 9, 10
MySQL server Required only for examples 5 and 8
Internet (first run) npm downloads packages; example 6 uses OpenStreetMap CDN

Shared node_modules: examples 1, 5 and 6 use the shared directory examples\common\node_modules.


Examples Overview

# Directory Demonstrates
0 00_basic Smoke test: factory/runtime/log/QI/PathToFileUrl
1 01_templating npm install + Handlebars HTML report from JSON data
2 02_bidirectional Bidirectional RPC via RunScriptText — no JS files on disk
3 03_pipeline ESM entry script, top-level await, Promise.all, crypto/perf_hooks
4 04_callback Push-callback mode, CRITICAL_SECTION, Win32 event — async DB simulation
5 05_mysql npm install mysql2, connect to MySQL, display tables and data
6 06_gis npm install @turf/turf, K-nearest cities, convex hull, K-Means, Leaflet HTML map
7 07_bridge INodeBridge — bidirectional Delphi↔JS calls, Direction A + B
8 08_geodata INodeBridge + mysql2 + d3-node: load MySQL spatial data, render SVG
9 09_osm Overpass API, d3-node SVG, admin boundaries + city labels
10 10_vectortiles node:sqlite + proj4, HTTP server, OpenLayers GeoJSON map
11 11_embedded_npm INodeNpmRunner — embedded npm without system npm
12 12_bidir_metrics Direction B live-pull: JS reads Delphi metrics synchronously
13 13_async_calls PostFunction — non-blocking JS calls from Delphi
14 14_webbrowser VCL + TEdgeBrowser (WebView2), bidirectional bridge with UI
15 15_marshal Full Automation type matrix through the bridge
16 16_inspect_repl V8 Inspector (CDP), Evaluate, IDNCallProxy typed calls
17 17_generic_handler BridgeRegisterWorkerHandler, TDNJson, IDNMainThreadLog, IDNRuntimeSession, WithEnvPair
18 18_dispatch_proxy TDNDispatchObject, BridgeRegisterWorkerDispatch, TDNJson.ArgObj, IDNAsyncCallProxy (PostStr/PostFloat)
19 19_binary_transfer Binary transfer v3.2.1: CallFunctionWithBuffer, CallFunctionWithStream, CallFunctionWithDataObject, PostFunctionWithBuffer, AllocSharedBuffer/IDNSharedBufferHandle

Example 0 — Basic Smoke Test (00_basic)

What it demonstrates: minimal DLL health check: factory/runtime creation, log polling, QI, PathToFileUrl.

Key APIs: DN_CreateFactory, INodeFactory.CreateRuntime, INodeRuntime.Start/Shutdown, INodeLog.Poll, DN_PathToFileUrl


Example 1 — Handlebars Templating (01_templating)

What it demonstrates: how to run npm install, load the Handlebars templating engine, and get an HTML report from Delphi.

Delphi (ExTemplating.dpr):

  • Checks for shared node_modules, installs handlebars if needed
  • Runs render.js, polls the log via INodeLog.Poll, collects HTML strings
  • Saves to ExTemplating_out.html

JavaScript (render.js):

  • Handlebars CJS renderer with 5 custom helpers (currency, revenue, pct, ifEven, bar)
  • Reads report-data.json (10 products, Q4 2024 sales)
  • Logs each HTML string with the [RENDER] prefix

Key APIs: INodeNpm.InstallFromPackageJson, INodeLog.Poll, PollCollect


Example 2 — Bidirectional RPC (02_bidirectional)

What it demonstrates: bidirectional data exchange, embedded JS scripts (no files on disk), keep-alive pattern.

Highlights:

  • All JS scripts are embedded as Delphi string constants — no .js files
  • Keep-alive pattern: setInterval keeps the event loop alive
  • 5 JS commands injected via RunScriptText after Start
  • JSON request/response, regex text processing, async timers, graceful shutdown

Key APIs: INodeRuntime.RunScriptText, globalThis persistence between injections

This example is the best starting point for learning the API. Requires no npm and no files.


Example 3 — ESM Async Pipeline (03_pipeline)

What it demonstrates: ESM modules (.mjs), top-level await, parallel execution via Promise.all, standard Node.js modules.

JavaScript (pipeline.mjs):

  • ESM with top-level await and import
  • Promise.all for parallel document processing
  • SHA-256 hashes, word statistics, lexical density
  • Uses node:crypto, node:os, node:perf_hooks

Key APIs: DN_MODULE_FORMAT_AUTO, ESM auto-detect, INodeLog.Poll


Example 4 — Push Callback + In-Memory DB (04_callback)

What it demonstrates: push-mode logging, thread-safe callback, Win32 synchronization.

JavaScript (multidb.js):

  • 6-stage async DB simulation: schema → users → orders → JOIN → dept aggregates → top customers
  • Seeded RNG, tick() yields event loop between stages

Delphi (ExCallback.dpr):

  • TCallbackCtx with TRTLCriticalSection + TStringList + Win32 manual-reset event
  • Callback registered via INodeLog.SetCallback before Start
  • WaitForSingleObject waits for JS work to complete

Key APIs: INodeLog.SetCallback, thread safety, WaitForSingleObject

⚠️ The callback is called on the Node.js event loop thread — always wrap the body in try..except.


Example 5 — MySQL Viewer (05_mysql)

What it demonstrates: connecting to a real database, passing configuration via a JSON file, interactive password prompt.

JavaScript (mysql_viewer.js):

  • mysql2/promise — async/await API
  • SHOW DATABASES, SHOW TABLES, SHOW COLUMNS, SELECT COUNT(*) in parallel
  • SELECT * LIMIT 5 with ASCII box-table formatting

Key APIs: INodeNpm, JSON config file pattern, INodeLog.Poll

A running MySQL server is required for this example.


Example 6 — GIS World Cities Viewer (06_gis)

What it demonstrates: spatial analysis, K-nearest neighbours, interactive HTML map generation.

JavaScript (gis_processor.js):

  • 89 world cities with lat/lon coordinates and population
  • @turf/turf: turf.distance, turf.convex, turf.clustersKmeans
  • Generates interactive HTML with Leaflet + OpenStreetMap

Key APIs: INodeNpm, INodeLog.Poll, ShellExecute


Example 7 — INodeBridge: Bidirectional Delphi↔JS Bridge (07_bridge)

What it demonstrates: full bidirectional bridge between Delphi and JavaScript via INodeBridge.

Direction A (Delphi → JS): INodeBridge.CallFunction — call a JS function by name with JSON arguments.

Direction B (JS → Delphi): register a Delphi object via TDNExport.Wrap — JS calls Delphi methods as bridge.ns.method(...).

Key APIs: INodeBridge, CallFunction, RegisterExport, TDNCallProxy, TDNExport.Wrap, IDNMainDispatcher


Example 8 — GeoData Bridge: MySQL → d3-node SVG (08_geodata)

What it demonstrates: INodeBridge.CallFunction (Direction A) — passing MySQL configuration from Delphi to JS; loading spatial data; rendering SVG without a browser.

Key APIs: INodeBridge.CallFunction, TDNCallProxy, d3-node, mysql2/promise

A MySQL server with a geometry table is required for this example.


Example 9 — OSM Map Viewer (09_osm)

What it demonstrates: fetching OpenStreetMap data via the Overpass API, SVG rendering via d3-node.

JavaScript (osm_service.js):

  • query-overpass — Overpass API queries
  • d3-node geoMercator — renders admin boundaries + city labels
  • Saves SVG, signals [OSM:done]

Key APIs: INodeBridge.CallFunction, TDNCallProxy, ShellExecute


Example 10 — Vector Tiles HTTP Server (10_vectortiles)

What it demonstrates: embedded HTTP server in Node.js, SQLite (node:sqlite), coordinate projection (proj4), OpenLayers map.

JavaScript (geo_tiles.js):

  • node:sqlite — built-in SQLite (no npm required)
  • proj4 — UTM40N → WGS84 coordinate conversion
  • wkt — WKT geometry parsing
  • HTTP server serves GeoJSON and HTML with OpenLayers

Delphi (ExVectorTiles.dpr):

  • Starts the HTTP server via Bridge.CallFunction('startServer')
  • Opens browser via ShellExecute
  • Stops the server via Bridge.CallFunction('stopServer') on exit

Key APIs: INodeBridge.CallFunction, node:sqlite, HTTP server from Delphi


Example 11 — Embedded npm (11_embedded_npm)

What it demonstrates: INodeNpmRunner — embedded npm without needing a globally installed npm.

Delphi (ExEmbeddedNpm.dpr):

  • Gets INodeNpmRunner via Factory.GetNpmRunner
  • Installs lodash into the working directory
  • Runs data_processor.js

Key APIs: INodeNpmRunner, INodeNpmRunner.Install, INodeNpmRunner.Run


Example 12 — Bidirectional Bridge Metrics (12_bidir_metrics)

What it demonstrates: "live data pull" pattern — JS synchronously reads Delphi metrics via Direction B at the time of each Direction A call.

Delphi (ExBidirMetrics.dpr):

  • TServerMetrics — published object with CPU/Memory/Users/Errors metrics
  • BridgeRegisterWorker(Bridge, 'srv', FMetrics) — Direction B (no parameters)
  • Three scenarios: Baseline → Load spike → Recovery
  • Direction A: Proxy.Call('snapshot'), getHealthScore(), getAlert()

Key APIs: BridgeRegisterWorker, IDNCallProxy, TDNExport.Wrap


Example 13 — Async Direction A with PostFunction (13_async_calls)

What it demonstrates: INodeBridge.PostFunction — non-blocking JS calls from Delphi.

Delphi (ExAsyncCalls.dpr):

  • BridgePostFunctionOnMainClosure — anonymous callback on the main thread
  • IDNMainDispatcher — hidden window for WM_DN_BRIDGE_RESULT
  • Three parallel calls: greetAsync, computeAsync, failAsync
  • Message pump waits for three callbacks

Key APIs: PostFunction, BridgePostFunctionOnMainClosure, IDNMainDispatcher


Example 14 — WebBrowser Bridge (14_webbrowser)

What it demonstrates: VCL GUI with TEdgeBrowser (WebView2), bidirectional bridge via window.chrome.webview.postMessage.

Note: DCC64 only (RAD Studio), requires WebView2Loader.dll.

Key APIs: TEdgeBrowser, INodeBridge, TDNExport.Wrap, Direction A + B


Example 15 — Type Marshal Round-Trip (15_marshal)

What it demonstrates: full Automation type matrix through the bridge in both directions.

Direction B → Delphi: JS calls bridge.types.getXxx() — each method returns a distinct type: Integer (VT_I4) → Int64 (VT_I8, BigInt) → Double (VT_R8) → Currency (VT_CY) → TDateTime (VT_DATE) → Boolean (VT_BOOL) → WideString (VT_BSTR) → OleVariantSAFEARRAY.

Direction A → JS: Delphi calls marshalArgs() with the full set of types, including {"$bigint":"..."} for Int64.

Also demonstrates: IDNArgs, IDNCallProxy, TDNResult, TDNSafeArrayBuilder.

Key APIs: TDNSafeArrayBuilder, IDNArgs, IDNCallProxy, TDNResult, full VT_* matrix


Example 16 — V8 Inspector + Evaluate (16_inspect_repl)

What it demonstrates: V8 Inspector (Chrome DevTools Protocol) and INodeBridge.Evaluate.

Delphi (ExInspectRepl.dpr):

  • Runtime.EnableInspector(nil, 9229, False) — opens WebSocket debug port before Start
  • BridgeEvaluate(Bridge, expr) — evaluate JS expressions without INodeCallResult boilerplate
  • IDNCallProxy.Evaluate* / Call* — typed variants (EvaluateInt, CallStr, ...)
  • IDNArgs fluent builder — builds a JSON argument array without string concatenation
  • TDNResult static extractors — decode JSON result into typed Delphi values
  • CallResultError / CallResultErrorName / CallResultStack — read error details

Key APIs: EnableInspector, Evaluate, IDNCallProxy, IDNArgs, TDNResult

chrome://inspect → Configure → 127.0.0.1:9229

Example 17 — Generic Bridge Handlers (17_generic_handler)

What it demonstrates: a set of high-level V3 APIs in a single example: BridgeRegisterWorkerHandler, TDNJson, IDNMainThreadLog, IDNRuntimeSession, WithEnvPair, RuntimeStateName.

Delphi (ExGenericHandler.dpr):

  1. TDNRuntimeConfigBuilder.WithEnvPair — injects environment variables into the Node.js process:

    RtCfg := TDNRuntimeConfigBuilder.Default
      .WithScriptsPath(ScriptDir)
      .WithModuleFormat(DN_MODULE_FORMAT_COMMONJS)
      .WithEnvPair('SERVICE_NAME', 'generic-handler-demo')
      .WithEnvPair('SERVICE_VER',  '1.0');
  2. CreateDNRuntimeSession — replaces the four-step initialization (factory → runtime → QI → Start) with a single call:

    Session := CreateDNRuntimeSession(RtCfg);
    // Session.Bridge, Session.Log, Session.Runtime, Session.Npm are ready
  3. RuntimeStateName — human-readable state name:

    if Session.Runtime.GetState(State) = S_OK then
      WriteLn(RuntimeStateName(State)); // → 'RUNNING'
  4. IDNMainThreadLog / CreateDNMainThreadLog — delivers log entries to the main thread via a hidden HWND_MESSAGE window:

    MainLog := CreateDNMainThreadLog(Session.Log, App.OnLog);
    // App.OnLog is called on the main thread for each console.log from JS
  5. BridgeRegisterWorkerHandler — registers a Direction B handler as an anonymous procedure (without a published class):

    BridgeRegisterWorkerHandler(Session.Bridge, 'calc',  HandleCalc);
    BridgeRegisterWorkerHandler(Session.Bridge, 'store', HandleStore);
  6. TDNJson — parse arguments and build results inside a handler:

    procedure HandleCalc(const Method, ArgsJson: string; var ResultJson: string);
    var Args: TJSONArray;
    begin
      Args := TDNJson.ParseArray(ArgsJson);
      try
        if Method = 'add' then
          ResultJson := TDNJson.ResultInt(
                          TDNJson.ArgInt(Args, 0) + TDNJson.ArgInt(Args, 1))
        else if Method = 'divide' then
          if TDNJson.ArgInt(Args, 1) = 0 then
            ResultJson := TDNJson.Error('Division by zero')  // raises JS Error on the JS side
          ...
      finally Args.Free; end;
    end;

JavaScript (handler_service.js):

  • Reads process.env.SERVICE_NAME and SERVICE_VER
  • Calls bridge.calc.add/mul/greet/divide and bridge.store.set/get
  • Demonstrates error catching via try/catch for divide(10, 0)
  • Signals [DONE] on completion
Sample output:
[state] After CreateDNRuntimeSession: RUNNING
[delphi] Registered bridge.calc and bridge.store
[delphi] handler_service.js queued to event loop
[log] [env] SERVICE_NAME = generic-handler-demo
[log] [env] SERVICE_VER  = 1.0
[log] [calc] add(10, 32)  = 42
[log] [calc] mul(6,  7)   = 42
[log] [calc] greet        = Hello, World!
[log] [calc] divide(10, 0) caught: Division by zero
[log] [store] get("user") = alice
[log] [store] get("role") = admin
[log] [store] get("nope") = null
[log] [DONE]
[state] After Shutdown: STOPPED
Example 17 completed successfully.

Example 18 — TDNDispatchObject + IDNAsyncCallProxy (18_dispatch_proxy)

Files: examples\18_dispatch_proxy\ExDispatchProxy.dpr, dispatch_service.js

Demonstrates all four V3.x NodeBridgeHelpers improvements:

Feature Where used
TDNDispatchObject — abstract base class TProductCatalog.DoDispatch
BridgeRegisterWorkerDispatch — one-line registration BridgeRegisterWorkerDispatch(Bridge, 'catalog', Catalog)
TDNJson.ArgObj / ArgObjStr / ArgObjInt — named arguments search({query:'node', limit:3})
IDNAsyncCallProxy.PostStr / PostFloat — typed async Direction A Proxy.PostStr('getRecommendations', ...)

What the example does:

  1. Creates TProductCatalog : TDNDispatchObject handling methods:
    • search({query, limit}) — filter by name, named arguments via TDNJson.ArgObjStr/Int
    • getById(id) — by product ID
    • getSummary() — count + average price
  2. Registers the object as bridge.catalog via BridgeRegisterWorkerDispatch
  3. Runs dispatch_service.js, which calls all three Direction B methods
  4. Creates IDNAsyncCallProxy and makes two Direction A calls:
    • getRecommendations('books')PostStr → string callback
    • computeDiscount(100, 15)PostFloat → Double callback
  5. Collects results via the message loop, sends __doneCb signal to terminate JS
[delphi] Registered bridge.catalog (TProductCatalog)
[delphi] dispatch_service.js queued
[delphi] --- Direction A: IDNAsyncCallProxy ---
[delphi] 2 async calls posted, pumping message loop...
[async] getRecommendations("books") → Clean Code, SICP, The Pragmatic Programmer
[async] computeDiscount(100, 15%) → 85.00
Example 18 completed successfully.

Example 19 — Binary Data Transfer (19_binary_transfer)

Added in v3.2.1. Files: examples\19_binary_transfer\ExBinaryTransfer.dpr, binary_service.js

Demonstrates all binary APIs added in v3.2.1: TBytesUint8Array transfer through the bridge.

Demo API Description
A CallFunctionWithBuffer Send TBytes as Uint8Array, receive TBytes back
B CallFunction + CallResultBytes / GetStream Receive binary result as TBytes or IStream
C CallFunctionWithStream Send data via IStream (BytesToStream)
D AllocSharedBuffer + IDNSharedBufferHandle Allocate a V8 SharedArrayBuffer, write from Delphi, read from JS
E PostFunctionWithBuffer Async buffer send, result via callback

JavaScript (binary_service.js):

  • invertBytes(buf) — XOR each byte with 0xFF, returns Uint8Array
  • makePng(w, h) — synthetic 24-byte PNG header (for GetBytes/GetStream verification)
  • echoBuffer(buf) — echo buffer (async, for PostFunctionWithBuffer)
  • processImage(meta, buf) — named arguments: {operation: 'invert'} or {operation: 'sum'}
  • readShared(name) — reads globalThis.__dn_buffers[name], returns JSON byte array

Key points:

  • AllocSharedBuffer must be called after Runtime.Start (requires a live V8 isolate)
  • SAB layout: [4-byte Int32 control word][payload]; Delphi writes via SABWriteBytes, JS reads via Uint8Array view
  • After using SAB: Handle.Invalidate removes it from globalThis.__dn_buffers, Handle := nil releases the backing store
  • CallResultBytes / StreamToBytes / BytesToStream — high-level helpers in NodeBridgeHelpers.pas
Sample output:
Example 19 — Binary Data Transfer (v3.2.1)
Demo A — CallFunctionWithBuffer (invertBytes)
  Input:  01 02 03 04
  Output: FE FD FC FB
  PASS
Demo B — GetBytes + GetStream (makePng)
  Length: 24 bytes
  Header (first 8): 89 50 4E 47 0D 0A 1A 0A
  PASS — PNG magic verified
  GetStream: PASS (same 24 bytes)
Demo C — CallFunctionWithStream
  Input:  10 20 30 40
  Output: EF DF CF BF
  PASS
Demo D — AllocSharedBuffer / IDNSharedBufferHandle
  AllocSharedBuffer + SABWriteBytes([DE AD BE EF]) — OK
  readShared("signature") = [222,173,190,239]
  PASS — values 0xDE(222) and 0xAD(173) found
Demo E — PostFunctionWithBuffer (async)
  Received: AA BB CC
  PASS
ALL BINARY TRANSFER DEMOS PASSED

Shared Helper Unit (ExHelpers.pas)

Function Purpose
GetDllPath Return the path to DelphiLibNodeJS.dll relative to the .exe
PollForMessage Poll the log with a timeout, return the first matching message
PollCollect Collect all messages with a given prefix up to a done-marker
MakeTempDir Create a unique temporary directory
WriteFileAnsi Write raw bytes (UTF-8 JS/JSON) to a file
GetCommonDir Return the path to the shared examples\common directory
Banner, Sep Console formatting helpers
LevelChar Convert DN_LOG_* level to a display character

Individual Build

# With RAD Studio (DCC64)
call "D:\Embarcadero RAD Studio\23.0\bin\rsvars.bat"
dcc64 -U"%BDS%\lib\win64\release;<repo>\source;<repo>\examples" ^
      -E"examples\17_generic_handler\Win64\Release" ^
      -NSSystem;Winapi ^
      examples\17_generic_handler\ExGenericHandler.dpr

See Also