Recipes
Writing a custom store
Implement the MediaStore contract for any persistence backend, verified by the shared conformance suite.
The four bundled ORM stores cover the common cases, but the contract is small — back media records with anything (a different ORM, a document store, a service API) by implementing five methods.
The contract
import type { MediaRecord, MediaStore } from '@dudousxd/nestjs-media-core';
export class MyMediaStore implements MediaStore {
constructor(private readonly db: MyDb) {}
async save(record: MediaRecord): Promise<MediaRecord> { /* upsert by id */ return record; }
async find(id: string): Promise<MediaRecord | null> { /* … */ }
async listByOwner(ownerType: string, ownerId: string, collection?: string): Promise<MediaRecord[]> {
/* filter by owner (+ collection if given), ORDER BY order ASC */
}
async delete(id: string): Promise<void> { /* idempotent */ }
async nextOrder(ownerType: string, ownerId: string, collection: string): Promise<number> {
/* MAX(order)+1 in the collection, or 0 */
}
}Semantics that matter
saveis an upsert —MediaLibrarycalls it both to create and to update (when a conversion is added). Key onid.listByOwneris ordered — return records sorted byorderascending; with nocollection, return all of the owner's collections.deleteis idempotent — deleting a missing id is a no-op, not an error.nextOrderreturns the append position —0for an empty collection.
These four behaviors are exactly what the conformance suite checks — they're the assumptions the media-library relies on.
Verify it
import { runMediaStoreConformance } from '@dudousxd/nestjs-media-testing';
import { MyMediaStore } from './my-store';
runMediaStoreConformance('MyMediaStore', () => new MyMediaStore(makeFreshDb()));Pass the suite and your store drops into MediaModule unchanged:
MediaModule.forRoot({ default: 's3', disks: { s3 }, store: new MyMediaStore(db) });For a real backend, also run it as a *.db.spec.ts against a container (the way the ORM stores are validated on Postgres). See -testing and Persistence.