Skip to content

Custom Adapter

Implement HirokiAdapter to connect any data source.

Minimal example

ts
import type { HirokiAdapter, HirokiQuery, UpdateSet, UpdateConfig, QueryLimits } from 'hiroki';
import type { ValidConditions } from 'hiroki';
import { DocumentNotFoundError } from 'hiroki';

export class MyAdapter implements HirokiAdapter {
  readonly modelName: string;

  constructor(modelName: string) {
    this.modelName = modelName;
  }

  canHandle(resource: unknown): boolean {
    return resource === this.modelName;
  }

  async findById(id: string, query?: HirokiQuery): Promise<unknown> {
    const doc = await myDb.findOne({ id });
    if (!doc) throw new DocumentNotFoundError(404);
    return doc;
  }

  async find(query: HirokiQuery): Promise<unknown[]> {
    return myDb.find(this.mapQuery(query));
  }

  async count(query?: HirokiQuery): Promise<number> {
    return myDb.count(query ? this.mapQuery(query) : {});
  }

  async distinct(field: string): Promise<unknown[]> {
    return myDb.distinct(field);
  }

  async create(data: Record<string, unknown>): Promise<unknown> {
    return myDb.insert(data);
  }

  async updateById(id: string, data: UpdateSet, config?: UpdateConfig): Promise<unknown> {
    const doc = await myDb.findOneAndUpdate({ id }, data);
    if (!doc) throw new DocumentNotFoundError(404);
    return doc;
  }

  async updateByConditions(conditions: ValidConditions | undefined, data: UpdateSet): Promise<unknown> {
    const doc = await myDb.findOneAndUpdate(conditions ?? {}, data);
    if (!doc) throw new DocumentNotFoundError(404);
    return doc;
  }

  async delete(id: string): Promise<unknown> {
    const doc = await myDb.findOneAndDelete({ id });
    if (!doc) throw new DocumentNotFoundError(404);
    return doc;
  }

  private mapQuery(q: HirokiQuery) {
    // translate HirokiFilter[] and HirokiSort[] to your DB's format
    return { filters: q.where ?? [], limit: q.limit, offset: q.offset };
  }
}

Registering via AdapterRegistry

Register a factory so Hiroki auto-resolves your adapter without explicit injection:

ts
import { adapterRegistry } from 'hiroki';
import { MyModel, isMyModel } from './my-model';

adapterRegistry.register((resource) => {
  if (isMyModel(resource)) return new MyAdapter((resource as MyModel).name);
  return null;
});

// Now importModel auto-picks MyAdapter for matching models
hiroki.importModel(someMyModel);

Logger injection

Implement optional setLogger to receive Hiroki's logger:

ts
import type { HirokiLogger } from 'hiroki';

export class MyAdapter implements HirokiAdapter {
  private logger?: HirokiLogger;

  setLogger(logger: HirokiLogger): void {
    this.logger = logger;
  }

  async find(query: HirokiQuery): Promise<unknown[]> {
    this.logger?.debug(`[${this.modelName}] find`);
    // ...
  }
}

Released under the MIT License.