diff --git a/rag/04-idioms.md b/rag/04-idioms.md index 452035e..f174ab3 100644 --- a/rag/04-idioms.md +++ b/rag/04-idioms.md @@ -217,30 +217,34 @@ module, blank-import with `--rtl`, call the registered function. Single static b no runtime deps. Use whenever a good Go library exists (Postgres, PDF, XLSX, crypto/rand, argon2, bluemonday). -**(b) npm package via the pure-Go Node bridge (`nodertl`).** When the library you want -is npm-only (QR, charting, JS SDKs), call Node from Go as a **subprocess** — no CGO, -no C addon. RTL lives in `fivenode_go/hbrtl_ext/nodertl`: +**(b) npm package via the handle-based Node bridge (`nodebridge`).** The original +C++ fivenode was `Harbour ⇄ C glue ⇄ Node(npm)` — JS always ran in real Node, the C was +only a JSON glue. The faithful Go port replaces the *glue* (not Node) with Go: +`Five(Go) ⇄ nodebridge(Go) ⇄ persistent node ⇄ npm`. Same handle protocol as the C++ +version (`require` → handle, `call` → method with auto-await, `end`), **no C/CGO**, and +**not** `node -e` string eval. RTL lives in `fivenode_go/hbrtl_ext/nodebridge`: ```five -// NODE_EVAL(cJs [, cWorkdir [, cInput]]) -> {"ok","out","err"} JSON -// cWorkdir = dir holding node_modules; cInput → env FIVE_NODE_INPUT (no code injection) -LOCAL cJs := "const qr=require('qrcode');" + ; - "qr.toString(process.env.FIVE_NODE_INPUT,{type:'svg'})" + ; - ".then(s=>process.stdout.write(s)).catch(e=>{process.stderr.write(String(e));process.exit(1);});" -LOCAL hRes := hb_jsonDecode( NODE_EVAL( cJs, hb_GetEnv("SOLMADE_NODE_DIR","./node"), cUrl ) ) -IF hb_HGetDef( hRes, "ok", .f. ) - cSvg := hb_HGetDef( hRes, "out", "" ) +// FN_REQUIRE(cModule) -> nHandle ; FN_CALL(nH,cMethod[,cArgsJson]) -> {ok,type,value,err} +LOCAL nH := FN_REQUIRE( "qrcode" ) // npm module handle (persistent) +LOCAL cArgs := hb_jsonEncode( { cUrl, { "type" => "svg", "width" => 280 } } ) // JSON arg array +LOCAL hR := hb_jsonDecode( FN_CALL( nH, "toString", cArgs ) ) // async method auto-awaited +FN_END( nH ) +IF hb_HGetDef( hR, "ok", .f. ) + cSvg := hb_HGetDef( hR, "value", "" ) // type: string|number|boolean|json|buffer(b64) ENDIF ``` -- Setup: a `node/` dir with `package.json` + `npm install `; point cWorkdir there. -- The JS prints its result to `process.stdout`; pass user data via `cInput` - (env `FIVE_NODE_INPUT`), never by string-concatenating into the JS. -- The Go binary stays pure Go and only spawns `node` when this RTL is called. -- Real example: solmade `app/api/article_qr.prg` → npm `qrcode` → SVG, verified. +- Setup: a `node/` dir with `package.json` + `npm install `; the bridge points + `NODE_PATH` there (default dir via env `SOLMADE_NODE_DIR`). +- One persistent `node` per process; calls are serialized; the Go binary stays pure Go + and only spawns node when `FN_*` is used. `FN_LASTERROR()` for diagnostics. +- Args are a JSON array (`hb_jsonEncode`); user data never concatenated into JS code. +- Real example + live demo: solmade `app/api/qr.prg` → npm `qrcode` → SVG, served at + `solmade.kr/qrcode`. -**Legacy (do NOT build new on it):** the original `fivenode` (Node + N-API/koffi calling -**C Harbour**) exposed `Require("pkg")`/`aWait()` directly in PRG. That path needs a C -Harbour native lib (`libfivenode`) — against the C→Go goal — so prefer (b). +**Legacy (do NOT build new on it):** the original `fivenode` used a **C** N-API/koffi +addon (`libfivenode`) for the same `Require()`/`aWait()` protocol — against the C→Go +goal. `nodebridge` reproduces that protocol with pure-Go glue instead. **Choose (b) over (a)** only when the npm package has no good Go equivalent or would be heavy to reimplement; the cost is a Node runtime dependency for that feature.