From 4ce927a8de2a7c624acb108c2b95a1fedbb83017 Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Mon, 15 Jun 2026 17:30:05 +0900 Subject: [PATCH] =?UTF-8?q?docs(rag):=20ecosystem=20=C2=A77=20=E2=80=94=20?= =?UTF-8?q?pure-Go=20Node=20bridge=20(nodertl),=20no=20C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite §7 around the C→Go goal: reach external code without C/CGO. Path (a) Go-package RTL (first choice); path (b) npm via pure-Go nodertl (NODE_EVAL → node subprocess), with the real article_qr.prg example. Mark the C N-API Require()/koffi path as legacy (needs C Harbour — avoid). Co-Authored-By: Claude Opus 4.8 (1M context) --- rag/04-idioms.md | 56 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/rag/04-idioms.md b/rag/04-idioms.md index 0886fb7..452035e 100644 --- a/rag/04-idioms.md +++ b/rag/04-idioms.md @@ -207,36 +207,40 @@ Deploy (launchd): `launchctl kickstart -k gui/$(id -u)/kr.solmade.web` (and `...worker1/2/3`). The worker build (`build_worker.sh`) links `cmd_prg/job_worker.prg` plus the shared `app/lib/*.prg` (so `LLM_CHAT` and prompts are available to it too). -## 7. Reaching the ecosystem — two paths (pick by runtime) +## 7. Reaching the ecosystem (no C — Five is the Go reimplementation) -There are two distinct ways to use the outside world from PRG; they belong to -different runtimes (see [[five-overview]]): +Five replaced Harbour's C with Go, so reach external code **without C/CGO/Harbour +native**. Two production-clean paths (see [[five-overview]]): -**(a) Go RTL — `fivenode_go` (single Go binary, NO Node).** Wrap a Go package in a -thin `hbrtl_ext` module, blank-import it with `--rtl`, call the registered function. -This is the production path (solmade): no runtime deps, one binary. Use for anything -with a good Go library (Postgres, PDF, XLSX, crypto/rand, argon2, bluemonday). +**(a) Go package via RTL — first choice.** Wrap a Go library in a thin `hbrtl_ext` +module, blank-import with `--rtl`, call the registered function. Single static binary, +no runtime deps. Use whenever a good Go library exists (Postgres, PDF, XLSX, +crypto/rand, argon2, bluemonday). -**(b) Node `Require()` — original `fivenode` (Node.js + N-API/koffi).** PRG calls an -**npm package directly**, Node-style, with no wrapper to write: +**(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`: ```five -oQR := Require( "qrcode" ) // npm package -cSvg := aWait( oQR:toString( cUrl, { "type" => "svg" } ) ) // async → aWait() -oQR:__end__() // release the handle - -oId := Require( "crypto" ):randomUUID() // Node builtin, sync -oOs := Require( "os" ) // builtins: os, path, fs, ... -cHost := oOs:hostname() ; oOs:__end__() +// 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", "" ) +ENDIF ``` -- Sync npm: `obj:methodSync(...)` (e.g. qr-image `imageSync`). Async (Promise): - wrap the call in `aWait(...)`. Always `obj:__end__()` when done. -- Pass PRG hashes as JS objects: `{ "width" => 240, "color" => { "dark" => "#000" } }`. -- The server allowlists each handler — register new ones in `napi/test/server.js` - (`allowedPrg.add('/api/foo.prg')`). +- 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. -**When to choose (b):** the npm package has no good Go equivalent, or reimplementing it -would be heavy (QR codes, charting, specialized parsers, ML/JS-only SDKs). You get the -entire npm registry with zero glue code — the cost is a Node runtime instead of a single -static binary. Example: `napi/test/api/sharekit.prg` composes npm `qrcode` + Node -`crypto`/`os` in one PRG to build an article share kit (QR svg+png + tracking UUID). +**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). + +**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.