@saptools/cf-hana
A command line and TypeScript API that runs SQL against the SAP HANA CloudSAP HANA CloudThe managed database under most of this: CAP's default target, the store Datasphere and Analytics Cloud sit on, and a service with plans and components.sap official, btp database bound to a Cloud FoundrySAP BTP, Cloud Foundry RuntimeThe Cloud Foundry runtime of SAP BTP: memory for applications in a Cloud Foundry org, run from SAP's Java, Node.js and Python buildpacks or community buildpacks and managed with the standard Cloud Foundry tools.sap official, btp app, addressed by a region/org/space/app selector.
Looking at the data a deployed app holds needs the credentials of its HANA binding, which sit in the app's VCAP_SERVICES. cf-hana reads that binding live on every connection and writes no credentials under ~/.saptools/; it does not read the snapshots of @saptools/cf-sync@saptools/cf-syncA command line and Node.js API that logs into Cloud Foundry once, walks every region, org, space and app the user can reach, and caches that topology as JSON for the other @saptools packages to read.independent, btp. Its query, tables, columns and count commands run over a pooled connection through the bundled hdb driver, with an opt-in read-only mode, a guard that blocks DROP, TRUNCATE, ALTER and an UPDATE or DELETE without a WHERE, and, for a CLI UPDATE, UPSERT, REPLACE, matched MERGE or DELETE, a copy of the affected rows saved before the write runs. Where the database accepts no connection from the developer's machine, it retries through a cf ssh port-forward opened on an app in the same org and space. The README calls the guard a convenience and not a security control.
Facts
Where it sits
Who is behind it
In the atlas: maintains 18, contributes to 18@dongitranmaintainer
What it declares
Every artifact
- Type
- CLI
- Origin
- Independently maintained
- Runs on
- Cloud Foundry
As a CLI
- Distributed through
- npm
Connections
Seeing what running code did between input and result
A cross-platform JavaScript runtime that servers, command line tools and scripts written in JavaScript are run with. For the things here, the prerequisite a repository names when its build is a set of npm scripts.
Cloud Foundry runtime of SAP BTP for polyglot applications
Managed SAP HANA database service on BTP
Download
Install
npm add @saptools/cf-hanaDerived from the package name. No recipe recorded; the README has the configuration.
Architecture
Download
README
quoted from dongitran/saptools under MIT, read@saptools/cf-hana
Run SQL directly against SAP HANA Cloud databases bound to a Cloud Foundry app — addressed by a
region/org/space/appselector.
@saptools/cf-hana opens live SAP HANA Cloud connections from Cloud Foundry app
bindings. Pass a selector, get a connected, pooled client, and run SELECT /
INSERT / UPDATE / DELETE / DDL. Bare app names use your active cf target
and current CF session first, so a healthy local CF login does not require SAP
password re-authentication.
Features
- Selector-based connect —
region/org/space/appor a bare app name. - Visible target provenance — every connecting CLI command confirms the resolved selector on stderr and labels ambient versus explicit targeting.
- Credentials, handled for you — HANA bindings are read live from Cloud Foundry. Bare app names use the current CF session first; explicit selectors use isolated live authentication.
- Parameterized queries — values always travel as bound
?parameters, never string-concatenated. - Connection pooling — pooled, reused connections; opt out with
pool: false. - Transactions —
transaction(work)commits on success, rolls back on throw. - Query-builder shorthands —
selectFrom,count,insertInto,update,deleteFrom— query a table by name without writing SQL. - Schema introspection — list schemas, tables, and columns.
- Table-name recovery — failed CLI queries for missing tables/views show nearby schema-local suggestions on stderr.
- Privilege recovery — HANA error 258 identifies the active schema, technical user, binding, and any sibling bindings worth trying explicitly.
- Local SQL history — direct SQL calls are appended to dated JSONL files
under
~/.saptools/cf-hana/histories/with five-day retention. - Write backups — CLI
UPDATE,UPSERT,REPLACE, matchedMERGE, andDELETEstatements preserve pre-image rows before the write runs. - Compact CLI results — CLI
SELECT/WITHoutput is compact CSV with bounded cells; truncated exact values are retained automatically for follow-up inspection. - Lossless CLI formats —
query --format table|json|json-compact|csvprovides exact cell values without changing the compact-CSV default. - Safety guard — opt-in read-only mode and a destructive-statement guard
(blocks destructive DDL, unscoped
UPDATE/DELETE, and unconditional matched deletes). - Typed results —
query<TRow>()returns typed rows. - CLI + API — a
cf-hanaCLI and an ergonomic TypeScript API.
Installation
npm install @saptools/cf-hana
# or, for the CLI
npm install -g @saptools/cf-hana
Requires Node.js >= 20. The pure-JavaScript hdb
driver is bundled as a dependency — there is no native build step.
Updates
Every command first checks npm for a newer @saptools/cf-hana (at most once an hour, one small request
with a 2-second timeout) and, when one exists, installs that exact version with the package manager
that owns the running binary and re-runs the command you typed on the new version. Both steps are
announced on stderr; nothing is printed when the install is already current:
cf-hana: updating 0.6.0 -> 0.7.0 ...
cf-hana: updated to 0.7.0; re-running the command
If the install cannot complete, one stderr line gives the manual command and the command runs on the
installed version; that version is not retried for a day. cf-hana self-update forces the check and
install now; cf-hana self-update --check only reports.
| Control | Effect |
|---|---|
SAPTOOLS_AUTO_UPDATE=on|notify|off | on (default) installs and re-runs; notify prints the manual command once per version; off never checks. Applies to every @saptools CLI. |
CF_HANA_AUTO_UPDATE | same values, this CLI only; wins over the global variable |
SAPTOOLS_UPDATE_INTERVAL_MINUTES | minutes between checks (default 60; 0 checks on every run) |
SAPTOOLS_NPM_REGISTRY | registry to check and install from (default: npm's configured registry, then npmjs) |
SAPTOOLS_UPDATE_DEBUG=1 | explain on stderr why nothing happened |
The updater switches itself off in CI (CI set), under NODE_ENV=test or NO_UPDATE_NOTIFIER, when
the binary runs from a source checkout, an npm link or an npx cache, and inside the re-run itself.
It never writes to stdout, never asks for input, never uses sudo, and never moves onto a prerelease.
Its state lives in ~/.saptools/updates/.
Quick start
import { connect, query } from "@saptools/cf-hana";
// Open a reusable, pooled client for one CF app's HANA database.
const db = await connect("eu10/example-org/space-demo/app-demo");
const open = await db.query("SELECT ID, STATUS FROM ORDERS WHERE STATUS = ?", ["OPEN"]);
console.log(open.rows);
const total = await db.count({ schema: "ORDERS_APP", table: "ORDERS", where: { STATUS: "OPEN" } });
await db.transaction(async (tx) => {
await tx.execute("UPDATE ORDERS SET STATUS = ? WHERE ID = ?", ["SHIPPED", 42]);
});
await db.close();
// One-shot: connect, run one query, close.
const rows = await query("app-demo", "SELECT COUNT(*) AS N FROM ORDERS");
The selector
Every entry point takes a selector as its first argument:
- Explicit —
region/org/space/app(e.g.eu10/example-org/space-demo/app-demo). Works without any cached topology. - Bare app name —
app-demo. Resolved only against the activecf target(current org/space/API endpoint) and fetched withcf env app-demousing your existing CF session before any isolated re-auth fallback.
Every connecting CLI command prints the resolved selector to stderr. A bare
selector is labeled as inherited from ambient cf target; an explicit selector
is labeled as pinned. For the direct bare-app path, cf-hana verifies the app,
API, org, and space embedded in the same VCAP_APPLICATION payload as the
bindings, then reads cf target again. Missing or mismatched identity, including
an A-to-B-to-A target race, is refused before a database connection is opened.
If the ambient API cannot be mapped to a supported region, the notice says the
region is unconfirmed instead of presenting current/... as a usable pin.
CLI
cf-hana query <selector> <sql> Run a single SQL statement
cf-hana tables <selector> [schema] List tables in a schema
cf-hana columns <selector> <schema.table> List the columns of a table
cf-hana count <selector> <schema.table> Count rows in a table
cf-hana ping <selector> Connect and measure round-trip latency
cf-hana info <selector> Print the resolved connection metadata
cf-hana result <command> Inspect saved query refs
Common options: --refresh (deprecated compatibility flag; binding discovery is already live), --role <runtime|hdi>, --binding <name> /
--binding-index <n>, --timeout <ms>, --read-only, --allow-destructive,
--limit <n>, --no-auto-limit, --tunnel, --refresh-tunnel (see
Connectivity fallback). The query command also accepts
--param <value> (repeatable), --cell-limit <n>, --save, --no-auto-save,
--format <table|json|json-compact|csv>, --result-ttl-minutes <n>, and
--refresh-metadata. tables and columns support the same four format values.
CLI UPDATE, UPSERT, REPLACE, matched MERGE, and DELETE statements are
backed up automatically before the write runs.
cf-hana query eu10/example-org/space-demo/app-demo "SELECT ID, STATUS FROM ORDERS WHERE STATUS = ?" \
--param OPEN --read-only --save
cf-hana query app-demo "UPDATE ORDERS SET STATUS = ? WHERE ID = ?" \
--param DONE --param 42
cf-hana tables app-demo
cf-hana columns app-demo ORDERS_APP.ORDERS
cf-hana ping eu10/example-org/space-demo/app-demo
Output formats and schemas
With no query --format, successful SELECT/WITH output remains compact CSV
for backward compatibility. An explicit format is lossless at the cell level:
query --format jsonreturns[{COLUMN: value, ...}].query --format json-compactreturns[value, ...]for a single-column projection and falls back to row objects for multiple columns.query --format csvreturns lossless RFC 4180 CSV.query --format tablereturns a lossless aligned table.tables --format jsonreturns[{SCHEMA,TABLE,TYPE}]; itsjson-compactmode returns[TABLE, ...].columns --format jsonreturns[{COLUMN,TYPE,LENGTH,NULLABLE,POSITION}]; itsjson-compactmode returns[COLUMN, ...].
The existing uppercase catalog keys are unchanged. Query formats are available
only for SELECT/WITH. --save cannot be combined with --format, which
keeps JSON and CSV stdout valid machine output. The automatic row cap still
applies unless changed with --limit or --no-auto-limit.
Compact query output and saved refs
For CLI SELECT and WITH statements, stdout is CSV. Bare reads return at most
100 rows by default; pass --limit <n> to request more, or --no-auto-limit to
disable the automatic cap. Data cells display at most 128 Unicode characters by
default; pass --cell-limit <n> to choose a value from 1 through 10,000.
When compact output actually shortens one or more cells, cf-hana automatically
saves the exact returned rows and prints a concrete result show command with
the generated ref to stderr. This does not add a ref= line to stdout. Disable
implicit retention with --no-auto-save. If an implicit save exceeds the
256 MiB store ceiling or local storage is unavailable, the query still succeeds
and the hint recommends --save or a larger --cell-limit.
Use explicit --save when you want a ref regardless of whether cells are shortened:
cf-hana query app-demo "SELECT ID, CONTENT FROM ORDERS" --read-only --save
Saved output starts with a control line, then CSV:
ref=q7f3a9c2b
ID,CONTENT
1,first 128 visible characters
The ref is not a CSV column. Exact returned rows are stored under
~/.saptools/cf-hana/results/ for 7 days by default. Only returned rows are
stored; rows beyond the selected --limit are not fetched or saved.
Follow-up commands:
cf-hana result show q7f3a9c2b
cf-hana result show q7f3a9c2b --row 1
cf-hana result show q7f3a9c2b --row 1 --column CONTENT --length 1000
cf-hana result show q7f3a9c2b --row 1 --column PAYLOAD --path /items/0
cf-hana result search q7f3a9c2b "ready"
cf-hana result export q7f3a9c2b --row 1 --column CONTENT --output content.txt
cf-hana result list
cf-hana result prune
cf-hana result clear
--save is available only for SELECT and WITH statements and remains a
hard failure if the result cannot be stored. The programmatic API keeps
returning full-fidelity QueryResult values and does not write result refs.
Saved results
A saved result is kept for 7 days, then removed by the next cf-hana command that touches the
store (or immediately by cf-hana result prune). Nothing else expires it and nothing caps how many
accumulate. Files are written 0600 inside 0700 directories.
Pruning only ever removes a result that has expired, or a ref directory it has verified is empty. One this version cannot read — a permission
error, a partial write, or a format a newer cf-hana wrote — is deliberately left on disk and
reported by cf-hana result prune on stderr, so a downgrade or a stale global install cannot destroy
saved results it merely fails to understand. cf-hana result show says which of those happened
rather than reporting a readable file as missing.
The trade-off is that a result this version cannot read is then kept indefinitely: no TTL reaches it,
and the only way to reclaim it today is cf-hana result clear, which removes every saved result it can see — it does not reclaim a leftover <ref>.tmp-<pid> directory from an interrupted save.
Insufficient-privilege guidance
When HANA reports error code 258, or the equivalent insufficient privilege
message without a numeric code, cf-hana keeps the original failure and exit
status and adds a stderr hint. The hint identifies the schema, role-selected
technical user, current binding, and other named HANA bindings already found on
the app. It suggests an explicit --binding <name> or another app/full
selector. It never retries the SQL automatically under another database user.
Invalid table/view suggestions and metadata cache
When cf-hana query fails with a likely HANA invalid table, view, or catalog
object error, the CLI keeps stdout empty/parseable and prints the original error
plus a small Did you mean: list to stderr. Suggestions are based on objects in
the active connection schema and include physical tables from SYS.TABLES and
views from SYS.VIEWS; tables output remains table-only for compatibility.
To avoid repeatedly reading catalog metadata after typo failures, cf-hana stores only schema, object name, and object type under:
~/.saptools/cf-hana/metadata/
Metadata cache files are private (0700 directories, 0600 files), written
atomically, and expire after exactly 30 minutes. The cache key is derived from
non-secret connection identity: selector, app name, host, active schema, role,
driver, and selected binding name/index. It does not include passwords,
certificates, tokens, SQL parameter values, result rows, or table data, and
malformed cache files are treated as
misses. Pass --refresh-metadata to bypass this cache for a query. The legacy
--refresh flag is accepted for compatibility but does not bypass the metadata
cache and is not a credential-cache control because binding discovery is already
live. If metadata lookup or cache I/O fails, cf-hana preserves the original
query error and simply omits suggestions.
Programmatic API
| Export | Purpose |
|---|---|
connect(selector, options?) | Open a reusable, pooled HanaClient. |
query(selector, sql, params?, options?) | One-shot: connect, query, close. |
withConnection(selector, work, options?) | Run work with a client that auto-closes. |
HanaClient | query, execute, backupWriteStatement, selectFrom, count, insertInto, update, deleteFrom, transaction, listSchemas, listTables, listCatalogObjects, listColumns, explain, close. |
createDriver, formatResult, build* | Lower-level building blocks. |
ConnectOptions highlights: role (runtime | hdi), bindingName /
bindingIndex, readOnly, allowDestructive, autoLimit, queryTimeoutMs,
connectTimeoutMs, deprecated refresh, pool.
HanaClient.info additively exposes optional target and binding provenance for
programmatic checks: selectorSource, regionConfirmed,
selectorCanBePinned, bindingName, bindingIndex, and
availableBindingNames. Library connections remain silent; only the CLI writes
the resolved-target notice to stderr.
Credentials
Credential discovery is live-only and does not read @saptools/cf-sync
snapshots or ~/.saptools/cf-db-bindings.json.
- Bare app selectors (
app-demo) read the activecf target, preserve its exact validated API endpoint, and first runcf env app-demowith your current CF session. This path does not requireSAP_EMAILorSAP_PASSWORDwhile the current session is healthy. - A successful direct
cf envis accepted only if a secondcf targetread confirms the same API endpoint, org, and space. - If that direct bare-app call fails with an auth/session error, cf-hana falls
back to an isolated temporary
CF_HOME, runscf api <current-endpoint>, authenticates withSAP_EMAIL/SAP_PASSWORD(or the programmaticemail/passwordoptions), targets the current org/space, and readscf env. - Explicit
region/org/space/appselectors always use isolated live authentication. Region keys support current SAP CF technical keys, including indexed regions such aseu10-005,eu20-001,us10-002, and China endpoints such ascn40onplatform.sapcloud.cn.
The legacy refresh / --refresh option is retained only for compatibility and
has no credential-cache meaning; binding discovery is already live. Use
--refresh-metadata when you specifically want to bypass the table/view
suggestion metadata cache. Credential resolution writes no binding credentials
under ~/.saptools/.
Cloud Foundry shell-outs use 60-second bounded timeouts and retry transient
timeout/network failures. HANA connection and query timeout defaults are also
60 seconds unless overridden with --timeout or API options.
SQL history
Successful direct SQL calls are appended to daily JSONL files:
~/.saptools/cf-hana/histories/YYYY-MM-DD.jsonl
Each entry includes the timestamp, package version, selector, app name, schema,
role, operation (query or execute), statement kind, SQL text, parameter
count, row count, truncation flag, and elapsed time. Parameter values,
credentials, certificates, and result rows are not stored.
History retention runs opportunistically after each append and deletes dated
history files older than five days. Helper-driven catalog SQL such as tables,
columns, and table/view suggestion metadata reads is not recorded as user SQL
history.
Write backups
When cf-hana query receives a supported write, it first builds and runs a
pre-image SELECT:
UPDATE <target> SET ... WHERE ...becomesSELECT * FROM <target> WHERE ....UPSERT <target> VALUES ... WHERE ...becomesSELECT * FROM <target> WHERE ....REPLACEfollows the same plan as its HANAUPSERTsynonym.- A reliably parsed matched
MERGE INTOuses a correlatedEXISTSquery for the matched target rows. If the target is unambiguous but an exact matched set is not, cf-hana backs up the whole target table. DELETE FROM <target> WHERE ...becomesSELECT * FROM <target> WHERE ....
UPSERT/REPLACE subquery forms use a conservative whole-target pre-image.
Insert-only MERGE statements need no pre-image. For the supported write forms
above, cf-hana refuses execution if it cannot identify a trustworthy target,
derive a safe pre-image, preserve the backup within the 256 MiB ceiling, or
write the backup files. This refusal cannot be overridden with
--allow-destructive.
The backup is saved before the write runs:
~/.saptools/cf-hana/backups/YYYYMM/
<region-org-space-app>-<operation>-<timestamp>.sql
<region-org-space-app>-<operation>-<timestamp>.statement.sql
<region-org-space-app>-<operation>-<timestamp>.json
The main .sql backup file contains CSV-formatted rows returned by the derived
SELECT, matching the requested region/org/space/app/action/timestamp naming
shape. The companion .statement.sql file contains the original write statement,
and .json contains non-secret metadata for auditability. Backup files are not
deleted by cf-hana; clean them up manually when they are no longer needed. The
backup path is printed to stderr so stdout remains parseable.
Backup directories use mode 0700; statement, CSV, and metadata files use
mode 0600.
Safety
- Read-only mode (
readOnly/--read-only) rejects every DML and DDL statement. - Destructive guard blocks
DROP/TRUNCATE/ALTERandUPDATE/DELETEwithout a top-levelWHERE, plus unconditional matchedMERGE DELETE, unlessallowDestructive/--allow-destructiveis set. - Single statement per call is an enforced, guarded property: a SQL
argument containing more than one genuine top-level statement (separated
by a real, non-quoted, non-commented
;) is refused unconditionally — not overridable by--allow-destructiveor--read-only.CREATE PROCEDURE/FUNCTION/TRIGGERdefinitions andDOanonymous blocks are exempted when chained back to back, since theirBEGIN/ENDbodies legitimately contain internal semicolons — but each chunk's own header (its name, then everything up to its firstBEGIN) must contain no top-level;and no keyword, from a fixed disqualifying set, that could lead an independent statement, and its body'sBEGIN/ENDnesting must genuinely balance and reach either the true end of the input or the start of another such definition. A reordered or partialBEGIN/END, a statement skipped over on the way to a laterBEGIN, and content appended after a real body's ownEND(whether trailing the last definition or sandwiched between two), are all refused; only chaining several independently well-formed definitions back to back is allowed. Known, narrow limitations (see CHANGELOG for full detail): aTRIGGERheader may legitimately referenceINSERT/UPDATE/DELETEas its own event keywords (BEFORE INSERT,AFTER UPDATE, ...), so those three are not disqualifying specifically forTRIGGER; a chunk disguised asCREATE TRIGGER, chained after a separate legitimate definition, referencing one of them with noBEFORE/AFTER/INSTEAD OFprefix at all is not caught by this check. Separately, the disqualifying keyword set is a fixed list (not every keyword that could lead a dangerous statement in general —GRANT/REVOKEare notable omissions); this is not specific to the carve-out, since a bare, semicolon-free statement leading with any keyword this guard's classifier does not recognize is already treated as non-destructive regardless of any routine/trigger wrapper. - Auto-limit appends a
LIMITto bareSELECTstatements (default 100);QueryResult.truncatedreports when it clipped the result. Disable withautoLimit: false/--no-auto-limit.
The guard is a convenience, not a security control: always pass values as bound parameters.
Connectivity fallback
HANA Cloud instances are frequently IP-allowlisted to only accept connections
from inside the same Cloud Foundry landscape. When a direct connection fails
in a way that means the initial socket could never be established — not an
authentication, privilege, or query failure — cf-hana can retry through an
SSH port-forward opened via cf ssh against another app in the same org/space,
which usually can reach the host even when your machine cannot.
auto(default): tries the direct connection first, with zero added behavior when it succeeds. Only a classified connectivity failure triggers the fallback: discover a jump-host app (the target app itself, then a few other started apps viacf apps), open a local port-forward, and retry through127.0.0.1.--tunnel: skips the direct attempt and connects via a tunnel immediately — for a host already known to be unreachable directly, so you are not paying the connect-timeout cost (up to 60s) on every invocation.--refresh-tunnel: bypasses a cached/live tunnel and forces a fresh establishment attempt.
There is deliberately no flag to disable this capability: a tunnel attempt
only ever engages after a direct failure (or when explicitly requested via
--tunnel), and on total failure it rethrows the original connection error
unchanged, so it can only help or no-op.
The live tunnel is reused — both across every connection this CLI's own pool
opens in one invocation, and across separate cf-hana invocations run in a
row against the same host (this CLI's realistic dominant usage pattern, e.g.
an AI agent running several queries back to back) — instead of re-negotiating
SSH on every single command. State lives at ~/.saptools/cf-hana/tunnel/
(mode 0700/0600, no credentials). A cached tunnel is closed immediately,
regardless of its remaining lifetime, the moment a later invocation targets a
different Cloud Foundry org — a different client's landscape never keeps an
unattended live SSH path open just because its keepalive has not lapsed.
In the worst case (a silently-dropping network path, so the direct attempt
runs its full connect timeout, and no candidate app works either), auto
mode takes roughly 15s (direct, capped once a tunnel fallback is available —
see below) + 25s (tunnel budget) ≈ 40s before surfacing a final error —
bounded and predictable, not open-ended. Use --tunnel to skip the direct
cost entirely for a host you already know is unreachable.
Whenever a tunnel fallback is available at all (auto mode, the default),
the direct attempt's own timeout is capped at the tunnel side's own
per-candidate ceiling (15s by default) even if a longer timeout is
configured — a silently-hanging (rather than actively refused) direct
connection would otherwise consume its entire configured timeout before the
tunnel path got a chance at all. This only ever shortens an
otherwise-hanging attempt; it never affects a host that refuses quickly, or
a configured timeout that is already shorter.
cf-hana also disables SAP HANA Cloud's reactive mid-auth redirect (used
for per-node/pod locality routing) on every connection, direct or tunneled:
without this, a multi-node HANA Cloud instance can redirect an
already-established connection to a different internal hostname that isn't
reachable outside SAP's own network, silently abandoning a working SSH
tunnel for a fresh, untunneled connection that fails for the same reason the
original direct connection did. The original bound host is always a real,
working endpoint for your binding, so this has no effect on which schema,
user, or data you reach.
Known limitations
- Cross-org reaper and concurrent processes. Every connection attempt closes any cached tunnel tagged for a different Cloud Foundry org than the current target, regardless of remaining lifetime — intentional hygiene so switching orgs never leaves a stale tunnel open. On a shared machine, this can also close another, unrelated, concurrently-running invocation's active tunnel to a different org; there is no portable, low-cost way to detect "is this port actively in use by someone else" from outside that TCP session.
- Stale-tunnel reaping trusts the recorded pid. The reaper terminates a
dead or expired tunnel's process by its recorded pid, with no additional
identity check (e.g. confirming the pid still refers to a
cf sshprocess). On a long-uptime machine, pid reuse could in principle target an unrelated process; this is a narrow race not worth a platform-specific check for. - Programmatic (non-CLI) pool usage can strand idle connections. The connection pool reuses the most-recently-released idle connection first (LIFO); a long-lived process that opens a burst of connections and then settles into serial one-at-a-time use can leave earlier connections idle-but-unreaped for the pool's lifetime. The CLI itself always drains its pool at the end of every command, so this only affects long-lived programmatic use of the library.
- Pre-write backups are never pruned. Unlike SQL history (5-day
retention) and saved query results (TTL plus
result prune/result clear), files under~/.saptools/cf-hana/backups/accumulate indefinitely with no retention policy or cleanup command.
Requirements
- Node.js >= 20.
- A HANA binding reachable from your network. Resolving a bare app name needs the Cloud Foundry CLI and an active
cf target. Isolated fallback or explicit selectors additionally needSAP_EMAIL/SAP_PASSWORD.
Development
pnpm --filter @saptools/cf-hana build
pnpm --filter @saptools/cf-hana lint
pnpm --filter @saptools/cf-hana typecheck
pnpm --filter @saptools/cf-hana test:unit
pnpm --filter @saptools/cf-hana test:e2e:fake
The live e2e suite (test:e2e:live) needs real SAP_EMAIL / SAP_PASSWORD and
a CF_HANA_E2E_TARGET selector pointing at a HANA-bound app.
License
MIT
History
Changed 1, Fixes 10, Other 3
Notes
Fixed
-
Pruning no longer deletes saved results it cannot read.
pruneResultSessionsruns at the head of every save, read and list, and it treated four distinct outcomes — file absent, I/O error, invalid JSON, unrecognized shape — as one "missing" answer, thenrm -rf'd the directory. So a nominally read-onlyresult listdestroyed a valid unexpired session whose only difference was a newerversion, and one that was merely unreadable at that moment (a permission error, with the data fully intact). Because the self-updater makes mixed versions on one machine normal, that also meant an older cf-hana silently wiped a store a newer one had written.Reads are now classified, and only an expired session or an empty ref directory is removed. A manifest that cannot be read or recognized is left exactly where it is and counted as retained; a ref directory holding files under names this version does not know is retained too.
-
A saved result with a damaged
expiresAtis no longer immortal.Date.parsereturnsNaNfor it andNaN <= nowisfalse, so such a session survived every prune for ever and still read back. Expiry now falls back tocreatedAtplusttlMinutes, and a session whose age cannot be established by either route is treated as expired. -
One undeletable directory no longer takes the whole store offline. Deletion is attempted per session, and the housekeeping prune is best-effort, so
result showandresult listkeep working on intact sessions even when the results directory cannot be listed.result showalso enforces the TTL itself instead of relying on that prune having succeeded. -
result shownow says which of the three things went wrong. An unreadable manifest and one written in a newer format used to report "Saved result not found or expired", which was untrue; both now name the file, and a newer format says it was left in place. Added theRESULT_UNREADABLEerror code. -
result listreports the ref that actually resolves. The listed ref came from inside the manifest while lookup uses the directory name; when those disagreed,listadvertised a ref thatresult showrejected. Expired sessions that prune could not remove are omitted rather than shown. -
Pruning no longer deletes a saved result whose expiry it cannot establish.
resolveExpiryMillispreviously fell back to "treat as expired", so a manifest with damaged timestamps had its rows deleted even though they were perfectly readable — and a version that changed only the timestamp encoding (a TemporalZonedDateTime, an ISO week date, epoch millis as a string, or canonical ISO with surrounding whitespace, whichDate.parsedoes not trim) would have had its data destroyed by an older binary. Such a session is now retained and reported, like any other manifest this version cannot fully interpret. One rule now covers every case: only an expired session with a resolvable date, or a ref directory verified to be empty, is ever deleted. -
A
ttlMinutestoo large to date no longer produces an immortal session. The bound is what aDatecan hold, not what a float can hold:Number.isSafeIntegeradmits a value whose product with 60000 reaches 5.4e20, while aDateholds at most ±8.64e15 ms — socreatedAt + ttlMinutescould yield a finite expiry no clock will ever reach, which survived every prune and was not even counted. A derived expiry beyond theDaterange is now treated as unresolvable, hence retained. -
result prunenames the refs it left in place instead of only counting them. A retained session is omitted fromresult listand no command removes one, so the ref is the only way to find the file. -
result pruneexits non-zero when it could not delete an expired session. It is the only machine-readable health signal the store has, and reporting a partial sweep as success meantif cf-hana result prune; then …could never detect a store it had failed to clean. -
PruneOutcomeis now{ removed, failed, retainedRefs }.retainedwas a count the user could not act on; the refs replace it.
Changed
pruneResultSessionsreturns{ removed, retained, failed }instead of a bare count.result prunestill printsremoved=Nas its only stdout line and reports retained and undeletable counts on stderr. The type is internal to the CLI:index.tsdeliberately exports no part of the result store.
Known limitations
- A leftover
<ref>.tmp-<pid>directory from an interrupted save is invisible toresult list,result pruneandresult clear, and no TTL reaches it. Reclaim it by hand. - A retained session is reported by
result prunebut not listed, and there is noresult rm;result clearis the only in-tool removal and it removes everything it can see. result listomits a session it could not read without saying so. Onlyresult prunereports those.
Other 1
Notes
- Self-updating. Every command now checks npm for a newer
@saptools/cf-hana(at most once an hour) and, when one exists, installs that exact version and re-runs the command on it, announcing both steps on stderr.SAPTOOLS_AUTO_UPDATE=on|notify|offcontrols it (see the README's Updates section); it is off by itself in CI, in tests, from a source checkout and inside the re-run. Newcf-hana self-update [--check]forces the check and install now.--versionnow readspackage.jsonat runtime. Both come from the private, build-time-bundled@saptools/corepackage; the published tarball gains no runtime dependency.
- Self-updating. Every command now checks npm for a newer
Other 4
Notes
- Security fix:
0.5.3's routine/anonymous-block carve-out (isRoutineDefinition/isAnonymousBlock) checked only that a top-levelBEGINandENDexisted somewhere in the string, not that they were correctly paired, ordered, or preceded only by plausible header content — a bare keyword-existence check, not a structural one. Two independent exploit families reopened the exact zero-flags bypass0.5.3exists to close, in normal mode with no flags required at all:- A reordered/partial body, e.g.
CREATE PROCEDURE p AS DELETE FROM t WHERE id=1; DROP TABLE other; END BEGIN(and theDOequivalent). - (Found by independent review, after this release's first cut
already shipped internally) the search for a routine/block's first
BEGINapplied no validation to what it skipped over on the way there — it would skip straight past an entire independent,;-separated statement to reach a later, genuinely well-formedBEGIN ... END, exempting the smuggled statement along with it, e.g.CREATE PROCEDURE p(x INT); DROP TABLE other; SELECT 1 BEGIN END. Fixed by depth-trackingBEGIN/ENDnesting the same way this file already depth-tracks parentheses, and requiring the region between a chunk's header keywords and its own firstBEGINto contain no top-level;and no keyword that could lead an independent, dangerous statement from a fixed set (SELECT/CREATE/DROP/ALTER/TRUNCATE/RENAME/COMMENT/WITH/CALL/MERGE/UPSERT/REPLACE, plusINSERT/UPDATE/DELETEfor every header type exceptTRIGGER, since a trigger header legitimately references those three as its own event keywords, e.g.BEFORE INSERT—MERGE/UPSERT/REPLACEhave no such legitimate role in a trigger header and stay disqualifying for every type). The region is also required to skip exactly the routine/trigger's own name first (a bare or quoted identifier, optionally schema-qualified) before that keyword check runs at all, since a legitimately-named routine can coincide with one of those keywords (e.g. an unquotedreplace) without being a smuggled statement — a single identifier token can never itself contain a real top-level;or a second keyword, so this cannot be used to also skip real smuggled content. This closes the reordering bypass, a strayENDwith no precedingBEGIN, both keywords appearing early with real content trailing the close, and both exploit families above. As a direct consequence,0.5.3's own disclosed residual limitation — content genuinely appended after a routine or anonymous block's own realEND— is also closed, for bothCREATE PROCEDURE/FUNCTION/TRIGGERandDOblocks. Chaining several independently-legitimate definitions back to back (e.g. twoCREATE PROCEDUREstatements in one call) remains allowed, since defining a routine is not itself destructive. Disclosed, narrow residual limitation: becauseTRIGGERheaders are exempted from theINSERT/UPDATE/DELETEpart of that keyword check (to avoid rejecting ordinary trigger event clauses), a chunk that specifically disguises itself asCREATE TRIGGERand references one of those three verbs with no;and noBEFORE/AFTER/INSTEAD OFprefix at all in its header (e.g.CREATE TRIGGER t2 DELETE FROM customers BEGIN END, chained after a separate legitimate definition so a real;exists for the check to matter at all — a fully standalone, semicolon- free instance of this shape is already unaffected by this check either way, sincehasMultipleStatementsnever flags a string with no top-level;regardless) is not rejected. Distinguishing a legitimate trigger event keyword from a bare one requires validating it is actually preceded by a trigger-timing keyword, which edges into full SQLScript-header grammar parsing — out of scope for this fix, the same way parsing a routine body's own contents is out of scope for the carve-out itself. This is narrower than either exploit family above: it requires specifically choosingTRIGGER(notPROCEDURE/FUNCTION/DO) as the disguise, using specificallyINSERT/UPDATE/DELETE(not any other keyword), chained with a real;elsewhere in the input.
- A reordered/partial body, e.g.
- Separately noticed while verifying the fix above, not fixed here:
the header-disqualifying keyword set above is a fixed list derived from
this file's own existing DDL/DML/SELECT/WITH/CALL vocabulary, not every
keyword that could lead an independent dangerous statement in general —
GRANT/REVOKEare notable omissions. This is not unique to the routine/block carve-out: a bare, semicolon-free single statement leading with an unrecognized keyword (GRANT,REVOKE, or anything elseclassifyByKeyworddoes not recognize) is already treated as non-destructive by this guard's existing classifier, with or without any routine/trigger wrapper around it, since only a statement's own leading keyword andWHERE-scope are inspected. Extending the classifier's keyword vocabulary to coverGRANT/REVOKE(and auditing for other gaps of the same shape) is a separate, broader task than the routine-header validation this release adds, and is not attempted here. - Security fix, pre-existing since
0.5.1/0.5.2, broader than previously disclosed: theWITH-CTE-list parser (cteListEndIndex) did not fail closed once a CTE list was itself well-formed — if whatever followed it was neither a comma nor a recognizable keyword (e.g. stray punctuation), it returned a defined result with an empty""keyword rather thanundefined, which slipped past the guard's unresolved-WITHfail-closed check.0.5.3's changelog disclosed a narrower version of this gap specific to a semicolon placed mid-CTE-list; this follow-up review found the same defect is reachable with no semicolon involved at all (WITH a AS (SELECT 1 FROM DUMMY) !!! DELETE FROM customers). Fixed by requiring the character immediately after a completed CTE list to be keyword-shaped (alphabetic) before treating the resolution as successful; otherwise it now fails closed exactly like an already-malformed CTE list. An unrecognized-but-alphabetic continuation (e.g.EXPLAIN) is unaffected and continues to resolve and classify exactly as before. - The hand-maintained zero-width/invisible-character allowlist (5 code
points, added in
0.5.3) is replaced with a Unicode-property match (\p{Cf}, the standard "Format" general category), closing several realistic gaps it missed — including left-to-right/right-to-left marks, soft hyphen, and several invisible math-layout characters — instead of only the handful of code points a prior session happened to notice. Verified empirically to match every previously-listed code point, match none of A-Z/0-9/standard SQL punctuation, and not create any way to hide real trailing content.
- Security fix:
Other 4
Notes
- Security fix: the safety guard and pre-write backup planner only ever
looked at a SQL string's leading statement — nothing checked whether the
string contained a second,
;-separated statement after it. A properlyWHERE-scoped write (exactly the kind of statement the guard is designed to wave through) could carry an entirely separate, unscoped statement (DELETE FROM t WHERE id=1; DROP TABLE other) past the guard with no flags required at all, in both normal and--read-onlymode. Until this release, the only thing preventing a smuggled second statement from actually running was SAP HANA's own rejection of multi-statement text — behavior this tool never guaranteed and must not be understood to rely on.cf-hananow refuses any SQL argument containing more than one genuine top-level statement, unconditionally — not overridable by--allow-destructiveor--read-only— via a structural, quote/comment/ paren-aware scan for a real (not string-literal, not commented-out, not nested);separator, matching the same "cannot safely determine the statement's true shape, refuse rather than guess" precedent already established for an unresolvableWITHclause. The pre-write backup planner applies the identical check before doing anything else, since it runs on every CLI statement before the main guard does and would otherwise fold a smuggled statement straight into its derived backupSELECT. CREATE PROCEDURE/FUNCTION/TRIGGERdefinitions that have a realBEGIN/ENDbody — and, separately, HANA SQLScript anonymousDO [(...)] BEGIN ... ENDblocks — are exempted from this check, since a genuine routine/block body legitimately contains many internal, top-level-looking semicolons that are not additional smuggled statements. An independent review caught that the first implementation of this exemption checked only the leading keyword pair (e.g.CREATE PROCEDURE) with no body required at all, which would have let a bareCREATE PROCEDURE p; DROP TABLE otherskip the multi-statement check entirely with no flags required — fixed before release by requiring an actual top-levelBEGINandENDto be present. Disclosed, intentional residual limitation: the exemption still covers the entire routine-creation statement once a body is confirmed present, so content genuinely appended after such a body's ownEND(e.g.CREATE PROCEDURE p AS BEGIN ... END; DROP TABLE other) is not caught by this check either — a full SQLScriptBEGIN/END-nesting-aware parser would be required to find where the routine body truly ends, which is out of scope for this fix. This narrows a previously-universal gap down to one specific, harder-to-reach statement shape; it does not close it completely.- Also caught by that same review and fixed before release: a lone
zero-width space or similar invisible Unicode formatting character
trailing an otherwise ordinary single statement's
;— a realistic artifact of pasting SQL out of a chat app, word processor, or web page — was being treated as a smuggled second statement and incorrectly refused. An unclosed top-level parenthesis before a genuine;separator (itself already invalid SQL) also defeated detection and the existingWHERE-scope check simultaneously; it is now treated the same as an unresolvableWITHclause — refused outright, since the statement's real shape can't be determined. - Separately noticed while verifying this fix, not fixed here: the
0.5.2WITH-CTE-list parser (cteListEndIndex) does not fail closed for a;placed inside a multi-CTE list (between two CTE definitions rather than after the whole list) — it returns a defined result with an emptykeywordinstead ofundefined. This is not a live guard bypass on its own (any input reaching this path also has a genuine top-level;, which this release's new check independently catches), but it is a real, pre-existing gap in the0.5.2parser worth a future, narrowly-scoped fix.
- Security fix: the safety guard and pre-write backup planner only ever
looked at a SQL string's leading statement — nothing checked whether the
string contained a second,
Other 1
Notes
- Security fix: the
0.5.1fix forWITH-prefixed guard bypasses shipped with its own gap — aWITHstatement whose CTE name was a double-quoted identifier (e.g.WITH "x" AS (...) DELETE FROM ...) was misclassified asunknowninstead of resolving to its real trailing statement, and thatunknownclassification was previously treated as non-destructive and exempt from the automatic pre-write backup. A quoted-CTE-name write with noWHEREclause reached the database with no client-side block at all, and no backup was attempted for a quoted-CTE-name write of any shape. Anyone who upgraded to0.5.1believing that release fully closed theWITHguard-bypass class should know it did not, quite yet — this release does. The parser now correctly recognizes a quoted CTE name instead of skipping past it as if it were blank space; a genuinely unparseableWITHstatement (for any reason, not only quoting) is now also treated as destructive by the safety guard and refuses the pre-write backup outright, rather than silently falling back to "not destructive, no backup" as it did before.
- Security fix: the
Other 8
Notes
- Security fix: a
WITH-prefixed write (e.g.WITH x AS (...) DELETE FROM ...) and a bareCALL <procedure>()both previously slipped past the--read-onlyand destructive-statement guards entirely — the former was always classified as a harmlessSELECTregardless of what it actually did, and the latter was never classified as destructive at all, so it ran with no--allow-destructiveconfirmation even in the default (non-read-only) mode. Both are now correctly classified and guarded. Anyone relying on--read-onlyor the destructive-statement guard as a hard trust boundary should treat this as the release that closes that gap. The automatic pre-write backup now also correctly recognizes and protects aWITH-prefixedUPDATE/DELETE/UPSERT/MERGE, matching what a non-WITHequivalent write already produced. - Disable SAP HANA Cloud's reactive mid-auth redirect on every connection (direct and tunneled): on a genuine multi-node HANA Cloud instance, the server can redirect an already-established connection to an internal per-node hostname that isn't reachable outside SAP's own network, which previously defeated the SSH-tunnel fallback by silently abandoning the tunnel for a fresh, untunneled connection that failed the same way the original direct connection did.
- Fix the SSH-tunnel fallback's candidate discovery calling
cf apps(measured at up to ~20s against a large space) before ever trying the already-known target app or a cached "last worked" app, which could starve the entire shared fallback budget before a working candidate got a chance. Known candidates are now always tried first;cf appsis only called if none of them work, and is bounded to whatever budget remains rather than retried on its own separate timeout/retry policy. - Cap the direct-connect attempt's own timeout to the tunnel fallback's per-candidate ceiling whenever a fallback is available, so a silently hanging (rather than actively refused) direct connection can no longer consume its full configured timeout before the tunnel path gets a chance — lowering the documented worst-case latency from ~85s to ~40s.
- Fix
--refresh-tunnelleaking the SSH process it superseded; the old tunnel is now terminated instead of running until its keepalive elapses. - Surface a failed tunnel candidate's actual stderr (e.g. "SSH disabled for this app") via the existing connectivity status output on stderr, instead of discarding it silently.
- Fix a freshly-established or reused tunnel being cached as usable even when its own post-connect setup failed in a way that looked like a dropped connection rather than a genuine, actionable HANA rejection.
- Bound how long a process waits for another concurrent invocation to finish establishing the same host's tunnel, so the entire shared deadline can no longer be spent waiting on a leader whose owning process has already died.
- Security fix: a
Other 3
Notes
- Add an SSH-tunnel fallback for HANA Cloud hosts unreachable directly (e.g.
IP-allowlisted landscapes): on a classified connectivity failure, discover
a jump-host app in the same org/space via
cf apps, open a localcf ssh -Lport-forward, and retry through it. Zero behavior change when the direct connection succeeds. - Add
--tunnelto skip the direct attempt and connect via a tunnel immediately, and--refresh-tunnelto bypass a cached/live tunnel and force a fresh attempt. No disable switch: the fallback can only help or no-op, and rethrows the original connection error unchanged on total failure. - Persist and reuse the live tunnel under
~/.saptools/cf-hana/tunnel/across every connection this CLI's pool opens and across separate invocations run in a row against the same host, with a race-free concurrent-establishment marker and automatic reaping of dead or cross-org tunnels.
- Add an SSH-tunnel fallback for HANA Cloud hosts unreachable directly (e.g.
IP-allowlisted landscapes): on a classified connectivity failure, discover
a jump-host app in the same org/space via
Other 6
Notes
- Back up
REPLACEand matchedMERGE INTOpre-images, cap backup size, and refuse those statements when their target or safe pre-image cannot be derived. - Confirm every resolved CLI target on stderr, distinguish ambient from explicit
selectors, and verify direct
cf envidentity against the ambient target. - Add actionable insufficient-privilege guidance with schema, technical user, current binding, and sibling binding names without automatic retries.
- Auto-save exact rows only when compact CSV shortens cells, add
--no-auto-save, and make implicit storage failures non-fatal. - Add lossless
query --format table|json|json-compact|csv, flat catalog name output, and exact per-command JSON schemas in help while preserving defaults. - Scope metadata suggestions by binding identity and document the existing bounded CF retries and 60-second timeout defaults.
- Back up
Other 1
Notes
- Normalize HANA
BOOLEANresult columns that arrive from the driver as1/0into JavaScripttrue/falsevalues before CLI/API formatting and saved refs.
- Normalize HANA
Other 1
Notes
- Keep explicit selector region lookup map-backed so unknown technical keys fail before isolated authentication unless they are added to the maintained SAP CF region catalog.
Other 4
Notes
- Fix Cloud Foundry region resolution for indexed SAP regions such as
eu10-005, representativeeu20/us10indexed endpoints, and Chinaplatform.sapcloud.cnendpoints. - Keep bare CLI selectors on the core current-session path so healthy
cf env <app>calls do not require isolated SAP re-authentication. - Preserve and validate the current CF API endpoint for auth fallback, reject unsafe endpoint shapes, and keep binding discovery live-only without restoring
cf-synccache reads. - Update
--refresh, README, and cf-hana skill text to describe the retained compatibility flag truthfully.
- Fix Cloud Foundry region resolution for indexed SAP regions such as
Other 1
Notes
- Remove the
cf-hana: saved result expires at...stderr notice fromquery --saveoutput while keeping result refs available for inspection.
- Remove the
Other 3
Notes
- Add actionable HANA LOB
ORDER BY/GROUP BYhints that recommend removing LOB columns or wrapping them withTO_VARCHAR(<column>). - Add invalid-column typo suggestions that inspect target table columns and print close matches to stderr.
- Increase the default saved result TTL from 60 minutes to 7 days.
- Add actionable HANA LOB
Other 3
Notes
- Add invalid table/view recovery suggestions for failed
querystatements, with nearby table and view names printed to stderr so stdout remains parseable. - Add a private local metadata cache under
~/.saptools/cf-hana/metadatawith a strict 30-minute TTL and--refresh-metadatabypass. - Include schema-scoped
SYS.TABLESandSYS.VIEWSmetadata for suggestions without caching credentials, parameters, result rows, or table data.
- Add invalid table/view recovery suggestions for failed
Other 5
Notes
- Change CLI
queryoutput forSELECT/WITHstatements to compact CSV and remove--formatfromquery. - Default bare
SELECTqueries to at most 100 returned rows, with accurate N+1 truncation detection. - Limit visible SELECT data cells to 128 characters by default, configurable
with
--cell-limit <n>up to 10,000. - Add
query --saveandcf-hana resultcommands to save exact returned rows for 60 minutes, inspect rows/cells/JSON paths by ref, search saved values, export exact cells, and prune local result sessions. - Keep programmatic query APIs and write backups full-fidelity.
- Change CLI
Other 3
Notes
- Expand fake-backed E2E coverage for complex
UPDATEandDELETEbackups, including mixed-case keywords, comments, quoted identifiers, nested queries, placeholder filtering, and unscoped writes. - Verify backup SELECT failures, write failures, filesystem failures, read-only mode, malformed SQL, and parameter mismatches cannot bypass backup safety.
- Add opt-in fake-driver statement tracing and deterministic failure injection without recording parameter values.
- Expand fake-backed E2E coverage for complex
Other 2
Notes
- Remove the CLI
--no-backupopt-out socf-hana queryalways attempts a local backup before runningUPDATEorDELETE. - Keep backup paths on stderr and keep stdout parseable for table, JSON, and CSV output.
- Remove the CLI
Other 3
Notes
- Add automatic local CSV backups before CLI
queryruns anUPDATEorDELETE. - Derive the backup
SELECTfrom the write target and top-levelWHEREclause, preserving only the WHERE parameters forUPDATEstatements. - Save each backup in its own non-expiring folder with
statement.sqlandbackup.csv.
- Add automatic local CSV backups before CLI
Other 3
Notes
- Add local SQL history for successful direct
queryandexecutecalls under~/.saptools/cf-hana/histories/YYYY-MM-DD.jsonl. - Rotate SQL history with five-day retention and keep parameter values, credentials, certificates, and result rows out of the history file.
- Keep helper-driven catalog SQL out of user SQL history and document the new local state behavior.
- Add local SQL history for successful direct
Other 5
Notes
- Harden connection pooling so queued callers continue after transient reconnect failures.
- Preserve query results when HANA statement cleanup fails and close partially opened clients on schema setup errors.
- Strengthen read-only and destructive-statement checks around comments, quoted identifiers, and unknown statements.
- Improve
explain()statement isolation, cleanup, and read-only behavior. - Validate CLI numeric options strictly and align E2E diagnostics with project defaults.
Other 1
Notes
- Patch release to publish via npm trusted publishing after the manual
0.1.0bootstrap.
- Patch release to publish via npm trusted publishing after the manual
Other 3
Notes
- Initial release: run SQL directly against SAP HANA Cloud databases bound to a Cloud Foundry app, addressed by a
region/org/space/appselector (or a bare app name). - Credentials are resolved cache-first via
@saptools/cf-sync, with an on-demand live Cloud Foundry fetch fallback. - Includes a
HanaClientwith pooled connections, parameterized queries, transactions, table introspection, query-builder shorthands, a read-only/destructive-statement safety guard, and acf-hanaCLI.
- Initial release: run SQL directly against SAP HANA Cloud databases bound to a Cloud Foundry app, addressed by a
Others providing
Compare all 6 on the Debugging page →- @saptools/cf-debugger@saptools/cf-debuggerOpens a verified Node.js inspector tunnel over cf ssh to an app on SAP BTP Cloud Foundry from one command, with its own local port for each concurrent session.independent, btpNode.js inspector tunnel over cf ssh to Cloud Foundry apps
- @saptools/cf-explorer@saptools/cf-explorerSearches the files of an app deployed to SAP BTP Cloud Foundry over cf ssh, finding the app root, grepping the deployed code and turning matches into file and line candidates.independent, btpFile search in apps deployed to SAP BTP Cloud Foundry over cf ssh
- @saptools/cf-function-trace@saptools/cf-function-traceRecords how one JavaScript function's state changes, step by step, in a Node.js process running locally or on SAP BTP Cloud Foundry, and stores the run as a redacted timeline that can be queried later.independent, btpStep-by-step state recorder for one JavaScript function in Node.js
- @saptools/cf-inspector@saptools/cf-inspectorSets breakpoints, captures variable snapshots and evaluates expressions on a remote Node.js process over the Chrome DevTools Protocol from the shell, with no IDE attached.independent, btpShell debugger for remote Node.js over the Chrome DevTools Protocol
- @saptools/cf-live-trace@saptools/cf-live-traceInjects a bounded HTTP trace hook into a running Node.js app on SAP BTP Cloud Foundry and streams each request and response to the terminal as JSON.independent, btpHTTP trace hook streaming a running CF Node.js app's traffic
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
History in the atlas
- First seen.
For your agent
- Ref
artifact:saptools-cf-hana- JSON
- /artifacts/saptools-cf-hana.json
- MCP
- Connecting an agent