--- status: implemented --- # Slice-Based Store Refactor `@videojs/store` to use "slice" terminology and add explicit `combine` primitive for composition. ## Problem 1. **"Feature" terminology in store package** - `@videojs/store` exports `defineFeature` which suggests media player concepts. Store primitives should be generic. 2. **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()`), pre-built features - `createPlayer` accepts `features: []` array, uses `combine` internally ## Quick Start ```ts // @videojs/store - Generic slice (config object pattern) const volumeSlice = defineSlice()({ 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()(combine(volumeSlice, playbackSlice)); // @videojs/core - definePlayerFeature = defineSlice() 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`: ```ts function defineSlice(): ( config: SliceConfig ) => Slice; interface SliceConfig { state: (ctx: StateContext) => State; attach?: (ctx: AttachContext) => void; } interface StateContext { task: Task; target: () => Target; } ``` ### combine Merge multiple slices into one: ```ts function combine[]>( ...slices: Slices ): Slice>; ``` - State factories called, results merged (last wins on conflict) - Attach handlers all run, errors caught and reported via `reportError` ### createStore ```ts function createStore(): ( slice: Slice, options?: StoreOptions ) => Store; // StoreOptions extends StoreCallbacks interface StoreOptions extends StoreCallbacks {} ``` ## Error Handling `AttachContext` includes `reportError` for error reporting (named after the web standard `reportError()` API): ```ts interface AttachContext { target: Target; signal: AbortSignal; store: AttachStore; get: () => Readonly; set: (partial: Partial) => 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()` | | `@videojs/html` | `createPlayer` | Uses `combine` internally | ## Migration Minimal changes - same config object pattern, just renamed: ```ts // Before (store package) import { defineFeature, createFeatureSelector } from '@videojs/store'; const feature = defineFeature()({ state: ..., attach: ... }); const store = createStore()({ features: [f1, f2] }); const select = createFeatureSelector(feature); // After (store package) import { defineSlice, combine, createSelector } from '@videojs/store'; const slice = defineSlice()({ state: ..., attach: ... }); const store = createStore()(combine(s1, s2)); const select = createSelector(slice); // Core package - definePlayerFeature for player features import { definePlayerFeature } from '@videojs/core'; const feature = definePlayerFeature({ state: ..., attach: ... }); // No () needed ``` ## Trade-offs | Gain | Cost | |------|------| | Cleaner package separation | Migration effort | | Explicit composition via `combine` | One more concept | | Generic store primitives | None |