rm-mapepire-js
v0.2.0
Published
Wrapper over the IBM Mapepire DB2 client for Node.js
Maintainers
Readme
rm-mapepire-js
A TypeScript wrapper over the IBM Mapepire DB2 client for Node.js, providing connection pooling and management for IBM i databases.
Why rm-mapepire-js?
The base @ibm/mapepire-js package provides a WebSocket-based DB2 client for IBM i — but it leaves connection lifecycle management, pooling strategy, and production concerns up to you. rm-mapepire-js builds on top of it to deliver a production-ready experience:
Enterprise Connection Pooling — Auto-scaling pools with configurable min/max sizing, on-demand growth, and batch connection creation. The base package has a basic pool; this library adds tiered connection management with separate policies for initial vs. overflow connections.
Automatic Connection Expiry — Idle overflow connections are automatically retired after a configurable timeout, freeing IBM i jobs during low-activity periods. Initial connections can be set to persist indefinitely.
Health Checks with Transparent Retry — Connections are validated before being handed out (
VALUES 1probe). Unhealthy connections are silently retired and replaced, so your application code never sees a stale connection.Thread-Safe Attach — A promise-chain mutex serializes connection checkout, preventing race conditions when multiple callers request connections simultaneously.
Multi-Pool Management — Run up to 8 isolated pools (e.g., production, reporting, batch) under a single
RmPoolsmanager with independent configuration, credentials, and lifecycle.Init Commands — Automatically execute CL commands or SQL statements on every new connection (set library lists, environment variables, job attributes) so connections are ready to use immediately.
Event-Driven Lifecycle —
RmPoolextendsEventEmitterwith 8 lifecycle events (connection:created,connection:expired,pool:exhausted, etc.) for monitoring, metrics, and orchestration.Injectable Logging — Plug in your own logger (Winston, Pino, etc.) via a simple interface. Logs flow hierarchically through the entire class chain with structured service metadata.
Hardened Error Handling — Pool operations are wrapped with contextual error information (pool ID, connection index, job name) and graceful degradation, so a single bad connection doesn't take down your pool.
TypeScript-First — Full type coverage with exported interfaces for all configuration, making misconfiguration a compile-time error rather than a runtime surprise.
Installation
npm install rm-mapepire-jsPrerequisites
This package requires a Mapepire server running on your IBM i. See @ibm/mapepire-js for details.
Install from GitHub (alternative)
# Latest stable release
npm install github:richardm90/rm-mapepire-js#main
# Development branch
npm install github:richardm90/rm-mapepire-js#devUsage
Basic Setup
import { RmPools } from 'rm-mapepire-js';
const poolsConfig = {
debug: true,
activate: true,
pools: [
{
id: 'myPool',
PoolOptions: {
creds: {
host: 'your-host',
user: 'your-user',
password: 'your-password',
rejectUnauthorized: false,
},
maxSize: 20,
initialConnections: {
size: 8,
expiry: 30 // minutes
},
JDBCOptions: {
libraries: "RMDATA"
}
}
}
]
};
const pools = new RmPools(poolsConfig);
await pools.init();Using a Connection
Direct Pool Query (Recommended)
The simplest way to execute queries - the pool automatically handles connection lifecycle:
// Get a pool
const pool = await pools.get('myPool');
// Execute a query directly on the pool (auto attach/detach)
const result = await pool.query('SELECT * FROM MY_TABLE');
// With query options
const result = await pool.query('SELECT * FROM MY_TABLE WHERE id = ?', {
parameters: [123]
});Manual Connection Management
For more control, you can manually attach and detach connections:
// Get a pool
const pool = await pools.get('myPool');
// Attach a connection
const connection = await pool.attach();
// Execute a query
const result = await connection.query('SELECT * FROM MY_TABLE');
// Detach the connection (return to pool)
await pool.detach(connection);Configuration Options
Pool Options
creds: Database credentials object - a standard Mapepire DaemonServer objectmaxSize: Maximum number of connections in the pool (default: 20)initialConnections: Initial connection settingssize: Number of connections to create on initialization (default: 8)expiry: Connection expiry time in minutes (default: null). Set tonullor omit for connections that never expire. A value of0is treated the same asnull(no expiry). Only positive values start an expiry timer.
incrementConnections: Settings for dynamically added connectionssize: Number of connections to add when pool is exhausted (default: 8)expiry: Expiry time for new connections in minutes (same rules as above)
dbConnectorDebug: Enable debug logging (default: false)JDBCOptions: JDBC options object - a standard Mapepire JDBCOptions objectinitCommands: Array of commands to execute when each connection is initialized. Each entry is an object withcommand(string) and optionaltype('cl'or'sql', defaults to'cl'). CL commands are executed viaQCMDEXCwith parameterised input; SQL commands are executed directly viajob.execute()without parameterisation. Security note: SQL-type init commands must be trusted, developer-supplied strings — never pass unsanitised user input as an init command.healthCheck: Health check settingsonAttach: Verify connections are alive before returning fromattach()by executing a lightweight query (VALUES 1). Unhealthy connections are automatically retired and replaced. (default:true). Set tofalseto disable.
logger: Custom logger object (per-pool override, see Logger below)
Pools Options
activate: Auto-activate pools on registration (default: true)debug: Enable debug logging (default: false)pools: Array of pool configurationslogger: Custom logger object implementing theLoggerinterface. Flows down to all pools and connections. Defaults to a built-in console logger.
Logger
All classes accept an optional logger that implements the Logger interface:
interface Logger {
log(level: string, message: string, meta?: any): void;
}The logger flows down from RmPools → RmPool → RmPoolConnection → RmConnection. You can set it at the top level or per-pool:
import { RmPools } from 'rm-mapepire-js';
// Custom logger (e.g. winston, pino, or any object with a log method)
const myLogger = {
log(level, message, meta) {
// Send to your logging infrastructure
console.log(`[${level}] ${message}`, meta);
}
};
const pools = new RmPools({
logger: myLogger, // All pools and connections use this logger
pools: [{ id: 'myPool', PoolOptions: { creds: { ... } } }]
});For standalone connections (without pools):
import { RmConnection } from 'rm-mapepire-js';
const conn = new RmConnection(creds, jdbcOptions, [], false, myLogger);
await conn.init();Quick Start: Winston
Winston's log(level, message, meta) method matches the Logger interface directly, so a Winston logger can be passed in as-is:
import winston from 'winston';
import { RmPools } from 'rm-mapepire-js';
const logger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'db.log' })
]
});
const pools = new RmPools({
debug: true,
logger,
pools: [
{
id: 'myPool',
PoolOptions: { creds: { host: '...', port: 8076, user: '...', password: '...' } }
}
]
});
await pools.init();All pool and connection activity will now flow through Winston — no adapter needed.
API Reference
RmPools
Main class for managing multiple connection pools.
Methods
init(): Initialize all registered poolsregister(poolConfig): Register a new pool configurationget(poolId?): Get a pool by ID (returns first pool if ID not provided)attach(pool): Attach a connection from the poolclose(): Close all active pools and mark them inactiveconnectionDiag(poolId, connection, sql): Log connection diagnosticsgetInfo(): Get information about all pools for debuggingprintInfo(): Print all pools info to consoleprintStats(): Print summary statistics for all pools
RmPool
Manages a pool of database connections. Extends EventEmitter.
Methods
init(): Initialize the pool with initial connectionsquery(sql, opts?): Execute a SQL query using a connection from the pool (automatically handles attach/detach)attach(): Get an available connection from the pooldetach(connection): Return a connection to the poolretire(connection): Remove a connection from the pool permanentlydetachAll(): Return all connections to the poolretireAll(): Remove all connections from the poolclose(): Close all connections in the pool (alias forretireAll())getInfo(): Get detailed pool information for debugginggetStats(): Get pool statistics summaryprintInfo(): Print detailed pool information to consoleprintStats(): Print pool statistics to console
Events
pool:initialized— Pool init complete. Payload:{ poolId, connections }connection:created— New connection added. Payload:{ poolId, poolIndex, jobName }connection:attached— Connection handed to consumer. Payload:{ poolId, poolIndex }connection:detached— Connection returned to pool. Payload:{ poolId, poolIndex }connection:retired— Connection removed from pool. Payload:{ poolId, poolIndex }connection:expired— Expiry timer fired. Payload:{ poolId, poolIndex }connection:healthCheckFailed— Health check failed before retire. Payload:{ poolId, poolIndex }pool:exhausted— Max connections reached. Payload:{ poolId, maxSize }
const pool = await pools.get('myPool');
pool.on('connection:created', ({ poolId, poolIndex, jobName }) => {
console.log(`New connection ${poolIndex} created (job: ${jobName})`);
});
pool.on('pool:exhausted', ({ poolId, maxSize }) => {
console.warn(`Pool ${poolId} exhausted at ${maxSize} connections`);
});RmPoolConnection
Represents a single pooled database connection.
Methods
query(sql, opts?): Execute a SQL querydetach(): Mark the connection as available and return itretire(): Close and retire the connectionisAvailable(): Check if the connection is availableisHealthy(): Check if the underlying connection is still alive (executesVALUES 1)getStatus(): Get the current status of the underlying jobgetInfo(): Get connection information for debuggingprintInfo(): Print connection info to console
RmConnection
Represents a standalone database connection (not pooled).
Methods
init(): Initialize the connection and connect to the databaseexecute(sql, opts?): Execute a SQL statementquery(sql, opts?): Execute a SQL query (alias forexecute)close(): Close the connectiongetStatus(): Get the current status of the underlying jobgetInfo(): Get connection information for debuggingprintInfo(): Print connection info to console
License
ISC
Author
Richard Moulton
Updating to the latest version
npm update rm-mapepire-jsReleasing
This project uses npm version for versioning. Versions follow semver.
# 1. Merge dev into main
git checkout main
git merge dev
# 2. Bump version (choose one)
npm version patch # 0.1.0 → 0.1.1 (bug fixes)
npm version minor # 0.1.0 → 0.2.0 (new features)
npm version major # 0.1.0 → 1.0.0 (breaking changes)
# 3. Push with tags
git push origin main --follow-tags
# 4. Publish to npm
npm publish
# 5. Return to dev
git checkout dev
git merge main
git push origin devUse npm link for Active Development
If you're making frequent changes, npm link is faster:
# In your rm-mapepire-js directory
npm link
# In your consuming project
npm link rm-mapepire-jsNow any changes you make and build in rm-mapepire-js are immediately
available.
When done:
# 1. In your consuming project - removes the symlink
npm unlink rm-mapepire-js
# 2. In your rm-mapepire-js directory - removes the global link
npm unlink
# 3. In your consuming project - reinstall normally
npm install rm-mapepire-js# Check if something is linked, in the consuming project
cd test-rm-mapepire-js
ls -l node_modules/ | grep "^l"