erpl-node-rfc
An SAP RFC client for Node.js and TypeScript that implements the protocol in Rust and ships prebuilt for seven platforms behind node-rfc's API, so there is no SAP NW RFC SDK to download.
SAP archived node-rfc, and npm install node-rfc never worked on its own: it is a C++ addon over the licence-gated SAP NW RFC SDK, roughly 30 MB of C libraries and ICU you register with SAP for and keep on the loader path at run time, built locally with node-gyp and a Python toolchain. This package implements the RFC protocol in Rust, in a crate the maintainer calls erpl-proto, and ships the addon prebuilt for seven platforms: Linux x64 and arm64 on glibc and musl, macOS x64 and arm64, Windows x64. The surface above it is node-rfc's own TypeScript layer, adapted under Apache-2.0, so Client, Pool, Throughput, Server, the error shapes, the promise and callback form of every method and sapnwrfc.ini destinations are that library's, the destinations parsed by this package rather than by the SDK. Compatibility is measured rather than asserted: examples/run_both.sh runs one program under SAP's node-rfc 3.3.1 on the real SDK and under this package and diffs the output field by field.
Facts
Where it sits
Who is behind it
In the atlas: maintains 3@DataZooDEmaintainerJoachim RosskopfContributes to this
In the atlas: contributes to 3@jrosskopf1 contributor
Before choosing this
Conditions
PracticalFunctional boundary
BUSL-1.1 rather than node-rfc's Apache-2.0, so a product you distribute has to be read against the licence and its Additional Use Grant first, and the source is closed: the GitHub repository is the issue tracker and the documentation, nothing else. RfcServerOptions.bgRfcHandlers raises instead of accepting handlers, so a server receiving inbound bgRFC units is outside what this does. An ABAP MESSAGE-raised failure still arrives with its T100 template rather than the rendered sentence, and the dump name is not reported as key for that case.
What it declares
Every artifact
- Type
- Package
- Origin
- Independently maintained
- Runtime
- Node.js
As a package
- Type declarations
- declared
- Where it runs
- In your own program
- Distributed through
- npm
Connections
Calling ABAP function modules from outside the system over RFC
SAP's application server and development platform for ABAP. For the things here it is the system a developer's editor or an agent's tool reaches over ADT, and a program calls over RFC; where it runs is the deployment's, as for SAP BW/4HANA.
Download
Install
npm add erpl-node-rfcDerived from the package name. No recipe recorded; the README has the configuration.
Where it sits in your app
Download
sapnwrfc.ini destination, and calls RFC-enabled function modules on it.README
quoted from DataZooDE/erpl-node-rfc-issues under BUSL-1.1, readerpl-node-rfc — SAP RFC for Node.js and TypeScript, with no SAP NW RFC SDK
This repository is the public issue tracker for erpl-node-rfc. The source is
not public; the bug tracker is. Open an issue →
npm install erpl-node-rfc
const { Client } = require("erpl-node-rfc");
const client = new Client({ ashost: "…", sysnr: "00", client: "001", user: "…", passwd: "…" });
await client.open();
const res = await client.call("STFC_CONNECTION", { REQUTEXT: "hello" });
console.log(res.ECHOTEXT);
await client.close();
Why this exists
SAP archived node-rfc. The repository is read-only, and the package was never installable on its own: you first had to register with SAP, download the licence-gated SAP NW RFC SDK, keep it on the loader path at run time, and have node-gyp and a Python toolchain available to build the C++ addon.
erpl-node-rfc removes all of that. It implements the RFC protocol in pure Rust and ships prebuilt binaries, so the
install is one command and nothing else.
| node-rfc | erpl-node-rfc | |
|---|---|---|
| SAP NW RFC SDK | required (licence-gated download) | not needed |
ICU, LD_LIBRARY_PATH | required | not needed |
| node-gyp / Python toolchain | required | not needed |
npm install alone works | no | yes |
| Platforms | build it yourself | 7 prebuilt: linux x64/arm64 (glibc + musl), macOS x64/arm64, windows x64 |
The API is node-rfc's: the same Client, Pool, Throughput and Server, the same promise/callback duality, the same
error shapes. See MIGRATION.md.
How compatibility is established
Not by argument. examples/run_both.sh runs the same program twice — once under SAP's node-rfc 3.3.1 on the real SDK, once
under this package — and diffs the output field by field. It currently reports no differences across 351 lines, including
every error field of RFC_RAISE_ERROR over METHOD 0-5 × MESSAGETYPE X/A/E/I/W.
That harness is also what found the bugs: the ABAP message type cannot classify an error (a MESSAGE X and a short dump
are both type X), and two T100 identities — DO/410 and 00/341 — mean "no message at all" and must not be reported.
The protocol specification is derived solely from our own packet captures of libsapnwrfc — never by reading another
implementation's source. Other implementations are used only as running black boxes to compare behaviour against.
The same thing for other languages
SAP archived its PyRFC, gorfc and node-rfc bindings alike. ERPL replaces all three over one pure-Rust protocol implementation, so wire behaviour, the ABAP error taxonomy and type handling agree wherever you call from:
| Package | Replaces | |
|---|---|---|
| Node.js / TypeScript | erpl-node-rfc | SAP's node-rfc |
| Python | erpl-pyrfc | SAP's PyRFC |
| Go | github.com/DataZooDE/erpl-gorfc | SAP's gorfc |
More at erpl.io.
Reporting a problem
The most useful bug report says what SAP's node-rfc did with the same input. A behavioural difference is far more actionable than a description of a symptom — and it is how every bug found so far was found.
Please redact credentials, hostnames and business data.
Licence
BUSL-1.1. See LICENSE, and NOTICE for the Apache-2.0 code adapted from SAP's node-rfc (its TypeScript layer and jest suite).
History
Notes
Metadata only. The description now names the siblings -- erpl-pyrfc on PyPI
and github.com/DataZooDE/erpl-gorfc -- because SAP archived all three of its
bindings and someone who found one of ours is likely to want another. The
README gains a table of the three against what each replaces, and a pointer to
erpl.io.
It also cuts both ways as discovery: a search for pyrfc or gorfc now has
something to match here, and vice versa once their own metadata says the same.
Notes
Discoverability only, no code change. Someone looking for a way off SAP's archived node-rfc searches for node-rfc, so that is now the first keyword and the description leads with what the package is rather than what it replaces:
SDK-free SAP RFC client for Node.js and TypeScript. Added the terms people actually type --sdk-free,nwrfc,saprfc,netweaver,bapi,s4hana-- and gave the README a comparison table, since the npm page is where a searcher lands.Worth being honest about the limit: npm ranks heavily on downloads, and no metadata outweighs that for a package published the same day. Keywords decide whether we are findable; time and use decide whether we are near the top.
Notes
The first working release. 2026.9.5 is broken and deprecated: its publish job never ran a build, so the tarball was
LICENSE,README.mdandpackage.json-- nolib/, nonative/-- andmainresolved to nothing. Everything the package ships is build output and none of it is in git.The
bare-containerjob could not have caught it: it runs its ownnpm run buildbefore packing, so it tested a different tarball than the publish job sent. The publish job now builds, and then asserts on the output ofnpm pack-- the bytesnpm publishwould send -- rather than on the tree they came from.Also dropped
--provenance: npm refuses it from a private source repository, and this repository is private on purpose, which is the whole reason npm works here where Go modules could not.Fixes, Dependencies, Other 26
Notes
Broken, deprecated, superseded by 2026.9.6. The first release. Built red/green against SAP's own jest suite and a live a4h system.
The ABAP error taxonomy, settled by measurement
Four bugs, all found by putting every error field of 30
RFC_RAISE_ERRORvariants throughexamples/run_both.shrather than by reading the specs:- The message type cannot classify an error. A
MESSAGE Xand a short dump are both typeX; the reference calls the firstRFC_ABAP_MESSAGEand the secondRFC_ABAP_RUNTIME_FAILURE. What separates them is whether the server sent anAbapExceptionKey, whichError::AbapExceptionnow carries asdump, and whether a real T100 came with it. - Two T100 identities mean "no message" --
DO/410with the variable&STRING&, and00/341, the runtime's generic dump wrapper. Both are typeX, so passing them through made an error with no message look like a dump: a connection that was still good got thrown away on every plainRAISE. Filtered indecode_call_result, so every binding gets the fix. - A declared exception's message is the identity rendered, not the text:
"ID:SR Type:E Number:006 Method = 1", or" Number:000"when there is no message at all. - A protocol error's
codenever reached JavaScript.classifyreturned no numeric code, so everyRfcLibErrorcarried a 0 -- which the unpacking read as "no code" and dropped, leaving napi's ownGenericFailurestring where the reference has2.
Settled before the release
DataZooDE/erpl-node-rfc-issuesis the public face: issues and docs only, likeerpl-pyrfc-issues, because npm carries the distribution and the Rust stays private.repositoryandbugspoint at it, on the main package and on all seven platform packages.bindings/nodeis gone. The older napi-rs crate -- unpublished, no CI, no workspace membership -- is superseded entirely. Worth recording that the reason Phase 0 gave for removing it was wrong: its npm nameerpl-protodoes not collide with anything here, since every package iserpl-node-rfc*. It went because nothing consumed it, not because of the collision.
The packaging was not what the plan specified
Found by running
npm publish --dry-run, which publishes nothing and should have been run much earlier. The seven targets were declared innapi.targets, but there were no per-platform packages and nooptionalDependencies: the tarball simply carried whatever.nodehappened to be innative/-- the local Linux build -- so it was 780 kB, platform-specific, and would have failed on macOS and Windows.npm/<triple>/generated for all seven, andoptionalDependencieswired to them, so npm selects the right binary per platform.- The main package no longer ships a binary at all: 780 kB to 25.8 kB, and
platform-independent. The loader already preferred a local
.nodeand fell back to the platform package, so this is the shape it was written for. - The bare-container smoke test now installs the main tarball and this platform's, as a real consumer does. Installing the main one alone would have proved only that the loader can fail to find a binary.
- A
publishjob, gated on anode-rfc-v*tag and on every other job passing, publishing the platform packages before the package that depends on them. It refuses to publish if any declared target's binary is missing, so a lost artifact fails there rather than for whoever installs it.
ping()reportsfalseas well as the errorOn a closed connection the reference calls back with the error and
false, and its spec asserts both. A promise cannot reject and resolve at once, so the value belongs in the callback wrapper rather than in the addon;ON_FAILUREnames it for the one method that has one.Two that this system cannot decide
Both are environment-dependent rather than defects, and neither is a gap in the binding:
client/direct.callback"Getters" assertsclient.alive === falsethree tests afterbeforeAllfiredopen()without waiting for it -- so it holds only while the open is still in flight. Measured here: a fresh client reportsalivefalse, still false immediately afteropen()is issued, and true after 14 ms, which is less than jest's own overhead between tests. The getter is right at every point; the reference's system is simply further away.errors/abap"AbapRuntimeError X" asserts the00/341dump identity its system produces where a4h raisesSR/006. The reference's own library, run against a4h, reports what we report.
Known gap: two metadata round trips per first call, and the lead on it
The reference resolves
STFC_CONNECTION's structure without a separate call -- its firstcall()in a fresh process counts 2, ours 4 -- while both count one per call thereafter. No earlier call warms its cache, so the difference is real: we fetch the interface withRFC_GET_FUNCTION_INTERFACEand then each referenced structure with its ownDDIF_FIELDINFO_GET.RFC_METADATA_GETexists on a4h, and one call returning the interface and the type definitions together is exactly a count of 2. That is the likely explanation and the place to start; it would cut two or more round trips off every first call, which is a performance argument rather than a test fix. Worth confirming against the reference with theLD_PRELOADcapture shim before building anything, since the metadata module a client uses is observable. Not attempted here: it changes how metadata is fetched and cached, which is its own piece of work.cancel(), and the per-call timeoutNeither existed on the addon:
cancelwas in the callback-wrapping list but had no method behind it, so the reference'sClient.cancel()threwthis.__client.cancel is not a functionand both cancel specs failed on it.A cancel is a bare 80-byte APPC record written to the conversation's own socket while another thread is blocked reading it (spec CAN-1), so it needs no second connection -- only the handle. That handle deliberately does not live in the client's
State: a call holds that lock for its whole duration, so acancel()that had to take it would block until the call it is meant to interrupt had already finished. It goes in a slot beside it, filled for the length of the call and cleared on every exit, so a cancel arriving after the reply has nothing to cancel rather than writing a stray record into the next conversation.The conversation is over either way (CAN-2), so the connection is reopened from the thread holding the lock -- the only one that can put a live connection back -- and the caller finds the client alive on a different handle, which is what the reference asserts.
Error::Canceled's text is now the reference's own,"Connection was canceled.".The suite's
sapnwrfc.iniis now generated from the environment into a temporary directory rather than committed. A destination is only useful with credentials in it, and a committed file would either carry a password or be unusable for the specs that connect bydest-- which is exactly whycancel/timeout.call, which does, could not open a connection at all.A metadata lookup is a call, and is counted
The reference's throughput spec asserts 2 calls after the first
STFC_CONNECTION, 3 after the second and 87 for oneBAPI_USER_GET_DETAIL-- so it charges the caller for the interface and structure lookups a call needs.do_invokehad been detaching the counter around them, to bill only what the application asked for; that was an assumption, and it disagreed with the reference and withthroughput.rs's own documented intent. Now counted.The counts still differ -- 4 where the reference reports 2 -- because we ask for each referenced structure separately where the SDK resolves
STFC_CONNECTION's from the interface reply. That is two round trips we do not need on a first call, and it is an efficiency gap rather than a counting bug: the shape agrees, and every later call adds exactly one on both sides.client.release()The client binding had no
release, so the reference'sClient.release()-- which callsrelease([this], cb), the client passing itself in a list -- threwthis.__client.release is not a function. Clients now reach the pool that lent them through a registry keyed by_pool_id, and a second release is refused by name: a released client keeps its connection, so nothing about the connection says it is spent, and releasing twice would have decrementedleasedfor a client that was already back.Errors are plain objects, as the reference's are
Every error node-rfc's addon produces is a
Napi::Object, never anError(src/cpp/nwrfcsdk.cc), so a node-rfc caller has never hadinstanceof Erroror a stack on an RFC failure. Its pool spec pins the difference -- it assertsrejects.toEqual({...})against an object literal, and jest'stoEqualnever matches an Error against one. The unpacking now returns the object rather than decorating an Error, and applies every field it is given instead of filtering by value: filtering could not tell acodethe addon omitted from one that is legitimately 0.Green
Run per file against a live a4h system:
Adapted spec Result addon.methods3/3 client/direct.promise4/4 client/direct.callback8/10 concurrency/{await,promise,callback}6/6 locking/callback5/5 pool/options3/3 pool/ready2/2 pool/acquire-release5/6 pool/errors3/3 pool/acquire-release6/6 throughput/client7/9 errors/connect4/4 errors/invoke8/8 errors/promise2/4 errors/abap9/11 datatypes/all17/26 datatypes/utclong0/3 call_options/{promise,callback}0/4 server/server(ours)2/4, 2 skipped 94 of 117 adapted tests pass against a live a4h system, and
examples/run_both.shreports no differences across 351 lines -- including every error field ofRFC_RAISE_ERRORover METHOD 0-5 x MESSAGETYPE X/A/E/I/W and ofSTFC_SAPGUI.15 of the 28 that do not pass cannot pass here: they call
/COE/RBP_FE_DATATYPES,ZDATATYPES,ZLONG_STRING,ZFLTPorEAM_TASKLIST_GET_DETAIL-- ABAP that ships with SAP's own test system and is absent from a4h, confirmed by askingRFC_GET_FUNCTION_INTERFACEfor each. Two more assert content that is that system's rather than a rule: itsAbapRuntimeError Xexpects the00/341dump identity, where the same call on a4h raisesSR/006-- and the reference's own library, run against a4h, reports what we report.The concurrency and locking specs are green, which is the point of them: they are what a binding that blocks the event loop cannot pass, and they were the reason for moving the work onto libuv.
Plus 9 Rust unit tests, and the TypeScript compiles under the reference's own compiler settings.
Two defects the suite found that nothing else would have
instanceof Erroris false under a test runner. jest evaluates a suite in its own vm context, so anErrorthe addon created belongs to a different realm andinstanceoffails there. The error-unpacking innoderfc-bindings.tswas guarded by it, so every error field --name,code,codeString,group,key-- was applied correctly undernodeand silently skipped under jest. It is duck-typed now. This is the worst shape of bug available: right everywhere except where it is tested.Patching a native prototype twice double-wraps it. The addon is cached by Node, so its prototype is one object for the process, but jest gives each test file a fresh module registry -- so
noderfc-bindings.tsruns again and wrapped its own wrapper. The second wrapper removed the callback from the arguments and handed the rest to the first, which no longer found one and threw synchronously. The patch carries a symbol marker now and is idempotent.The work now runs off the event loop
The addon's methods return promises backed by
napi::Task:computeruns on libuv's pool with no napi values in sight,resolveturns the result back into JavaScript on the event-loop thread. A call's inputs are read into a plain RustPayloadbefore the task is created, because a JavaScript object cannot leave the thread that made it.The reference's client calls
binding.open(cb), sonoderfc-bindings.ts-- the one file rewritten rather than adapted -- wraps each promise back into a callback. Two details cost time and are worth keeping:- The callback is not always the last argument. The client calls
invoke(name, params, callback, callOptions), so the adapter finds the function rather than popping the end. - napi's
Errorcarries one string, and it becomes the message, so there is nowhere to put a JSname. The addon packsname\u0001messageinto it and the adapter splits them, because the reference's specs match on name and message together.
Known: the callback spec is order-dependent
test/client/direct.callback.spec.tsis 7/10 alone and 0/10 when it runs after the other files. ItsbeforeAllisbeforeAll((done) => { client = direct_client(); void client.open((err) => { return done(err); }); // unconditional done(); });doneis called twice by construction: once from the callback whether or not there was an error, once after. That is a race in the reference's own hook, and it only became visible once the callback became genuinely asynchronous -- which is the correct behaviour. It is not a binding defect, but it does mean the file cannot be trusted in a combined run until the hook is adapted, and adapting a test to fit the implementation needs care: the point of reusing this suite is that it is not ours to bend.Pool and Throughput
Poolkeeps the two counts the reference exposes --ready, connections opened and waiting, andleased, the ones handed out -- rather than wrappingerpl_proto::Pool, whosemax_size/min_idleshape does not answer those questions the same way. Clients it hands out carry a non-zero_pool_id, which is how the reference'sClientconstructor tells a pooled binding from a bag of connection parameters.Throughputmaps ontoerpl_proto::throughput::Throughputand reports the reference's seven counters in its names and units -- milliseconds, though the measurements underneath are finer, because a caller comparing the two libraries could not line up nanoseconds. The reference addresses connections by handle rather than by object, so the addon keeps a weakly-held registry of live client states to resolve one.The pool's methods are synchronous behind a callback shim that defers the callback; the client's work already runs on libuv. Moving the pool's onto it too is the remaining half of that job.
What is built
- The napi-rs addon:
Clientwithopen,close,ping,invoke,connectionInfo, and the_id/_alive/_connectionHandle/_pool_id/_configproperties the TypeScript layer reads. - The module surface:
languageIsoToSap/languageSapToIsoover the reference's own 76-entry table,setIniFileDirectory,reloadIniFile. sapnwrfc.iniparsing, ported from the Go binding — needed because most of the reference's specs connect by destination.- The error shapes: a JS
name(nodeRfcError,ABAPError,RfcLibError) alongside the message, which the reference's specs match on together. - Structures and tables in both directions, driven by the interface rather than
by the shape of the value —
describeanddescribe_structureresolve the layouts,invoke_with_table_inputscarries the rows, and replies decode withdecode_refand neverdecode_auto(spec TBL-1). - All seven of the reference's TypeScript files, adapted.
- bluebird is a dependency after all. It was dropped on the reasoning that
the reference's own client has the import commented out — true, but its
exported
Promiseis bluebird's anddirect.callback.spec.tsasserts the version against the dependency. The test is the specification; the reasoning was about internal use and the contract is the exported surface.
Server
Serverregisters a program ID, dispatches inbound calls to JavaScript handlers and answers ABAP with what they return. It is the one place a napiThreadsafeFunctionis unavoidable: the call arrives on a serving thread with no access to the JavaScript heap, and the handler's answer has to come back to that thread before the reply can be sent, so the serving thread blocks on a channel while JavaScript runs. Inbound values are decoded by their declared type through a client connection kept alongside the server, because a request arrives one byte per character (spec SRV-7) and cannot be read as text without knowing what each parameter is.bgRfcHandlersraises, naming spec BGR-5. That is the single surface of node-rfc this package does not implement.The two registering tests are skipped by default, behind
SAP_NODE_SERVER_TESTS=1. Registering hung once, and a registration left behind at the gateway poisons the next run with a failure naming a function that run never mentions (CLAUDE.md, spec SRV-9). They aretest.skip, not an early return, so they read as skipped rather than as green -- a test that returns before its first assertion passes having checked nothing, which is the same trap as counting a skipped live test as a pass. Why start hangs is the next thing to find.The differential: no differences
examples/run_both.shruns one program under SAP's node-rfc 3.3.1 on the real NW RFC SDK and under this package, and diffs the output. No differences across 35 lines -- connection attributes, the echo and structure round trips, a table with its rows, an ABAP error's name and key, the alive states, and the language table.It found one defect on its first run: an unknown function module came back with
key: undefinedwhere the reference givesFU_NOT_FOUND, because the error classification carried the key only as a prefix on the message and dropped the field. That is what this harness is for.The reference is hard-required through
SAPNWRFC_HOME, so the script can never quietly compare this package against itself and always report agreement.Packaging
npm packproduces a 758 KB tarball that installs and runs in a stocknode:20-bookwormcontainer with no SDK, no ICU and no toolchain -- verified, not asserted..github/workflows/bindings-node-rfc.ymlbuilds the addon for seven targets, runs the suite on three operating systems, and repeats that bare-container install as a job, because it is the whole pitch and a claim about a machine we do not control.Values are encoded against the declared type, not guessed from the value
This is the encoding half of "decode by metadata, never by the value", and it was missing: a string is a perfectly good CHAR and a perfectly bad INT1, and only the interface can say which. Each value is now checked against the type its parameter or field declares, and refused with the reference's own message -- "Integer number expected from NodeJS for ABAP field of type 10", where 10 is SAP's numeric code for INT1 and part of the asserted text.
The error carries the reference's
rfmPathtoo: the function module, the parameter, the structure and the field, so a rejected value says where it sat.datatypes/allwent from 3/26 to 9/26 on that alone.Wrong type, out of range, and unconvertible are three different things
The reference distinguishes them and its specs check the distinction, so this does too:
- Wrong type -- a string where an integer belongs, or
Infinity, which is not an integer at all:nodeRfcError, "Integer number expected from NodeJS for ABAP field of type 10, got Infinity", no code. - Out of range -- a finite 256 into an INT1: "Overflow or other error when putting NodeJS value 256 into ABAP integer field of type 10".
- Unconvertible -- "A" for a FLOAT:
RfcLibErrorwithRFC_CONVERSION_FAILUREand code 22, naming the character position.
The first two are one word apart and easy to swap by accident; the suite tells them apart, which is why it is worth running unmodified.
Integer width, and the shape of a type error
An ABAP INT1 holds 0..255 and an INT2 -32768..32767. A value outside that is refused here rather than narrowed on the way to the server, where it would arrive as something the caller never wrote: "Overflow or other error when putting NodeJS value 256 into ABAP integer field of type 10". Infinity and NaN are reported the same way, with the value named.
A non-numeric string for a FLOAT or a decimal is refused with the position it gave up at, as the reference does: "Cannot convert string value A at position 0 for the field RFCFLOAT to type RFCTYPE_FLOAT".
The
rfmPathcarries only the parts that apply --tablefor a table parameter,structurefor a structure, never both -- plustable_line, which is-1when the parameter itself was not an array. The specs compare the whole object, so an empty field left in fails a comparison that should pass.A name the interface does not declare
A parameter, a structure field and a table-row field are all reported the same way, with the reference's own text:
field 'XXX' not found, keyRFC_INVALID_PARAMETER, code 20. It says "field" even for a parameter, which is worth not tidying up -- a caller matching on the message is matching on that.errors/invokewent to 8/8.Closing an already-closed client is refused rather than quietly accepted. Two closes in a row usually mean a caller has lost track of a connection's lifetime, which is worth being told about, and the reference tells them.
A Throughput counts the caller's calls, not the library's
Throughput.getFromConnectionis a static on the class, not an instance method, and answers with a handle the reference looks up in its own registry.Describing a function module costs real round trips, and they belong to the binding rather than to the application, so the counter is detached while the metadata is fetched and restored afterwards. Without that, two calls measured as four.
What the remaining red actually is
Of the 46 still failing, a large share cannot pass on this system at all:
call_options(4) needsEAM_TASKLIST_GET_DETAIL, a Plant Maintenance module.datatypes/utclong(3) and 6 of the 24 indatatypes/allneedZDATATYPES,ZLONG_STRINGorZFLTP-- ABAP that ships with SAP's own test system.
That is 13 tests gated on ABAP rather than on this binding. Deploying a
ZDATATYPESmatching SAP's interface would unlock them; this repository'sZ_ERPL_DATATYPEScovers the same ground under different parameter names, so it is a translation job rather than new ABAP.The rest is real work: the remaining
datatypes/allcases that do useSTFC_STRUCTURE, fourerrors/*, one pool case, three throughput, and two inclient/direct.callbackthat turn on the reference's suite assumingopen()is slower than two trivial tests -- true against a remote system, false against localhost.Two fixtures the reference's suite needs and a4h does not have
call_options(0/4) needsEAM_TASKLIST_GET_DETAIL, a Plant Maintenance module a4h does not have.datatypes/utclong(0/3) and six of thedatatypes/allcases callZDATATYPES,ZLONG_STRINGandZFLTP-- ABAP that ships with SAP's own test system. They fail withFU_NOT_FOUNDrather than for any reason to do with this binding. The rest ofdatatypes/allusesSTFC_STRUCTURE, which a4h does have, so most of that file is real coverage and worth pursuing. This repository hasZ_ERPL_DATATYPEScovering the same ground under different parameter names (bindings/pyrfc/abap/), so the choice is to deploy aZDATATYPESmatching SAP's interface, or to cover those cases separately and mark these as unrunnable here. Neither is done yet.The ABAP error taxonomy, and a fix in erpl-proto
errors/abapwent from 0/11 to 7/11, and the fix was in the protocol library rather than the binding.Everything the reference reports was already on the wire and already parsed --
AbapRuntimeIdcarriesCOMPUTE_INT_ZERODIVIDE,AbapErrorMessagecarries "Division by 0 (type I or INT8)".decode_call_resultwas picking whichever of three tags appeared first in field order, and a reply carries the T100 identity before the rendered text, so every message-raised failure came back as its own template:"&STRING&: &STRING&". It now prefers by tag, not by position, and falls back toAbapRuntimeIdfor the key when no exception named itself. 513 Rust tests still pass.On top of that the binding maps the reference's taxonomy: a short dump is
RFC_ABAP_RUNTIME_FAILURE(3) keyed by the dump name, aMESSAGEwith nothing declared isRFC_ABAP_MESSAGE(4) keyed by the rendered text, and a declared exception isRFC_ABAP_EXCEPTION(5) keeping its own name. TheabapMsgClass,abapMsgType,abapMsgNumberandabapMsgV1..V4fields travel with it.Reconnect is decided by the failure, not by the socket. A MESSAGE-raised error deallocates the conversation and nothing on the wire says so -- only the next call finds out (spec PRO-12) -- so
conversation_lost()is false at that moment and cannot be the test. The error's own ABAP message type is.The differential caught the one regression this introduced within a minute: using the rendered text as the key whenever the type was
EturnedFU_NOT_FOUNDinto the function's name. A declared exception keeps its key.What is next, in order
- The ten reds in
test/client/direct.callback.spec.ts(above), thentest/errors/**. test/datatypes/**— decimals asDecimal, dates, UTCLONG.test/call_options/**, thentest/cancel/**.test/concurrency/**andtest/locking/**, which are the reason the binding must stop blocking the event loop. Every native method runs its work inline today and hands the result to the callback through one function,client::complete, so moving it to a worker thread behind a threadsafe function is a change in one place rather than a rewrite. This is a known step, not an oversight.test/pool/**,test/throughput/**, then Server (specs ours — the reference has none), withbgRfcHandlersraising per spec BGR-5.- The differential against node-rfc 3.3.1 on the real SDK, CI, and the per-platform npm packages.
- The message type cannot classify an error. A
Others providing
Compare all 7 on the RFC connectivity page →- rfc-connectorrfc-connectorCalls abap2UI5 apps of another system over RFC: an ICF handler on the system the browser talks to forwards each roundtrip through an SM59 destination to the system the apps run on.community org, abapForwards abap2UI5 roundtrips to another system over RFC
- abap_fm_jsonabap_fm_jsonA JSON adapter that exposes ABAP function modules over HTTP: a handler class in an ICF service calls the module named in the URL and serialises its parameters to and from JSON.independent, abapJSON adapter exposing ABAP function modules over HTTP
- ERPLERPLA DuckDB extension that reads SAP ERP tables, CDS views and BW queries from SQL over RFC, BICS and ODP, and can mount an SAP system as a DuckDB catalog to join against other data.independent, abapDuckDB extension reading SAP ERP tables, CDS views and BW queries
- ADELE RFC APIADELE RFC APIAn RFC-callable ABAP API for extracting SAP data, implemented for the Java-based ADELE tool, with an OLAP mode for group by and count.independent, abapRFC-callable ABAP API for SAP data extraction for ADELE
- Cloud ConnectorCloud ConnectorSAP's on-premises agent that links SAP BTP applications to systems in a customer's own network, as a reverse invoke proxy that exposes only the systems and resources it is configured to allow.sap official, btpSAP's on-premises agent linking SAP BTP to customer-network systems
- srijan9864/zrfcreadsrijan9864/zrfcreadAn RFC-enabled ABAP function module that counts the entries of any table against dynamic where and join criteria.independent, abapAn RFC-enabled ABAP function module that counts the entries of any table against dynamic where and join criteria.
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
Contributors
History in the atlas
- First seen.
For your agent
- Ref
artifact:datazoode-erpl-node-rfc- JSON
- /artifacts/datazoode-erpl-node-rfc.json
- MCP
- Connecting an agent