docs(store): update readme

This commit is contained in:
Rahim
2026-01-04 15:48:47 +11:00
parent 2901593085
commit 4c7ba6df23
4 changed files with 77 additions and 80 deletions
+3 -2
View File
@@ -60,7 +60,8 @@ pnpm install
# Run all demos/sites in parallel
pnpm dev
# Typecheck across repo (fast)
# Typecheck across repo (fast - uses TypeScript project references)
# Always run from root, not per-package
pnpm typecheck
# Build all packages/apps
@@ -84,7 +85,7 @@ pnpm -F <pkg> test src/core
# Lint all workspace packages
pnpm lint
# Lint and fix a single file
pnpm lint:file:fix <file>
pnpm lint:fix:file <file>
# Remove all dist and types outputs
pnpm clean
+67 -71
View File
@@ -78,9 +78,37 @@ const audioSlice = createSlice<HTMLMediaElement>()({
});
```
### Explicit Types
### Slice Type Inference
For shared type definitions, use `Request<Input, Output>`:
State and request types are fully inferred from the slice config:
```ts
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
const audioSlice = createSlice<HTMLMediaElement>()({
initialState: { volume: 1, muted: false },
// ...
});
// Infer types from the slice
type AudioState = InferSliceState<typeof audioSlice>;
type AudioRequests = InferSliceRequests<typeof audioSlice>;
```
For stores with multiple slices:
```ts
import type { UnionSliceRequests, UnionSliceState } from '@videojs/store';
const slices = [audioSlice, playbackSlice] as const;
type MediaState = UnionSliceState<typeof slices>;
type MediaRequests = UnionSliceRequests<typeof slices>;
```
### Explicit Slice Types
For upfront type definitions, use `Request<Input, Output>`:
```ts
import type { Request } from '@videojs/store';
@@ -93,10 +121,10 @@ interface AudioState {
}
interface AudioRequests {
setVolume: Request<number>; // (volume: number) => void
setMuted: Request<boolean>; // (muted: boolean) => void
play: Request; // () => void
getDuration: Request; // () => number
setVolume: Request<number>; // (volume: number) => Promise<void>
setMuted: Request<boolean>; // (muted: boolean) => Promise<void>
play: Request; // () => Promise<void>
getDuration: Request<void, number>; // () => Promise<number>
}
const audioSlice = createSlice<HTMLMediaElement, AudioState, AudioRequests>({
@@ -109,6 +137,8 @@ const audioSlice = createSlice<HTMLMediaElement, AudioState, AudioRequests>({
Requests are operations against the target. Use function shorthand for simple cases, or full config for guards and scheduling.
```ts
import { onEvent } from '@videojs/utils/events';
request: {
// Shorthand - just the handler
setVolume(volume, { target }) {
@@ -187,6 +217,17 @@ const store = createStore({
});
```
### Type Inference
```ts
import type { InferStoreRequests, InferStoreState } from '@videojs/store';
const store = createStore({ slices: [audioSlice, playbackSlice] });
type State = InferStoreState<typeof store>;
type Requests = InferStoreRequests<typeof store>;
```
### Attaching a Target
```ts
@@ -203,6 +244,15 @@ store.request.play();
detach();
```
### Destroying a Store
Clean up when the store is no longer needed:
```ts
// Detaches target, aborts pending requests, clears queue
store.destroy();
```
### Subscribing to State
```ts
@@ -325,14 +375,20 @@ Schedule controls _when_ a request executes. The schedule function receives a `f
optionally returns a cancel function. Default schedule is microtask (executes at end of current tick).
```ts
import { delay } from '@videojs/store';
import { delay, microtask } from '@videojs/store';
import { raf, idle } from '@videojs/store/dom';
request: {
// Debounce 100ms - good for sliders
// Microtask - default, executes at end of current tick
setVolume: {
schedule: microtask,
handler: (volume, { target }) => { target.volume = volume; },
},
// Debounce 100ms - good for sliders
seek: {
schedule: delay(100),
handler: (volume, { target }) => { target.media.volume = volume; },
handler: (time, { target }) => { target.currentTime = time; },
},
// Sync with animation frame
@@ -385,6 +441,8 @@ request: {
```ts
import type { Guard } from '@videojs/store';
import { onEvent } from '@videojs/utils/events';
const canMediaPlay: Guard<HTMLMediaElement> = ({ target, signal }) => {
if (target.readyState >= HAVE_ENOUGH_DATA) return true;
return onEvent(target, 'canplay', { signal }); // wait for canplay
@@ -499,60 +557,6 @@ await queue.enqueue({
});
```
## React
```ts
import { useStore, useSlice, useRequest, usePending } from '@videojs/store/react';
// Full state - re-renders on any change
function Player() {
const state = useStore(store);
return <div>{state.paused ? 'Paused' : 'Playing'}</div>;
}
// Selector - re-renders only when volume changes
function VolumeDisplay() {
const volume = useStore(store, (s) => s.volume);
return <div>{Math.round(volume * 100)}%</div>;
}
// Multiple values
function AudioControls() {
const { volume, muted } = useStore(store, (s) => ({
volume: s.volume,
muted: s.muted
}));
return <div>{volume} {muted ? '🔇' : '🔊'}</div>;
}
// Track request state
function PlayButton() {
const { dispatch, isPending, error } = useRequest(store.request.play);
return (
<button onClick={dispatch} disabled={isPending}>
{isPending ? 'Starting...' : 'Play'}
</button>
);
}
// Check pending by key
function SeekBar() {
const isSeeking = usePending(store, 'seek');
return <input type="range" disabled={isSeeking} />;
}
// Check if slice exists
function QualityMenu() {
const quality = useSlice(store, qualitySlice);
if (!quality) return null;
return <Menu items={quality.state.levels} />;
}
```
## Advanced
### Custom State
@@ -645,14 +649,6 @@ function QualityMenu() {
}
```
## Exports
```md
@videojs/store # Core: createStore, createSlice, createQueue, Request
@videojs/store/dom # raf(), idle() schedulers
@videojs/store/react # useStore, useSlice, useRequest, usePending
```
## How It's Different
| | Redux/Zustand | React Query | @videojs/store |
+1 -1
View File
@@ -71,7 +71,7 @@ export type UnionSliceState<Slices extends Slice<any, any, any>[]> = UnionToInte
InferSliceState<Slices[number]>
>;
export type UnionSliceRequest<Slices extends Slice<any, any, any>[]> = UnionToIntersection<
export type UnionSliceRequests<Slices extends Slice<any, any, any>[]> = UnionToIntersection<
ResolveSliceRequestHandlers<Slices[number]>
>;
+6 -6
View File
@@ -1,6 +1,6 @@
import type { PendingTask, TaskContext } from './queue';
import type { RequestMeta, RequestMetaInit, ResolvedRequestConfig } from './request';
import type { AnySlice, InferSliceTarget, Slice, UnionSliceRequest, UnionSliceState, UnionSliceTasks } from './slice';
import type { AnySlice, InferSliceTarget, Slice, UnionSliceRequests, UnionSliceState, UnionSliceTasks } from './slice';
import type { StateFactory } from './state';
import { getSelectorKeys } from '@videojs/utils/object';
@@ -16,7 +16,7 @@ export class Store<Target, Slices extends AnySlice<Target>[] = AnySlice<Target>[
readonly #slices: Slices;
readonly #queue: Queue<UnionSliceTasks<Slices>>;
readonly #state: State<UnionSliceState<Slices>>;
readonly #request: UnionSliceRequest<Slices>;
readonly #request: UnionSliceRequests<Slices>;
readonly #requestConfigs: Map<string, ResolvedRequestConfig<Target>>;
readonly #setupAbort = new AbortController();
@@ -59,7 +59,7 @@ export class Store<Target, Slices extends AnySlice<Target>[] = AnySlice<Target>[
return this.#state.value;
}
get request(): UnionSliceRequest<Slices> {
get request(): UnionSliceRequests<Slices> {
return this.#request;
}
@@ -258,7 +258,7 @@ export class Store<Target, Slices extends AnySlice<Target>[] = AnySlice<Target>[
return configs;
}
#buildRequestProxy(): UnionSliceRequest<Slices> {
#buildRequestProxy(): UnionSliceRequests<Slices> {
const proxy: Record<string, (...args: any[]) => Promise<unknown>> = {};
for (const [name, config] of this.#requestConfigs) {
@@ -271,7 +271,7 @@ export class Store<Target, Slices extends AnySlice<Target>[] = AnySlice<Target>[
};
}
return proxy as UnionSliceRequest<Slices>;
return proxy as UnionSliceRequests<Slices>;
}
async #execute(
@@ -410,6 +410,6 @@ export type InferStoreSlices<S extends AnyStore> = S extends Store<any, infer Sl
export type InferStoreState<S extends AnyStore> = UnionSliceState<InferStoreSlices<S>>;
export type InferStoreRequests<S extends AnyStore> = UnionSliceRequest<InferStoreSlices<S>>;
export type InferStoreRequests<S extends AnyStore> = UnionSliceRequests<InferStoreSlices<S>>;
export type InferStoreTasks<S extends AnyStore> = UnionSliceTasks<InferStoreSlices<S>>;