First Contract Integration
This tutorial walks through a first integration of @ddgutierrezc/legato-contract in a plain TypeScript app. You will model Track, QueueSnapshot, and PlaybackSnapshot, then read event payload shapes without importing any Capacitor package.
Prerequisites
Section titled “Prerequisites”- Node.js 18+ and npm.
- A TypeScript project (or a new empty folder).
- Basic familiarity with TypeScript interfaces and unions.
Install the package
Section titled “Install the package”-
Install the contract package.
Terminal window npm install @ddgutierrezc/legato-contract -
Import contract types and constants.
import {LEGATO_EVENT_NAMES,type LegatoEventPayload,type PlaybackSnapshot,type QueueSnapshot,type Track,} from '@ddgutierrezc/legato-contract';
1) Model a Track
Section titled “1) Model a Track”Start with a minimal track, then enrich it as your integration grows.
Minimal track
Section titled “Minimal track”import type { Track } from '@ddgutierrezc/legato-contract';
const introTrack: Track = { id: 'ep-001', url: 'https://cdn.example.com/audio/episode-001.mp3',};Metadata-rich and transport-aware track
Section titled “Metadata-rich and transport-aware track”import type { Track } from '@ddgutierrezc/legato-contract';
const introTrack: Track = { id: 'ep-001', url: 'https://cdn.example.com/audio/episode-001.mp3', title: 'Episode 001', artist: 'Legato Team', album: 'Season 1', artwork: 'https://cdn.example.com/artwork/ep-001.jpg', duration: 180, type: 'progressive', headers: { Authorization: 'Bearer static-token-for-demo', 'X-App-Client': 'docs-tutorial', },};What this gives you immediately:
typeonly accepts'file' | 'progressive' | 'hls' | 'dash'.headersis a string map (Record<string, string>) scoped per track.
2) Model a QueueSnapshot
Section titled “2) Model a QueueSnapshot”Start with one item, then move to a realistic multi-item queue.
import type { QueueSnapshot } from '@ddgutierrezc/legato-contract';
const queue: QueueSnapshot = { items: [introTrack], currentIndex: 0,};const queue: QueueSnapshot = { items: [ introTrack, { id: 'ep-002', url: 'https://cdn.example.com/audio/episode-002.m3u8', title: 'Episode 002', type: 'hls', }, ], currentIndex: 1,};QueueSnapshot is the serializable queue projection used by snapshots and event payloads.
3) Model a PlaybackSnapshot
Section titled “3) Model a PlaybackSnapshot”import type { PlaybackSnapshot } from '@ddgutierrezc/legato-contract';
const playback: PlaybackSnapshot = { state: 'playing', currentTrack: introTrack, currentIndex: 0, position: 12.4, duration: 180, bufferedPosition: 30, queue,};Model the no-active-item state too:
const idlePlayback: PlaybackSnapshot = { state: 'idle', currentTrack: null, currentIndex: null, position: 0, duration: null, queue: { items: [], currentIndex: null, },};Important contract semantics visible in this shape:
durationcan benumber | null.currentTrackandcurrentIndexcan benullwhen no active item exists.queueis always present and typed asQueueSnapshot.
4) Read event payload shapes without Capacitor
Section titled “4) Read event payload shapes without Capacitor”You can consume contract event names and payload maps directly in shared code:
import { LEGATO_EVENT_NAMES, type LegatoEventName, type LegatoEventPayload,} from '@ddgutierrezc/legato-contract';
function handleLegatoEvent<E extends LegatoEventName>( eventName: E, payload: LegatoEventPayload<E>,) { if (eventName === 'playback-progress') { payload.position; payload.duration; payload.bufferedPosition; }
if (eventName === 'remote-seek') { payload.position; }}
const allNames = LEGATO_EVENT_NAMES;This pattern keeps your domain and UI code runtime-neutral while still fully typed.
Common mistakes to avoid
Section titled “Common mistakes to avoid”- Treating
typeas a playback guarantee. It declares media semantics; runtime capability still decides what operations are available. - Treating
headersas dynamic auth orchestration. The contract defines static per-track headers, not token refresh or DRM/license flows. - Forgetting nullable branches.
duration,currentTrack, andcurrentIndexare intentionally nullable and must be handled. - Mixing runtime behavior into shared contract-only modules. Keep this layer transport-neutral.
Verify the result
Section titled “Verify the result”Your integration is correct when:
Track,QueueSnapshot, andPlaybackSnapshotobjects type-check.- Invalid
typevalues (for example'stream') fail at compile time. - Event payload access narrows by event name (for example,
remote-seekexposesposition).
When this tutorial is no longer enough
Section titled “When this tutorial is no longer enough”Move to runtime integration when you need:
- Real playback commands (
setup,add,play,seekTo) against native adapters. - Runtime event lifecycle management (listener registration + cleanup).
- Capability-gated UI decisions (
getCapabilities()for seek/skip availability). - Native setup diagnostics and configuration checks.
Next steps
Section titled “Next steps”- Read the Contract reference index for the canonical API surface.
- Deep dive into Track fields.
- Review Snapshots, Events, and Errors.
- Continue with Set Up Legato Capacitor for runtime integration.