@openibm/driver
v0.1.16
Published
Transport and connection layer for IBM i — HTTP, HTTPS, SSH, ODBC
Downloads
508
Maintainers
Readme
@openibm/driver
Transport and connection layer for IBM i. Sends XML to XMLSERVICE, executes SQL against DB2, calls programs, runs CL/QSH/SH commands, manages transactions, and queries schema metadata — with a TypeScript-first, Promise-based API.
Prerequisites
XMLSERVICE on IBM i
All transports except ODBC communicate through XMLSERVICE — an open-source IBM i service program that exposes DB2, program calls, CL commands, and more over XML.
- Documentation: xmlservice.readthedocs.io
- Source: github.com/IBM/xmlservice
XMLSERVICE must be installed and running on your IBM i before connecting. The recommended install method is via yum:
# On IBM i (run in PASE / QShell)
yum install itoolkit-utils # installs XMLSERVICE + xmlservice-cliAlternatively, install the IBM i Open Source Package Management from ACS (Access Client Solutions), which includes XMLSERVICE in the default package set.
| Transport | XMLSERVICE requirement |
|---|---|
| HTTP / HTTPS | Must have the XMLSERVICE CGI endpoint active (/cgi-bin/xmlcgi.pgm) on port 57700 / 47700. Enable via STRTCPSVR SERVER(*HTTP) HTTPSVR(APACHEDFT). |
| SSH | Must have xmlservice-cli installed: yum install itoolkit-utils |
| ODBC | Uses direct DB2 — XMLSERVICE still required for program calls but not for SQL |
Installation
npm install @openibm/driverSSH transport — install the optional peer dependency:
npm install ssh2
# On IBM i: yum install itoolkit-utils (provides xmlservice-cli)
# See: https://xmlservice.readthedocs.io/en/latest/ODBC transport — install the optional peer dependency and the IBM i Access ODBC Driver:
npm install odbc
# Linux: apt-get install ./ibm-iaccess-*.deb
# Mac: install IBM i Access Client Solutions .pkgHTTP / HTTPS transport — no additional installation needed.
Quick Start
HTTP (via SSH tunnel — recommended for development)
import { createDriver } from '@openibm/driver';
const driver = createDriver({
transport: 'http',
system: 'localhost', // SSH tunnel forwards to IBM i
username: process.env.IBMI_USER!,
password: process.env.IBMI_PASS!,
http: {
port: 57700,
database: '*LOCAL', // required when system = 'localhost'
},
});
await driver.connect();
const rows = await driver.query(
'SELECT * FROM MYLIB.ORDERS WHERE CUSTID = ?',
['C001'],
);
await driver.disconnect();SSH (direct — no HTTP server required)
const driver = createDriver({
transport: 'ssh',
system: 'ibmi.example.com',
username: process.env.IBMI_USER!,
password: process.env.IBMI_PASS!,
});
await driver.connect();ODBC (production — native DB2 pool)
const driver = createDriver({
transport: 'odbc',
system: process.env.IBMI_SYSTEM!,
username: process.env.IBMI_USER!,
password: process.env.IBMI_PASS!,
odbc: { poolSize: 10 },
});
await driver.connect();Switch via environment variable
const driver = createDriver({
transport: (process.env.IBMI_TRANSPORT ?? 'http') as 'http' | 'odbc' | 'ssh',
system: process.env.IBMI_SYSTEM!,
username: process.env.IBMI_USER!,
password: process.env.IBMI_PASS!,
});Transport Comparison
| | HTTP / HTTPS | SSH | ODBC |
|---|---|---|---|
| Extra install | None | ssh2 + xmlservice-utils on IBM i | IBM i Access ODBC Driver + odbc |
| SQL path | XMLSERVICE | XMLSERVICE | Direct DB2 |
| Program calls | XMLSERVICE CGI | XMLSERVICE CLI | XMLSERVICE stored proc |
| Connection model | Stateless per request | Persistent SSH session | Persistent pool |
| Default port | 57700 (HTTP) / 47700 (HTTPS) | 22 | 8471 (SSL) / 446 |
| Best for | Dev (with SSH tunnel) | Direct IBM i access | Production |
SSH Tunnel for HTTP Transport
If your IBM i is on a private network, forward the HTTP port locally:
ssh -L 57700:localhost:57700 user@ibmi-host -p <port>Then use system: 'localhost' and http: { database: '*LOCAL' }.
API Reference
Lifecycle
createDriver(config): IBMiDriver
Creates a driver instance. Does not connect — call connect() first.
driver.connect(): Promise<void>
Establishes the connection. For ODBC, opens the pool. For SSH, starts the SSH session. For HTTP/HTTPS, verifies reachability.
driver.disconnect(): Promise<void>
Tears down the connection and releases resources.
driver.isConnected(): boolean
Returns true after connect() and before disconnect().
SQL
driver.query(sql, params?): Promise<Row[]>
Executes a parameterised SQL statement and returns rows as plain objects.
// Simple query
const rows = await driver.query('SELECT * FROM SYSIBM.SYSDUMMY1');
// Parameterised — always use ? placeholders, never interpolate values
const orders = await driver.query(
'SELECT * FROM MYLIB.ORDERS WHERE CUSTID = ? AND STATUS = ?',
['C001', 'OPEN'],
);
// DML / DDL — returns []
await driver.query('INSERT INTO MYLIB.LOG (MSG) VALUES (?)', ['started']);Transport behaviour:
- ODBC — direct DB2
prepare / bind / execute, XMLSERVICE not involved - HTTP / HTTPS / SSH — goes through XMLSERVICE
<sql>
Transactions
await driver.setOptions({ autoCommit: false });
try {
await driver.query('INSERT INTO MYLIB.ORDERS VALUES (?, ?)', [1, 'NEW']);
await driver.query('UPDATE MYLIB.STOCK SET QTY = QTY - 1 WHERE ID = ?', [42]);
await driver.commit();
} catch (e) {
await driver.rollback();
throw e;
}driver.commit(): Promise<void>
driver.rollback(): Promise<void>
Connection Options
driver.setOptions(options): Promise<void>
Sets connection-level options for all subsequent SQL operations.
await driver.setOptions({
autoCommit: false,
naming: 'system', // *LIB/FILE instead of LIB.TABLE
dateFormat: 'iso', // YYYY-MM-DD
timeFormat: 'hms', // HH:MM:SS
defaultLibrary: 'MYLIB', // sets CURLIB
});| Option | Type | Default | Description |
|---|---|---|---|
| autoCommit | boolean | true | Auto-commit each statement |
| naming | 'sql' \| 'system' | 'sql' | sql = LIB.TABLE, system = *LIB/FILE |
| dateFormat | string | — | 'iso', 'usa', 'eur', 'jis', 'mdy', 'dmy', 'ymd', 'jul' |
| timeFormat | string | — | 'iso', 'usa', 'eur', 'jis', 'hms' |
| defaultLibrary | string | — | Sets the current library (CURLIB) |
Program Calls
driver.callProgram(options): Promise<CallProgramResult>
Calls an IBM i *PGM object via XMLSERVICE.
const result = await driver.callProgram({
library: 'MYLIB',
program: 'SIMPLECALC',
params: [
{ type: '15p0', value: '42', io: 'in' },
{ type: '16p0', value: '0', io: 'out' },
],
});
console.log(result.success); // true
console.log(result.params[0].value); // '4200'driver.callServiceProgram(options): Promise<CallServiceProgramResult>
Calls an exported function in a *SRVPGM object.
const result = await driver.callServiceProgram({
library: 'MYLIB',
program: 'MATHSRV',
func: 'multiply',
params: [{ type: '10i0', value: '6', io: 'in' }],
returnType: { type: '10i0' },
});
console.log(result.returnValue?.value); // '42'PgmParam fields:
| Field | Type | Default | Description |
|---|---|---|---|
| type | string | required | XMLSERVICE type: '10i0', '100a', '15p5', 'ds', … |
| value | string | '' | Serialised value (leave empty for io='out') |
| io | 'in' \| 'out' \| 'both' \| 'omit' | 'both' | Parameter direction |
| by | 'ref' \| 'val' | 'ref' | Pass by reference or value |
| name | string | — | Optional name for documentation |
| fields | PgmParam[] | — | Sub-fields for type='ds' data structures |
| dim | number | — | DS array dimension |
Note: Only
io='out'andio='both'params appear in the XMLSERVICE response.io='in'-only params are dropped by XMLSERVICE after the call.
CL / QSH / SH Commands
driver.runCommand(options): Promise<RunCommandResult>
// CL command (default)
const r1 = await driver.runCommand({ command: 'CRTLIB LIB(TESTLIB)' });
console.log(r1.success); // true
// QSH — IBM i Qshell (native paths)
const r2 = await driver.runCommand({ command: 'ls /home', type: 'qsh' });
console.log(r2.output); // file listing
// PASE sh — AIX userspace (/QOpenSys paths)
const r3 = await driver.runCommand({ command: 'echo hello', type: 'sh' });
console.log(r3.output); // 'hello'
// Return success=false instead of throwing on error
const r4 = await driver.runCommand({ command: 'NOTACMD', error: 'off' });
console.log(r4.success); // false
console.log(r4.error); // error messageSQL Metadata
// List tables in a schema
const tables = await driver.tables({ schema: 'MYLIB' });
// List columns for a table
const columns = await driver.columns({ schema: 'MYLIB', table: 'ORDERS' });
// Primary key columns
const pks = await driver.primaryKeys({ schema: 'MYLIB', table: 'ORDERS' });
// Foreign key relationships
const fks = await driver.foreignKeys({ fkSchema: 'MYLIB', fkTable: 'ORDERS' });
// Stored procedures
const procs = await driver.procedures({ schema: 'MYLIB' });
// Table indexes / statistics
const stats = await driver.statistics({ schema: 'MYLIB', table: 'ORDERS', unique: true });All metadata methods accept optional catalog, schema, and object name filters. Omit a filter to return all.
Batch Execution
Send multiple operations in a single XMLSERVICE round-trip to reduce latency.
driver.batch(): IBMiBatch
const responseXml = await driver.batch()
.addCommand({ command: 'CRTLIB LIB(TMPLIB)' })
.addProgram({
library: 'MYLIB',
program: 'INIT',
params: [{ type: '10a', value: 'TMPLIB', io: 'in' }],
})
.addCommand({ command: 'ls /home', type: 'qsh' })
.execute();| Method | Description |
|---|---|
| .add(xml) | Add a raw XML fragment (<pgm>, <sql>, <cmd>, …) |
| .addProgram(options) | Add a callProgram / callServiceProgram operation |
| .addCommand(options) | Add a runCommand operation |
| .execute() | Send all queued operations and return raw XML |
| .size | Number of operations currently queued |
Raw XMLSERVICE
driver.executeXml(xml): Promise<string>
const xml = await driver.executeXml(`
<pgm name='MYPGM' lib='MYLIB' error='fast'>
<parm><data type='10a'>hello</data></parm>
</pgm>
`);
// xml is the raw XMLSERVICE response stringConfiguration
DriverConfig
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| transport | 'odbc' \| 'https' \| 'http' \| 'ssh' | ✓ | — | Transport to use |
| system | string | ✓ | — | IBM i hostname or IP |
| username | string | ✓ | — | IBM i user profile |
| password | string | ✓ | — | IBM i password |
| odbc | OdbcConfig | — | — | ODBC-specific options |
| http | HttpConfig | — | — | HTTP/HTTPS-specific options |
| ssh | SshConfig | — | — | SSH-specific options |
| reconnect | ReconnectConfig | — | — | Auto-reconnect behaviour |
| xmlserviceLibrary | string | — | 'QXMLSERV' | XMLSERVICE library on IBM i |
| debug | boolean | — | false | Log debug info to console |
OdbcConfig
| Field | Type | Default | Description |
|---|---|---|---|
| connectionString | string | — | Full ODBC connection string — overrides all other options |
| driver | string | 'IBM i Access ODBC Driver' | ODBC driver name |
| nam | 0 \| 1 | 1 | 1 = SQL naming, 0 = system naming |
| ccsid | number | 1208 | Character set (1208 = UTF-8) |
| ssl | boolean | true | SSL/TLS on port 8471 |
| poolSize | number | 5 | Max pool connections |
| idleTimeout | number | 60000 | Idle connection timeout (ms) |
HttpConfig
| Field | Type | Default | Description |
|---|---|---|---|
| port | number | 57700 / 47700 | XMLSERVICE CGI port |
| path | string | '/cgi-bin/xmlcgi.pgm' | CGI endpoint path |
| database | string | value of system | RDB name for db2= param. Set to '*LOCAL' when tunnelling via system: 'localhost' |
SshConfig
| Field | Type | Default | Description |
|---|---|---|---|
| port | number | 22 | SSH port |
| command | string | '/usr/bin/qsh -c /QOpenSys/pkgs/bin/xmlservice-cli' | Command to invoke XMLSERVICE CLI on IBM i |
| privateKey | Buffer \| string | — | SSH private key. When omitted, password auth is used |
| passphrase | string | — | Passphrase for an encrypted private key |
| keepaliveInterval | number | 30000 | SSH keepalive interval (ms) |
SQL Injection Protection
Always use ? placeholders. Never interpolate user values into SQL strings.
// ✅ Safe
const rows = await driver.query(
'SELECT * FROM MYLIB.ORDERS WHERE CUSTID = ?',
[custId],
);
// ❌ Never do this
const rows = await driver.query(
`SELECT * FROM MYLIB.ORDERS WHERE CUSTID = '${custId}'`,
);The driver enforces this at two levels:
- Placeholder count validation — counts
?in the SQL (ignoring?inside string literals) and rejects mismatches before any network call. - Type validation — only safe primitives are accepted:
string,number,boolean,null,Date,Buffer. Objects, arrays,NaN,Infinity, and invalid Dates all throwIBMiQueryParamError.
Error Reference
All errors extend IBMiError which extends Error.
| Error class | When thrown |
|---|---|
| IBMiConnectionError | Cannot reach host, connection lost, SSH auth failed, HTTP error |
| IBMiXmlServiceError | XMLSERVICE returned <error> — .xmlResponse has full context |
| IBMiQueryError | DB2 SQL execution failed at the engine level |
| IBMiQueryParamError | Invalid param type, NaN/Infinity, invalid Date, or ? count mismatch |
| IBMiPayloadTooLargeError | XML payload > 15 MB |
| IBMiOdbcNotAvailableError | odbc package or IBM i Access ODBC Driver not installed |
| IBMiNotConnectedError | Called before connect() |
import {
IBMiConnectionError,
IBMiXmlServiceError,
IBMiQueryParamError,
} from '@openibm/driver';
try {
await driver.query('SELECT * FROM T WHERE ID = ?', [userId]);
} catch (e) {
if (e instanceof IBMiQueryParamError) {
console.error('Bad param:', e.message);
} else if (e instanceof IBMiConnectionError) {
console.error('Network error — check tunnel/VPN');
} else if (e instanceof IBMiXmlServiceError) {
console.error('IBM i error:', e.xmlResponse);
}
}Development
npm run build # compile TypeScript → dist/
npm test # run unit tests (no IBM i required)
npm run typecheck # type-check without emitting
npm run dev # watch modeIntegration tests
Integration tests run against a live IBM i and are in tests/driver-integration/. Set up .env:
IBMI_SYSTEM=localhost
IBMI_USER=myuser
IBMI_PASS=mypass
IBMI_TRANSPORT=http
IBMI_PORT=57700
IBMI_DATABASE=*LOCAL# Start SSH tunnel if IBM i is not directly reachable
ssh -L 57700:localhost:57700 user@ibmi-host -p <port>
# Run tests
npm --filter @openibm/test-driver-integration testLicense
MIT
