Skip to content

Adapters

The adapter layer isolates connection lifecycle, query execution, transactions, and health checks from the ORM core. The core package has zero database driver dependencies.

Each connection source is defined with defineConnectionSource() and registered in defineJsormConfig():

// jsorm.config.ts
import { defineConnectionSource, defineJsormConfig } from 'jsorm';
import { pgAdapter } from 'jsorm-pg';
const main = defineConnectionSource({
adapter: pgAdapter({
name: 'main',
connectionString: process.env.DATABASE_URL!,
pool: {
min: 2,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
},
}),
});
export default defineJsormConfig({
connectionSources: { main },
defaults: { connectionSource: 'main' },
});

Requires pg peer dependency:

Terminal window
pnpm add jsorm-pg pg

Define multiple named connection sources and target them with jsorm.use() or jsorm.with():

// jsorm.config.ts
import { defineConnectionSource, defineJsormConfig } from 'jsorm';
import { pgAdapter } from 'jsorm-pg';
import { mysqlAdapter } from 'jsorm-mysql';
import { sqliteAdapter } from 'jsorm-sqlite';
const main = defineConnectionSource({
adapter: pgAdapter({ name: 'main', connectionString: process.env.DATABASE_URL! }),
});
const analytics = defineConnectionSource({
adapter: mysqlAdapter({
name: 'analytics',
host: process.env.ANALYTICS_HOST!,
user: process.env.ANALYTICS_USER!,
password: process.env.ANALYTICS_PASSWORD!,
database: process.env.ANALYTICS_DB!,
}),
});
const cache = defineConnectionSource({
adapter: sqliteAdapter({ name: 'cache', file: ':memory:' }),
});
export default defineJsormConfig({
connectionSources: { main, analytics, cache },
defaults: { connectionSource: 'main' },
});

Target a specific source at runtime:

import { jsorm } from 'jsorm';
// use() returns a scoped client (no global mutation)
const rows = await jsorm.use('analytics').get(Event, {
select: { id: true, type: true },
pagination: { perPage: 100, currentPage: 1 },
});
// with() scopes a callback and releases automatically
await jsorm.with('cache', async (cacheDb) => {
await cacheDb.executeSql('SELECT 1');
});

Use jsorm.healthCheck() to verify adapter connectivity — useful in readiness probes:

const health = await jsorm.healthCheck();
// { main: 'ok', analytics: 'ok', cache: 'ok' }

Also available via CLI:

Terminal window
jsorm db:check
await jsorm.transaction(async (tx) => {
await tx.insert(User, {
name: 'Alice',
createdAt: new Date(),
role: { connect: 1 },
});
await tx.update(Role, {
data: { name: 'member' },
where: { id: { eq: 1 } },
});
// rolls back automatically if either throws
});

If your application already manages its own pool or connection lifecycle, implement the createPooledAdapter contract:

import {
defineConnectionSource,
defineJsormConfig,
jsorm,
createPooledAdapter,
type DBRow,
} from 'jsorm';
import { Pool, type PoolClient } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const toPostgresSql = (sql: string) =>
sql.replace(/\?/g, (_, i) => `$${++i}`);
const adapter = createPooledAdapter<Pool, PoolClient>({
name: 'main',
connect: async () => pool,
disconnect: async (p) => p.end(),
getConnection: async (p) => p.connect(),
releaseConnection: async (c) => c.release(),
query: async (c, sql, params): Promise<readonly DBRow[]> => {
const result = await c.query(toPostgresSql(sql), [...params]);
return result.rows as readonly DBRow[];
},
transaction: async (p, run) => {
const c = await p.connect();
try {
await c.query('BEGIN');
const result = await run(c);
await c.query('COMMIT');
return result;
} catch (e) {
await c.query('ROLLBACK');
throw e;
} finally {
c.release();
}
},
metadata: {
dialect: 'postgres',
driver: 'pg',
packageName: 'custom',
peerDependencies: ['pg'],
capabilities: { pooling: true, transactions: true, healthCheck: true },
},
});
const main = defineConnectionSource({ adapter });
export default defineJsormConfig({
connectionSources: { main },
defaults: { connectionSource: 'main' },
});
  • Adapters are lazy-initialized on the first query — pools do not open until used.
  • Pools are reused across queries in the same process.
  • Call jsorm.close() during graceful shutdown to drain all active pools.
  1. Never create a new adapter per request — always reuse.
  2. Use jsorm.use('name') for occasional cross-database reads; use jsorm.with('name', fn) for scoped callback patterns.
  3. Use jsorm.healthCheck() in readiness endpoints and deploy verification.
  4. Keep JSORM_DEBUG=1 enabled in local development to log config resolution and pool events.