docs(rag): ecosystem §7 — pure-Go Node bridge (nodertl), no C
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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 <pkg>`; 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.
|
||||
|
||||
Reference in New Issue
Block a user