@reldens/storage Driver Methods

Complete reference for the driver abstraction layer in @reldens/storage: what problem it solves and the full set of methods every driver must implement.

The Abstraction Layer

The @reldens/storage package provides a UNIFIED API across six drivers:

  • Knex - SQL query builder, the default driver and the only one bundled with the package.
  • Kysely - type safe SQL query builder (optional).
  • Drizzle - TypeScript ORM (optional).
  • ObjectionJS - built on Knex.js (optional).
  • MikroORM - for MongoDB / NoSQL support (optional).
  • Prisma - schema-first ORM (optional).

The optional drivers expect their packages installed in the project, passed to the data server through its [driver]Modules option. See @reldens/storage Prisma Setup for the Prisma driver.

Write once, run anywhere:

// Application code - SAME for all drivers
let category = await categoriesRepo.create({name: 'Electronics', slug: 'electronics'});
let products = await productsRepo.loadWithRelations({category_id: category.id}, ['related_reviews']);
let count = await categoriesRepo.count({is_active: 1});

No matter which driver is configured (knex, kysely, drizzle, objection-js, mikro-orm or prisma), the code above works identically.

Without Abstraction

Each ORM has different method names, parameter structures, query syntax, relation loading syntax, and filter operators:

// ObjectionJS
let category = await Category.query().insert({name: 'Electronics'});
let products = await Product.query().where('category_id', category.id).withGraphFetched('related_reviews');

// MikroORM
let category = await em.create(Category, {name: 'Electronics'});
await em.flush();
let products = await em.find(Product, {category_id: category.id}, {populate: ['related_reviews']});

// Prisma
let category = await prisma.category.create({data: {name: 'Electronics'}});
let products = await prisma.product.findMany({where: {category_id: category.id}, include: {related_reviews: true}});

The Contract

Every driver MUST implement:

  1. Same method signatures - same method names, same parameters.
  2. Same behaviour - same inputs produce same outputs.
  3. Same return format - results structure is identical.
  4. Driver-agnostic code - application code doesn't know which driver is used.

The BaseDriver class defines this contract (lib/base-driver.js).

Public Methods - The Complete API

CREATE operations

  • create(params) - create single record
  • createWithRelations(params, relations) - create record with nested relations

READ operations (no relations)

  • loadAll() - load all records (no filters, respects limit / offset / sort)
  • load(filters) - load records by filters (respects limit / offset / sort)
  • loadBy(field, fieldValue, operator) - load records by single field
  • loadById(id) - load single record by primary key
  • loadByIds(ids) - load multiple records by an IDs array
  • loadOne(filters) - load first record matching filters
  • loadOneBy(field, fieldValue, operator) - load first record by single field

READ operations (with relations)

  • loadAllWithRelations(relations) - load all records with relations
  • loadWithRelations(filters, relations) - load with filters and relations
  • loadByWithRelations(field, fieldValue, relations, operator) - load by field with relations
  • loadByIdWithRelations(id, relations) - load by ID with relations
  • loadOneWithRelations(filters, relations) - load first record with relations
  • loadOneByWithRelations(field, fieldValue, relations, operator) - load first by field with relations

UPDATE operations

  • update(filters, updatePatch) - update records by filters
  • updateBy(field, fieldValue, updatePatch, operator) - update records by single field
  • updateById(id, params) - update record by ID
  • upsert(params, filters) - update if the record exists (by ID or by filters), create otherwise

DELETE operations

  • delete(filters) - delete records by filters
  • deleteById(id) - delete single record by ID

COUNT operations

  • count(filters) - count records matching filters
  • countWithRelations(filters, relations) - count records with relation filters

UTILITY operations

  • rawQuery(content) - execute a raw SQL query (implemented by the data server)
  • executeCustomQuery(methodName, methodOptions) - execute a custom model method
  • isJsonField(fieldName) - check if a field is JSON type
  • parseRelationsString(relationsString) - parse a comma separated relations string to an array

PROPERTY accessors

  • databaseName(), id(), name(), tableName(), property(propertyName)

CONFIGURATION properties

  • limit - result limit (0 = no limit)
  • offset - result offset
  • sortBy - sort field name
  • sortDirection - sort direction (ASC or DESC)
  • select - fields to select (array)

Driver Implementations

  • KnexDriver (lib/knex/knex-driver.js) - uses the Knex query builder. Default driver.
  • KyselyDriver (lib/kysely/kysely-driver.js) - uses the Kysely query builder.
  • DrizzleDriver (lib/drizzle/drizzle-driver.js) - uses Drizzle ORM.
  • ObjectionJsDriver (lib/objection-js/objection-js-driver.js) - uses the Objection query builder on top of Knex.
  • MikroOrmDriver (lib/mikro-orm/mikro-orm-driver.js) - uses MikroORM EntityManager.
  • PrismaDriver (lib/prisma/prisma-driver.js) - uses typed Prisma Client.

The Knex, Kysely and Drizzle drivers extend QueryBuilderDriver (lib/query-builder-driver.js), which implements most of the API once on top of five primitives (insertRow(), selectRows(), update(), delete(), count()). They load relations with one extra query per relation level through RelationsLoader, and turn relation filters into IN (SELECT ...) sub queries, so counts are never inflated by joins.

Filter Syntax

Every driver accepts the same filter objects:

// WHERE field = value
{field: value}

// WHERE field > 10 (operators: GT, GTE, LT, LTE, NE, EQ)
{field: {operator: 'GT', value: 10}}

// WHERE field IN (1, 2, 3)
{field: {operator: 'IN', value: [1, 2, 3]}}

// WHERE field LIKE '%pattern%'
{field: {operator: 'LIKE', value: 'pattern'}}

// WHERE field1 = value1 OR field2 = value2
{OR: [{field1: value1}, {field2: value2}]}

// WHERE field1 = value1 AND field2 = value2
{AND: [{field1: value1}, {field2: value2}]}

Operators are case-insensitive (converted to uppercase). NOT is also supported, AND / OR conditions can be nested, and LIKE wraps the value as %value% (the Knex and ObjectionJS drivers cast JSON columns to text for it). The base BaseDriver.operatorsMap maps GT, GTE, LT, LTE, NE and EQ to their SQL operators, and each driver translates the filters to its own syntax internally.

Related Documentation

Go Up