
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Minimal, lightweight ORM for PostgreSQL with dependency injection, fluent API, and TypeScript support.
A production-ready query builder designed with SOLID principles: clean architecture, decoupled components, and extensibility through dependency injection.
✨ Clean Architecture - Decoupled components using dependency injection
🔄 Fluent API - Chainable methods for elegant query building
🛡️ Type-Safe - Full TypeScript support with strict types
⚡ Async/Parallel - Native Promise support with parallel query execution
🔌 Extensible - Implement custom compilers and executors
📊 Parameterized Queries - Protection against SQL injection
🎯 Fail-First - Immediate validation with clear error messages
npm install pipehood
import { QueryBuilderFactory, PostgresCompiler, SupabaseExecutor } from 'own-orm';
import postgres from 'postgres';
// Initialize database connection
const db = postgres({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});
// Create factory with dependencies
const compiler = new PostgresCompiler();
const executor = new SupabaseExecutor(db);
const factory = new QueryBuilderFactory(compiler, executor);
// Build and execute queries
const result = await factory.create()
.table('usuarios')
.select(['id', 'nombre', 'email'])
.whereEq('activo', true)
.orderBy('nombre', 'ASC')
.limit(10)
.execute();
console.log(result);
const qb = factory.create();
// SELECT
const usuarios = await qb.table('usuarios')
.select(['id', 'nombre', 'email'])
.execute();
// WHERE with equality
const activos = await qb.table('usuarios')
.select(['*'])
.whereEq('estado', 'Activo')
.execute();
// WHERE with IN clause
const pedidos = await qb.table('pedidos')
.select(['*'])
.whereIn('estado', ['Pendiente', 'En tránsito'])
.execute();
// Raw WHERE clause
const custom = await qb.table('pedidos')
.select(['*'])
.whereRaw('monto > ? AND monto < ?', [50, 500])
.execute();
// Ordering and Limiting
const paginated = await qb.table('usuarios')
.select(['*'])
.orderBy('fecha_creacion', 'DESC')
.limit(10)
.offset(0)
.execute();
Execute multiple queries concurrently for better performance:
// Run 3 queries in parallel instead of sequentially
const [usuarios, pedidos, clientes] = await Promise.all([
factory.create()
.table('usuarios')
.select(['*'])
.execute(),
factory.create()
.table('pedidos')
.select(['*'])
.execute(),
factory.create()
.table('clientes')
.select(['*'])
.execute(),
]);
// Results are typically 20-30% faster than sequential
Create query variants without mutations:
const baseQuery = factory.create()
.table('pedidos')
.select(['*'])
.whereEq('estado', 'Pendiente');
// Create variations safely
const query1 = baseQuery.clone().orderBy('fecha', 'ASC').limit(10);
const query2 = baseQuery.clone().orderBy('monto', 'DESC').limit(5);
// Both execute independently
const [result1, result2] = await Promise.all([
query1.execute(),
query2.execute(),
]);
Complete Create, Read, Update, Delete support:
// CREATE (INSERT)
const newUser = await factory.create()
.table('usuarios')
.insert({ nombre: 'Juan', email: 'juan@example.com', activo: true })
.execute();
// READ (SELECT) - covered above
// UPDATE
const updated = await factory.create()
.table('usuarios')
.update({ nombre: 'María', activo: false })
.whereEq('id', 1)
.execute();
// DELETE (requires WHERE for safety)
const deleted = await factory.create()
.table('usuarios')
.whereEq('id', 1)
.delete()
.execute();
Support for INNER, LEFT, RIGHT, FULL, and CROSS JOINs:
// INNER JOIN
const usersWithOrders = await factory.create()
.table('usuarios u')
.select(['u.id', 'u.nombre', 'o.id_pedido', 'o.monto'])
.innerJoin('pedidos o', 'u.id = o.id_usuario')
.execute();
// LEFT JOIN - all users even without orders
const allUsers = await factory.create()
.table('usuarios u')
.select(['u.id', 'u.nombre', 'COUNT(o.id_pedido) as total_pedidos'])
.leftJoin('pedidos o', 'u.id = o.id_usuario')
.groupBy('u.id', 'u.nombre')
.execute();
// Multiple JOINs
const complex = await factory.create()
.table('pedidos p')
.select(['p.id_pedido', 'u.nombre as usuario', 'c.nombre as cliente'])
.innerJoin('usuarios u', 'p.id_usuario = u.id')
.leftJoin('clientes c', 'p.id_cliente = c.id')
.whereEq('p.estado', 'completado')
.execute();
Aggregate data and filter on group conditions:
// GROUP BY with aggregation
const stats = await factory.create()
.table('pedidos')
.select(['estado', 'COUNT(*) as cantidad', 'SUM(monto_total) as total'])
.groupBy('estado')
.orderBy('total', 'desc')
.execute();
// GROUP BY with HAVING filter
const highVolume = await factory.create()
.table('pedidos')
.select(['id_usuario', 'COUNT(*) as num_pedidos', 'SUM(monto_total) as total'])
.groupBy('id_usuario')
.havingRaw('COUNT(*) > ?', 5)
.orderBy('num_pedidos', 'desc')
.execute();
table(name: string): thisSet the table to query from.
qb.table('usuarios')
select(columns: string[]): thisSelect specific columns. Use ['*'] for all columns.
qb.select(['id', 'nombre', 'email'])
qb.select(['*']) // Select all
whereEq(column: string, value: unknown): thisAdd an equality condition.
qb.whereEq('estado', 'Activo')
qb.whereEq('edad', 25)
whereIn(column: string, values: unknown[]): thisAdd an IN condition for multiple values.
qb.whereIn('estado', ['Activo', 'Pendiente'])
whereRaw(sql: string, params: unknown[]): thisAdd a raw SQL condition with parameters.
qb.whereRaw('edad > ? AND edad < ?', [18, 65])
orderBy(column: string, direction: 'ASC' | 'DESC'): thisAdd ordering.
qb.orderBy('nombre', 'ASC')
qb.orderBy('fecha_creacion', 'DESC')
limit(n: number): thisSet result limit.
qb.limit(10)
offset(n: number): thisSet result offset for pagination.
qb.offset(20) // Skip first 20 rows
clone(): QueryBuilderCreate an independent copy of the query builder.
const query2 = query1.clone().limit(5)
insert(values: Record<string, unknown>): thisInsert a new record. Returns the query builder for chaining.
qb.insert({ nombre: 'Juan', email: 'juan@example.com', activo: true })
update(values: Record<string, unknown>): thisUpdate records. Must be combined with where().
qb.update({ nombre: 'María', activo: false }).whereEq('id', 1)
delete(): thisDelete records. Requires where() clause for safety.
qb.whereEq('id', 1).delete()
innerJoin(table: string, on: string): this (v1.2.0)Add an INNER JOIN clause.
qb.innerJoin('pedidos p', 'usuarios.id = p.id_usuario')
leftJoin(table: string, on: string): this (v1.2.0)Add a LEFT JOIN clause.
qb.leftJoin('pedidos p', 'usuarios.id = p.id_usuario')
rightJoin(table: string, on: string): this (v1.2.0)Add a RIGHT JOIN clause.
qb.rightJoin('pedidos p', 'usuarios.id = p.id_usuario')
fullJoin(table: string, on: string): this (v1.2.0)Add a FULL JOIN clause.
qb.fullJoin('pedidos p', 'usuarios.id = p.id_usuario')
crossJoin(table: string): this (v1.2.0)Add a CROSS JOIN clause (Cartesian product).
qb.crossJoin('clientes')
groupBy(...columns: string[]): this (v1.2.0)Add GROUP BY clause for aggregation.
qb.groupBy('estado', 'tipo')
havingEq(column: string, value: unknown): this (v1.2.0)Add a HAVING equality condition for filtered aggregations.
qb.havingEq('COUNT(*)', 5)
havingRaw(sql: string, ...params: unknown[]): this (v1.2.0)Add a raw HAVING condition with parameters.
qb.havingRaw('SUM(monto) > ?', 1000)
execute(): Promise<T[]>Execute the query and return results.
const results = await qb.execute()
QueryBuilderFactory
├── ICompiler (PostgresCompiler)
│ └── Converts QueryState → SQL string
└── IExecutor (SupabaseExecutor)
└── Executes compiled SQL → Results
Components are loosely coupled through interfaces:
// QueryBuilder depends on abstractions, not concrete implementations
class QueryBuilder {
constructor(
private compiler: ICompiler,
private executor: IExecutor
) {}
}
// Easy to swap implementations
const customExecutor = new LoggingExecutor(new SupabaseExecutor(db));
const factory = new QueryBuilderFactory(compiler, customExecutor);
Implement your own executor for logging, caching, or custom logic:
import { IExecutor } from 'own-orm';
class LoggingExecutor implements IExecutor {
constructor(private executor: IExecutor) {}
async execute(sql: string, params: unknown[]): Promise<unknown[]> {
console.log('SQL:', sql);
console.log('Params:', params);
const result = await this.executor.execute(sql, params);
console.log('Result:', result);
return result;
}
}
// Use it
const executor = new LoggingExecutor(new SupabaseExecutor(db));
const factory = new QueryBuilderFactory(compiler, executor);
Create a .env file in your project root:
DB_HOST=aws-1-us-east-2.pooler.supabase.com
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres.your-project-id
DB_PASSWORD=your-secure-password
Then load it in your application:
import * as dotenv from 'dotenv';
dotenv.config();
Promise.all()limit() and offset() for large result setsRun included examples to verify functionality:
# Basic connection test
npm run verify
# Run examples
npm run example:connection
npm run example:async
npm run example:complete
All queries are fully typed:
interface User {
id: number;
nombre: string;
email: string;
activo: boolean;
}
const usuarios = await factory.create()
.table('usuarios')
.select(['id', 'nombre', 'email'])
.execute() as User[];
// TypeScript knows about all properties
console.log(usuarios[0].nombre);
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT © 2024 Joshua Villegas
FAQs
simple y minimalista query builder para pgsql
We found that pipehood demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.