Files
v10/.agents/skills/write-docs/patterns/error-docs.md
T

6.9 KiB

Error Documentation Pattern

Document store errors consistently across Video.js packages.


Error Code Reference Table

Always document errors in this format:

Code Meaning Recovery
ABORTED Request aborted via signal Expected during cleanup — no action needed
CANCELLED Cancelled by another request's cancel: [] Check request coordination
SUPERSEDED Same-key request replaced this one Expected during rapid input — no action needed
REJECTED Guard returned falsy Check preconditions, show user feedback
TIMEOUT Guard timed out Increase timeout or check target readiness
NO_TARGET No target attached Call attach() before making requests
DETACHED Target was detached Re-attach or abort operation
DESTROYED Store was destroyed Create new store instance

Expected vs Unexpected Errors

Document which errors are "normal" vs programming errors:

Code Expected? Notes
SUPERSEDED Often Rapid user input (scrubbing, repeated clicks)
ABORTED Often Component unmount, navigation
CANCELLED Sometimes Intentional coordination between requests
REJECTED Sometimes Guard logic blocking execution
TIMEOUT Rarely Slow media load, network issues
NO_TARGET Never Programming error — attach before use
DETACHED Rarely Lifecycle timing issue
DESTROYED Never Programming error — don't use after destroy

Error Handling Patterns

Global Handler (store config)

const store = createStore({
  features: [playbackFeature, volumeFeature],
  onError: ({ error, request }) => {
    if (request) {
      console.error(`${request.name} failed:`, error.code);
    }
    // Report to analytics, show toast, etc.
  },
});

Local Handler (try/catch)

import { isStoreError } from '@videojs/store';

try {
  await store.request.play();
} catch (error) {
  if (isStoreError(error)) {
    switch (error.code) {
      case 'SUPERSEDED':
        // Another request took over — expected, ignore
        break;
      case 'REJECTED':
        // Guard blocked execution — show feedback
        showMessage('Cannot play right now');
        break;
      case 'TIMEOUT':
        // Took too long — retry or show error
        showMessage('Media not ready');
        break;
      default:
        console.error(`[${error.code}]`, error.message);
    }
  } else {
    throw error; // Re-throw unknown errors
  }
}

Type Guard Pattern

Always show the type guard:

import { isStoreError } from '@videojs/store';

function handleError(error: unknown) {
  if (isStoreError(error)) {
    // error is StoreError — has .code, .message
    return { code: error.code, message: error.message };
  }
  throw error;
}

Troubleshooting Section Format

Structure

  1. Error code/message as heading
  2. Cause: One sentence
  3. Solution: Code example

Examples

NO_TARGET

Cause: Request made before attach() was called.

Solution:

// ❌ Wrong
const store = createStore({ features: [playbackFeature] });
await store.request.play(); // Error: NO_TARGET

// ✅ Correct
const store = createStore({ features: [playbackFeature] });
store.attach(videoElement);
await store.request.play();

SUPERSEDED

Cause: Another request with the same key started before this one finished.

Solution: This is usually expected behavior. If you need the result, check before making a new request:

// If you need to know the final state
const result = await store.request.play();
// Result may be from a later request if superseded

// If you want to prevent supersession, use unique keys
request: {
  trackEvent: {
    key: () => Symbol(), // Each call gets unique key
    handler: (data) => analytics.log(data),
  },
}

REJECTED

Cause: A guard returned a falsy value.

Solution: Check what condition the guard expects:

// Guard that checks readyState
const canPlay: Guard<HTMLMediaElement> = ({ target }) => {
  return target.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA;
};

// If rejected, media isn't ready — wait for canplay event
player.on('canplay', () => {
  // Now safe to request play
  store.request.play();
});

TIMEOUT

Cause: A guard didn't resolve within the timeout period.

Solution: Increase the timeout or ensure the target is ready:

import { timeout } from '@videojs/store';

request: {
  play: {
    // Increase timeout for slow connections
    guard: timeout(canMediaPlay, 10000), // 10 seconds
    handler: async (_, { target }) => {
      await target.play();
    },
  },
}

ABORTED

Cause: The abort signal was triggered (usually from component unmount).

Solution: This is expected behavior. Ensure cleanup runs:

// React
useEffect(() => {
  const controller = new AbortController();

  store.request.play(null, { signal: controller.signal });

  return () => controller.abort(); // Cleans up on unmount
}, []);

API Reference Format

When documenting error-related APIs:

isStoreError

Type guard for store errors.

import { isStoreError } from '@videojs/store';

if (isStoreError(error)) {
  console.log(error.code); // 'ABORTED' | 'CANCELLED' | ...
}

Parameters

Parameter Type Description
error unknown Any caught error

Returns

error is StoreError — Type predicate

StoreError

Error thrown by store operations.

Properties

Property Type Description
code StoreErrorCode Error classification
message string Human-readable description

StoreErrorCode

type StoreErrorCode =
  | 'ABORTED'
  | 'CANCELLED'
  | 'DESTROYED'
  | 'DETACHED'
  | 'NO_TARGET'
  | 'REJECTED'
  | 'SUPERSEDED'
  | 'TIMEOUT';

Checklist

When documenting errors:

  • Error code table with all codes
  • Expected vs unexpected classification
  • Global handler example (onError)
  • Local handler example (try/catch)
  • Type guard usage shown
  • Troubleshooting section for common errors
  • Each troubleshooting entry has: Cause + Solution
  • Code examples are self-contained