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
- Error code/message as heading
- Cause: One sentence
- 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