FutoIn Database Interface
futoin-database is a neutral database interface designed for microservices, providing advanced Query and a unique 'revolutionary' Transaction builder. As a reference implementation for FTN17: FutoIn Interface - Database, it focuses on enforcing specific patterns by design, notably lacking direct support for large result sets, cursors, and explicit client-side transaction control to prevent common database anti-patterns. The library leverages a 'single call' transaction pattern, executing multiple statements on the DB side within a single transaction, with back references for sequential query data. It supports auto-configuration through environment variables for connection pooling and works with MySQL, PostgreSQL, and SQLite out of the box. The current stable version is 1.9.12, and it appears to be actively maintained, often with minor version updates. Its core differentiators include its opinionated approach to database interaction and its tight integration with the FutoIn ecosystem, requiring `futoin-asyncsteps`, `futoin-executor`, and `futoin-invoker` as peer dependencies.
Common errors
-
Error: Cannot find module 'futoin-asyncsteps' (or futoin-executor, futoin-invoker)
cause One of the required FutoIn peer dependencies is missing from your project's `node_modules`.fixInstall the missing peer dependencies: `npm install futoin-asyncsteps futoin-executor futoin-invoker`. -
Error: Service 'your_db_alias' is not registered in CCM
cause The `AutoConfig` function was either not called, failed to execute, or the alias provided to `ccm.get()` does not match the one used during configuration.fixVerify that `AutoConfig(as, ccm, config, alias)` is called successfully before `ccm.get(alias)`. Double-check the alias string for consistency and ensure environment variables (like `DB_TYPE`, `DB_PATH`) are correctly set if using auto-configuration. -
TypeError: db.query is not a function
cause The object retrieved from `ccm.get()` is not a valid FutoIn database service instance, or the underlying database driver failed to initialize, resulting in an incomplete service object.fixEnsure `AutoConfig` successfully initializes the database connection and registers the service. Check the console for earlier errors during `AutoConfig` execution. Confirm that the required database driver (e.g., `sqlite3` for SQLite) is correctly installed and accessible by `futoin-database`.
Warnings
- breaking FutoIn database interface fundamentally differs from traditional database APIs by intentionally omitting support for large result sets, cursors, and explicit client-side transaction control. This design choice is to enforce specific operational patterns suitable for microservices and may require significant application refactoring for traditional SQL-based projects.
- gotcha The `futoin-database` package includes optional dependencies for various database drivers (e.g., `sqlite3`, `pg`, `mysql`). If not all database types are used, these optional dependencies might be installed unnecessarily, increasing package size. The documentation recommends installing with `--ignore-optional` to mitigate this.
- breaking `futoin-database` relies heavily on its peer dependencies: `futoin-asyncsteps`, `futoin-executor`, and `futoin-invoker`. These libraries must be installed in the consuming project alongside `futoin-database` for the system to function correctly. Failure to install them will result in runtime errors.
Install
-
npm install futoin-database -
yarn add futoin-database -
pnpm add futoin-database
Imports
- AutoConfig
const AutoConfig = require('futoin-database').AutoConfig;import { AutoConfig } from 'futoin-database'; - QueryBuilder
import QueryBuilder from 'futoin-database';
import { QueryBuilder } from 'futoin-database'; - IDataBaseService
const IDataBaseService = require('futoin-database');import { IDataBaseService } from 'futoin-database';
Quickstart
/*
Installation:
npm install futoin-asyncsteps futoin-executor futoin-invoker futoin-database sqlite3
(sqlite3 is an optional peer dependency for SQLite support that must be explicitly installed if you use it.)
*/
// Set environment variables for a test in-memory SQLite database.
// In a real application, these would be set externally (e.g., .env file, Docker config).
process.env.DB_TYPE = 'sqlite';
process.env.DB_PATH = ':memory:'; // Use an in-memory SQLite DB for quick demonstration
process.env.DB_MAXCONN = '1';
process.env.FUTOIN_DB_ALIAS = 'my_sqlite_db'; // Custom alias for clarity
const AsyncSteps = require('futoin-asyncsteps');
const Executor = require('futoin-executor'); // Required for ContainerConnectionManager
const { AutoConfig } = require('futoin-database');
// Create a FutoIn Container Connection Manager (CCM)
const ccm = new Executor.ContainerConnectionManager();
// Main execution block using AsyncSteps for flow control
AsyncSteps((as) => {
// 1. Auto-configure the database connection using environment variables.
// The configuration object indicates supported types, and the last argument
// is the alias under which the DB service will be registered in CCM.
AutoConfig(as, ccm, {
[process.env.FUTOIN_DB_ALIAS]: { type: ['sqlite'] }
}, process.env.FUTOIN_DB_ALIAS);
as.add((as) => {
console.log(`Database service '${process.env.FUTOIN_DB_ALIAS}' configured.`);
// 2. Get the database service instance from the CCM
const db = ccm.get(process.env.FUTOIN_DB_ALIAS);
// 3. Execute a simple query (Level 1 interface)
console.log('\n--- Executing simple query (Level 1) ---');
db.query(as, 'SELECT ? + ? AS result_value', [10, 20]);
as.add((as, rows) => {
console.log('Query result:', rows);
console.log('10 + 20 =', rows[0].result_value);
// 4. Execute a multi-statement transaction (Level 2 interface)
// This transaction creates a table, inserts data, and then selects it.
// If any step fails, the entire transaction is rolled back.
console.log('\n--- Executing transaction (Level 2) ---');
const tx = [
// Statement 0: Create table if not exists
'CREATE TABLE IF NOT EXISTS sample_users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT)',
// Statement 1: Insert data for User A
{ query: 'INSERT INTO sample_users (name, email) VALUES (?, ?)', params: ['Charlie', 'charlie@example.com'] },
// Statement 2: Insert data for User B
{ query: 'INSERT INTO sample_users (name, email) VALUES (?, ?)', params: ['Diana', 'diana@example.com'] },
// Statement 3: Select data for User A by name
{ query: 'SELECT id, name, email FROM sample_users WHERE name = ?', params: ['Charlie'], expect: 'rows' },
// Statement 4: Select data for User B by email
{ query: 'SELECT id, name, email FROM sample_users WHERE email = ?', params: ['diana@example.com'], expect: 'rows' }
];
db.transaction(as, tx);
as.add((as, results) => {
console.log('Transaction results:');
// 'results' is an array where each element corresponds to the outcome of a statement in 'tx'.
// For 'expect: rows', it will contain an array of row objects.
console.log('Charlie data:', results[3][0]); // Result of statement 3
console.log('Diana data:', results[4][0]); // Result of statement 4
if (results[3][0].name === 'Charlie' && results[4][0].name === 'Diana') {
console.log('Transaction successful: data inserted and retrieved correctly.');
}
});
});
});
},(as, err) => {
console.error('An error occurred during execution:', err.message);
// Ensure connections are released on error
ccm.release();
process.exit(1);
}).whenEnd(() => {
console.log('\nQuickstart execution complete. Releasing resources.');
// Ensure connections are released upon successful completion
ccm.release();
process.exit(0);
});