4.7 KiB
status
| status |
|---|
| implemented |
Slice-Based Store
Refactor @videojs/store to use "slice" terminology and add explicit combine primitive for composition.
Problem
-
"Feature" terminology in store package -
@videojs/storeexportsdefineFeaturewhich suggests media player concepts. Store primitives should be generic. -
Composition is hidden - Features are passed as an array to
createStore, which handles merging internally. No first-class composition primitive.
Solution
Rename "feature" to "slice" in store package, add explicit combine function. "Feature" moves to @videojs/core as domain-specific alias.
Layering:
@videojs/store- generic primitives:defineSlice,combine,createStore@videojs/core-definePlayerFeature(=defineSlice<PlayerTarget>()), pre-built featurescreatePlayeracceptsfeatures: []array, usescombineinternally
Quick Start
// @videojs/store - Generic slice (config object pattern)
const volumeSlice = defineSlice<MediaElement>()({
state: ({ task, target }) => ({
volume: 1,
setVolume(v: number) {
return task(({ target }) => { target.volume = v; return v; });
},
}),
attach: ({ target, set, signal }) => {
const sync = () => set({ volume: target.volume });
target.addEventListener('volumechange', sync);
signal.addEventListener('abort', () => {
target.removeEventListener('volumechange', sync);
});
},
});
// Compose slices
const store = createStore<MediaElement>()(combine(volumeSlice, playbackSlice));
// @videojs/core - definePlayerFeature = defineSlice<PlayerTarget>()
const volumeFeature = definePlayerFeature({
state: ({ task }) => ({ volume: 1, setVolume: ... }),
attach: ({ set }) => { ... },
});
// @videojs/html - createPlayer unchanged
createPlayer({ features: [volumeFeature, playbackFeature] });
API
defineSlice
Config object pattern - same structure as current defineFeature:
function defineSlice<Target>(): <State>(
config: SliceConfig<Target, State>
) => Slice<Target, State>;
interface SliceConfig<Target, State> {
state: (ctx: StateContext<Target>) => State;
attach?: (ctx: AttachContext<Target, State>) => void;
}
interface StateContext<Target> {
task: Task<Target, any>;
target: () => Target;
}
combine
Merge multiple slices into one:
function combine<Target, Slices extends Slice<Target, any>[]>(
...slices: Slices
): Slice<Target, UnionSliceState<Slices>>;
- State factories called, results merged (last wins on conflict)
- Attach handlers all run, errors caught and reported via
reportError
createStore
function createStore<Target>(): <State>(
slice: Slice<Target, State>,
options?: StoreOptions<Target, State>
) => Store<Target, State>;
// StoreOptions extends StoreCallbacks
interface StoreOptions<Target, State> extends StoreCallbacks<Target, State> {}
Error Handling
AttachContext includes reportError for error reporting (named after the web standard reportError() API):
interface AttachContext<Target, State> {
target: Target;
signal: AbortSignal;
store: AttachStore;
get: () => Readonly<State>;
set: (partial: Partial<State>) => void;
reportError: (error: Error) => void;
}
combine catches errors in attach handlers and reports via reportError, allowing other handlers to continue.
Package Layering
| Package | Exports | Description |
|---|---|---|
@videojs/store |
defineSlice, combine, createStore, createSelector, Slice, AnySlice |
Generic primitives |
@videojs/core |
definePlayerFeature, pre-built features |
definePlayerFeature = defineSlice<PlayerTarget>() |
@videojs/html |
createPlayer |
Uses combine internally |
Migration
Minimal changes - same config object pattern, just renamed:
// Before (store package)
import { defineFeature, createFeatureSelector } from '@videojs/store';
const feature = defineFeature<Target>()({ state: ..., attach: ... });
const store = createStore<Target>()({ features: [f1, f2] });
const select = createFeatureSelector(feature);
// After (store package)
import { defineSlice, combine, createSelector } from '@videojs/store';
const slice = defineSlice<Target>()({ state: ..., attach: ... });
const store = createStore<Target>()(combine(s1, s2));
const select = createSelector(slice);
// Core package - definePlayerFeature for player features
import { definePlayerFeature } from '@videojs/core';
const feature = definePlayerFeature({ state: ..., attach: ... }); // No <Target>() needed
Trade-offs
| Gain | Cost |
|---|---|
| Cleaner package separation | Migration effort |
Explicit composition via combine |
One more concept |
| Generic store primitives | None |