mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
275 lines
6.9 KiB
Markdown
275 lines
6.9 KiB
Markdown
# 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)
|
|
|
|
```ts
|
|
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)
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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:**
|
|
|
|
```ts
|
|
// ❌ 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:
|
|
|
|
```ts
|
|
// 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:
|
|
|
|
```ts
|
|
// 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:
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
// 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.
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|