Aviary
Concepts

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):

EventEmitted byPayload
attachmedia-libraryid, ownerType, ownerId, collection, disk, path, size, mimeType
deletemedia-libraryid, ownerType, ownerId
conversionmedia-libraryid, conversion, path
upload.startresumable uploadsid, disk, key, size?, contentType?
upload.progressresumable uploadsid, offset, parts, size?
upload.completeresumable uploadsid, disk, key, size
upload.abortresumable uploadsid
attachment.createattachmentsdisk, path, size, mimeType, name, variants
attachment.deleteattachmentsdisk, 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 list

That'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.

On this page