Skip to content

Adapters

@tsed/adapters provides a generic persistence abstraction used by Ts.ED modules (for example OIDC provider stores).

Installation

sh
npm install --save @tsed/adapters
sh
yarn add @tsed/adapters
sh
pnpm add @tsed/adapters
sh
bun add @tsed/adapters

How It Works

@tsed/adapters separates domain logic from storage implementation:

  • Your feature code targets the Adapter contract (create, findOne, update, delete, ...)
  • Ts.ED resolves an adapter implementation at runtime via Adapters.invokeAdapter(...)
  • The same domain flow can switch from memory to file to Redis without changing business logic

Basic Usage

typescript
import {Adapter, AdapterModel, InjectAdapter} from "@tsed/adapters";
import {Injectable} from "@tsed/di";
import {Property} from "@tsed/schema";

class UserModel implements AdapterModel {
  @Property()
  _id: string;

  @Property()
  name: string;
}

@Injectable()
export class MyService {
  @InjectAdapter({
    collectionName: "users",
    model: UserModel
  })
  protected adapter: Adapter<UserModel>;

  async getItems() {
    return this.adapter.findAll();
  }

  async getItemById(id: string) {
    return this.adapter.findById(id);
  }
}

Available Adapters

AdapterPackage (import from)BackendTypical use
MemoryAdapter@tsed/adaptersIn-memory MapUnit tests, local dev
LowDbAdapter@tsed/adapterslowdb JSON databaseSimple local persistence
FileSyncAdapter@tsed/adaptersFile sync storageDeterministic local storage
RedisAdapter@tsed/adapters-redisRedis (@tsed/redis / node-redis)Production Redis persistence
OIDCRedisAdapter@tsed/adapters-redisRedis (@tsed/redis / node-redis)OIDC Redis persistence
OIRedisAdapter@tsed/adapters-ioredisRedis (@tsed/ioredis / ioredis)Production Redis persistence
OIDCIORedisAdapter@tsed/adapters-ioredisRedis (@tsed/ioredis / ioredis)OIDC Redis persistence

Implement Your Own Adapter

Create a class extending Adapter<TModel> and implement all required CRUD methods:

typescript
import {Adapter, AdapterConstructorOptions, AdapterModel} from "@tsed/adapters";
import {Opts} from "@tsed/di";

export interface MyAdapterOptions extends AdapterConstructorOptions {
  connectionName?: string;
}

export class MyAdapter<Model extends AdapterModel> extends Adapter<Model> {
  constructor(@Opts options: MyAdapterOptions) {
    super(options);
  }

  async create(value: Partial<Omit<Model, "_id">>, expiresAt?: Date): Promise<Model> {
    // persist and return created entity
    throw new Error("Not implemented");
  }

  async upsert(id: string, value: Model, expiresAt?: Date): Promise<Model> {
    throw new Error("Not implemented");
  }

  async update(id: string, value: Model, expiresAt?: Date): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }

  async updateOne(predicate: Partial<Model>, value: Partial<Model>, expiresAt?: Date): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }

  async findOne(predicate: Partial<Model>): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }

  async findById(id: string): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }

  async findAll(predicate: Partial<Model> = {}): Promise<Model[]> {
    throw new Error("Not implemented");
  }

  async deleteOne(predicate: Partial<Model>): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }

  async deleteMany(predicate: Partial<Model>): Promise<Model[]> {
    throw new Error("Not implemented");
  }

  async deleteById(id: string): Promise<Model | undefined> {
    throw new Error("Not implemented");
  }
}

Use a Custom Adapter

typescript
import {Adapter, Adapters, AdapterModel} from "@tsed/adapters";
import {Injectable} from "@tsed/di";
import {Property} from "@tsed/schema";

class User implements AdapterModel {
  @Property()
  _id: string;

  @Property()
  email: string;

  @Property()
  displayName: string;
}

@Injectable()
export class UsersRepository {
  private usersStore: Adapter<User>;

  constructor(private adapters: Adapters) {
    this.usersStore = this.adapters.invokeAdapter<User>({
      adapter: MyAdapter,
      collectionName: "users",
      model: User
    });
  }

  async createUser(input: Pick<User, "email" | "displayName">) {
    return this.usersStore.create(input);
  }

  async getUsers() {
    return this.usersStore.findAll();
  }

  async getUserById(id: string) {
    return this.usersStore.findById(id);
  }

  async updateUser(id: string, patch: Partial<User>) {
    const current = await this.usersStore.findById(id);
    if (!current) {
      return undefined;
    }

    return this.usersStore.update(id, {...current, ...patch});
  }

  async deleteUser(id: string) {
    return this.usersStore.deleteById(id);
  }
}

You can also use @InjectAdapter(...) when injection is preferable to manual invocation.

Released under the MIT License.