Aviary
Concepts

Persistence

The MediaStore contract, how the media table is shaped, and the non-destructive auto-schema convention shared across ORM adapters.

The media-library layer keeps file metadata in a media table. How that table is managed is deliberately consistent across every ORM adapter — this page is the shared model.

The MediaStore contract

Every store implements the same small interface:

interface MediaStore {
  save(record: MediaRecord): Promise<MediaRecord>;
  find(id: string): Promise<MediaRecord | null>;
  listByOwner(ownerType: string, ownerId: string, collection?: string): Promise<MediaRecord[]>;
  delete(id: string): Promise<void>;
  nextOrder(ownerType: string, ownerId: string, collection: string): Promise<number>;
}

listByOwner returns records ordered by order ascending; nextOrder returns the next append position in a collection. That's the whole surface the MediaLibrary needs — which is why a custom store is easy.

Adapters are POJOs

Following the ecosystem's persistence convention, no adapter is a NestJS provider and none cracks open a connection itself. Each is a plain object that receives your ORM handle in its constructor:

new TypeOrmMediaStore(dataSource);
new MikroOrmMediaStore(orm.em);
new DrizzleMediaStore(db);
new PrismaMediaStore(prisma);

You wire them through MediaModule.forRootAsync, injecting whatever connection you already manage — default or named. The library never owns your database lifecycle.

Non-destructive auto-schema

Where the ORM allows it, the table is created on first use without you running a migration — but never destructively:

AdapterAuto-schema
TypeORMensureMediaSchema — create table + add missing columns; never drop/alter. autoCreateSchema: false to opt out
MikroORMupdateSchema({ safe: true }) — same guarantees, native
Drizzlemigration-first (drizzle-kit); createMediaTable helper for dev/test
Prismaconsumer-managed — you own the schema + migrations

Forward-compatible columns

Any column added after v1 is nullable or has a default, so an ADD COLUMN on a populated table never fails. Auto-schema only ever adds — it will not change a column you've customized.

Portability

The order property maps to a position column everywhere (avoiding the reserved word), and customProperties / conversions are JSON columns. Timestamps in the TypeORM adapter are stored as ISO strings so one schema spans sqlite, mysql, and postgres. The behavior of all four adapters is pinned by the shared runMediaStoreConformance suite — in-memory, sqlite, and real Postgres (testcontainers) — so they're genuinely interchangeable.

On this page