Diagnostics & the glue points
Media emits to nestjs:media:* diagnostics channels with zero coupling; Telescope, Codegen, and React plug in from there.
media is wired into the same four glue points every @dudousxd/nestjs-* library shares. None of them are required to use media — they're how it composes with the rest of the ecosystem when you want it to.
1. Diagnostics channels
Every layer of the library publishes to node:diagnostics_channel with the standard ecosystem envelope. This is the lowest-coupling integration there is: media doesn't import Telescope or know it exists — it just emits, and anything that cares subscribes. When nobody's listening, publishing is a no-op (a cheap hasSubscribers check), so it's always on by default with no measurable cost.
// channel: nestjs:media:<event>
{ ts: number, lib: 'media', event: 'attach', payload: { id, ownerType, ownerId, collection, disk, path, size, mimeType } }Every event
Each event is its own channel (nestjs:media:<event>) carrying a typed payload. The full set (MEDIA_DIAGNOSTIC_EVENTS is exported for convenience):
| Event | Emitted by | Payload |
|---|---|---|
attach | media-library | id, ownerType, ownerId, collection, disk, path, size, mimeType |
delete | media-library | id, ownerType, ownerId |
conversion | media-library | id, conversion, path |
upload.start | resumable uploads | id, disk, key, size?, contentType? |
upload.progress | resumable uploads | id, offset, parts, size? |
upload.complete | resumable uploads | id, disk, key, size |
upload.abort | resumable uploads | id |
attachment.create | attachments | disk, path, size, mimeType, name, variants |
attachment.delete | attachments | disk, path, variants |
This is what makes "do something on upload start / end" a two-line subscription — no hooks API to learn, just the channel:
import { subscribe } from 'node:diagnostics_channel';
import type { MediaDiagnosticEnvelope, UploadCompletePayload } from '@dudousxd/nestjs-media-core';
subscribe('nestjs:media:upload.start', (msg) => {
console.log('upload started', (msg as MediaDiagnosticEnvelope).payload);
});
subscribe('nestjs:media:upload.complete', (msg) => {
const { key, size } = (msg as MediaDiagnosticEnvelope<UploadCompletePayload>).payload;
metrics.observe('media.upload.bytes', size, { key });
});Each emitter takes emitDiagnostics: false to opt out (MediaLibrary, ResumableUploadManager, AttachmentManager). See Diagnostics for the cross-ecosystem convention this follows.
2. Telescope
@dudousxd/nestjs-media-telescope ships a MediaWatcher that subscribes to those channels and records a media entry per event — so attaches, deletes, conversions, uploads, and attachments show up in the Telescope console next to your requests, queries, jobs, and mails, correlated under the same batch. It records every milestone but skips per-chunk upload.progress so a large resumable upload doesn't flood the timeline (progress stays on its channel for your own code).
import { MediaWatcher } from '@dudousxd/nestjs-media-telescope';
// add new MediaWatcher() to your telescope watcher listThat's the entire integration: media emits, the watcher records. No changes to your media code, no coupling between the packages beyond the channel name.
3. Codegen
@dudousxd/nestjs-media-codegen contributes a typed media client to your generated output, so the front-end gets uploadMedia() + mediaUrl() with no hand-written upload protocol. The emitted file is a thin binding over the runtime client (it bakes in your tus base path and re-exports), which is the point of the next section. See Codegen.
4. React
@dudousxd/nestjs-media-react provides useMediaUpload() and <MediaUploader/>, built on the same resumable client — progress, status, and resume handled for you.
One protocol, many surfaces
The tus client lives exactly once, in @dudousxd/nestjs-media-client. The React hook wraps it; the codegen extension re-exports it. There is a single implementation of the wire protocol, so a protocol change is a one-file change — the front-end surfaces follow automatically.
Why this shape
Every glue point here is opt-in and decoupled. media is useful with none of them (it's a storage + media library on its own), better with each of them, and never forced to depend on Telescope, the codegen runtime, or React to function. That's the ecosystem contract: libraries emit on shared channels and ship satellite packages, so you assemble exactly the surface you need.
Attachments (column model)
The adonis-attachment-style persistence model — a file stored as a JSON value object on your own entity's column, instead of in a separate media table. When to reach for it, the Attachment value object, the AttachmentManager API, and how each ORM serializes it.
Overview
The full nestjs-media package set — core, the NestJS module, the browser client, disks, ORM stores, the image engine, and the ecosystem glue points.