Control
Raw SQL stays first-class. You can drop to sql, ref, and compiled queries without fighting the ORM.
Modern ORM and query builder
Inspired by Objection.js, rebuilt around an embedded SQL engine, typed graph operations, official SQLite/Postgres/MySQL drivers, codegen, plugins, and a production-ready NestJS module.
defineModelcreateSnakeCaseNamingPlugin() for camelCase over snake_case schemascreateSnakeCaseNamingStrategy() for session-wide physical naminginsertGraph, upsertGraph, eager loadingRaw SQL stays first-class. You can drop to sql, ref, and compiled queries without fighting the ORM.
Use eager loading, insertGraph, upsertGraph, relate, and unrelate with the same session model.
ExecutionContextManager carries transaction scope, tenant ids, actor ids, request metadata, and plugin state.
Real migrations, seeds, code generation, starter templates, validation adapters, and framework integration already ship as packages.
Install
Switch between the main installation presets. The rest of the API stays the same.
Typed models
OBJX treats schema metadata as runtime truth and type signal at the same time. Defaults can be static or factory-based, generated fields stay out of required insert payloads, relation columns stay explicit, and naming plugins can keep models in camelCase even when the database stays in snake_case.
col.bigint() and col.bigInt() for bigint columns.default(value) and .default(() => value).generated() for runtime-populated fields like tenant ids and graph-owned foreign keyscreateSnakeCaseNamingPlugin() remaps physical columns during model definitioncreateSnakeCaseNamingStrategy() keeps physical naming configurable per sessionbelongsToOne, hasOne, hasMany, manyToManyimport { col, defineModel, hasMany } from '@qbobjx/core';
import { createSnakeCaseNamingPlugin } from '@qbobjx/plugins';
const generateSnowflakeId = () => BigInt(Date.now());
export const Project = defineModel({
name: 'Project',
table: 'projects',
columns: {
id: col.bigInt().primary().default(() => generateSnowflakeId()),
tenantId: col.text().generated(),
name: col.text(),
status: col.text().default('planned'),
deletedAt: col.timestamp().nullable(),
},
relations: (project) => ({
tasks: hasMany(() => Task, {
from: project.columns.id,
to: Task.columns.projectId,
}),
}),
plugins: [createSnakeCaseNamingPlugin()],
});
const rows = await executionContextManager.run(
{
values: {
tenantId: 'demo',
actorId: 'user_admin',
},
},
() =>
session.transaction(async (trx) => {
await trx.execute(
Project.insert({
name: 'Ship docs',
status: 'planned',
}),
);
return trx.execute(
Project.query()
.where(({ status }, op) => op.eq(status, 'planned'))
.withRelated({
tasks: true,
}),
{ hydrate: true },
);
}),
);
createSqliteSession, createPostgresSession, and
createMySqlSession expose the same execution model.
Every official driver supports session.transaction(...) and nested
transactions where savepoints are available.
Dates, booleans, json payloads, and bigint values can be hydrated automatically from query results.
Register plugins globally in the session or per model to compose tenant scope, audit, validation, timestamps, and soft delete behavior.
Operational workflow
The recommended path is explicit migrations and seeds. Use templates to scaffold the folders, then run the same commands across SQLite, Postgres, or MySQL.
Generate starter migration and seed schemas.
Run up and down versions explicitly per dialect.
Load initial data and revert it with tracked seed history.
Introspect and generate model output when you need schema-driven bootstrapping.
npm run codegen -- template --template migration-seed-schemas --out ./db
npm run codegen -- migrate --dialect postgres --database "$DATABASE_URL" --dir ./db/migrations --direction up
npm run codegen -- seed --dialect postgres --database "$DATABASE_URL" --dir ./db/seeds --direction run
db/migrations/*.migration.mjsdb/seeds/*.seed.mjsnpm run db:migrate before app startnpm run db:seed for dev and demo dataFramework integration
@qbobjx/nestjs ships a dynamic module, session injection helpers,
request-context interception, validation mapping, and shutdown hooks for drivers that
need cleanup.
ObjxModule.forRoot(...) and ObjxModule.forRootAsync(...)@InjectObjxSession() for clean session injectionx-tenant-idObjxValidationError@Module({
imports: [
ObjxModule.forRootAsync({
global: true,
inject: [AuditTrailStore],
useFactory: (auditTrailStore) => ({
session: createSqliteSession({
driver: createSqliteDriver({
databasePath: './data/app.sqlite',
pragmas: ['foreign_keys = on'],
}),
executionContextManager: createExecutionContextManager(),
hydrateByDefault: true,
plugins: [
createTenantScopePlugin(),
createSoftDeletePlugin(),
createAuditTrailPlugin({
actorKey: 'actorId',
emit: (entry) => auditTrailStore.append(entry),
}),
],
}),
requestContext: {
enabled: true,
},
}),
}),
],
})
export class AppModule {}
Repository examples
The repository already includes end-to-end examples for runtime behavior, APIs, and tooling.
Packages
Install only what you need or compose the whole stack.
pg pools and clients.
@qbobjx/mysql-driver
MySQL driver compatible with mysql2/promise.
@qbobjx/plugins
Snake case naming, tenant scope, timestamps, soft delete, and audit trail.
@qbobjx/validation
Validation adapters and runtime contracts for inserts, updates, and graphs.
@qbobjx/codegen
Introspection, generation, templates, migrations, and seeds.
@qbobjx/nestjs
Dynamic module, request context, validation filter, and DI helpers.
GitHub Pages
This documentation lives directly in the pages/ folder and is deployed by
a dedicated GitHub Actions workflow.
pages/.main or master.pages/ and deploys it as the site artifact.