@saptools/cf-hana
v0.1.1
Published
Run SQL against SAP HANA Cloud databases bound to a Cloud Foundry app by region/org/space/app selector
Maintainers
Readme
@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 closes the last gap in the saptools chain: @saptools/cf-sync
already discovers Cloud Foundry topology and HANA service bindings, but nothing
actually executes SQL. This package does. Pass a selector, get a connected,
pooled client, and run SELECT / INSERT / UPDATE / DELETE / DDL.
It is fast because credentials come from cf-sync's on-disk cache (no 5–15s CF
login on the hot path) and connections are pooled and reused within a process.
Features
- Selector-based connect —
region/org/space/appor a bare app name. - Credentials, handled for you — cache-first via
@saptools/cf-sync, with an on-demand live Cloud Foundry fetch as a fallback. - 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.
- Safety guard — opt-in read-only mode and a destructive-statement guard
(blocks
DROP/TRUNCATE/ALTERand unscopedUPDATE/DELETE). - 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-hanaRequires Node.js >= 20. The pure-JavaScript hdb
driver is bundled as a dependency — there is no native build step.
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/acme/dev/orders-srv");
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("orders-srv", "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/acme/dev/orders-srv). Works without any cached topology. - Bare app name —
orders-srv. Resolved against the topology cached bycf-sync sync; throws if the name is ambiguous across spaces.
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 metadataCommon options: --format <table|json|csv>, --refresh, --role <runtime|hdi>,
--binding <name> / --binding-index <n>, --timeout <ms>, --read-only,
--allow-destructive, --limit <n>, --no-auto-limit. The query command also
accepts --param <value> (repeatable) to bind ? placeholders.
cf-hana query eu10/acme/dev/orders-srv "SELECT ID, STATUS FROM ORDERS WHERE STATUS = ?" \
--param OPEN --format json
cf-hana tables orders-srv
cf-hana columns orders-srv ORDERS_APP.ORDERS
cf-hana ping eu10/acme/dev/orders-srvProgrammatic 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, selectFrom, count, insertInto, update, deleteFrom, transaction, listSchemas, listTables, listColumns, explain, close. |
| createDriver, formatResult, build* | Lower-level building blocks. |
ConnectOptions highlights: role (runtime | hdi), bindingName /
bindingIndex, readOnly, allowDestructive, autoLimit, queryTimeoutMs,
connectTimeoutMs, refresh, pool.
Credentials
Credentials are resolved cache-first:
- Read what
cf-sync db-synccached in~/.saptools/cf-db-bindings.json. - On a cache miss (or when
refresh: true/--refreshis passed), fetch them live from Cloud Foundry. The live fetch needsSAP_EMAILandSAP_PASSWORD(or theemail/passwordoptions) and never persists anything to disk.
cf-hana itself writes nothing under ~/.saptools/ — it only reads what
cf-sync cached. The connection pool is in-process and in-memory only, so it is
safe to run many cf-hana processes in parallel and alongside any cf-sync
command.
Safety
- Read-only mode (
readOnly/--read-only) rejects every DML and DDL statement. - Destructive guard blocks
DROP/TRUNCATE/ALTERandUPDATE/DELETEwithout aWHEREclause unlessallowDestructive/--allow-destructiveis set. - Auto-limit appends a
LIMITto bareSELECTstatements (default 1000);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.
Requirements
- Node.js >= 20.
- A HANA binding reachable from your network. Resolving a bare app name, or a
live credential fetch, additionally needs the Cloud Foundry CLI and
SAP_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:fakeThe 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
