From 0354e3ef9c8bd48888c1de7b1319700e587d7224 Mon Sep 17 00:00:00 2001 From: rahim Date: Tue, 6 Jan 2026 14:09:36 +1100 Subject: [PATCH] chore(packages): remove `isolatedDeclarations` for store type inference support (#295) --- CLAUDE.md | 85 ++++++++++++++- packages/core/package.json | 2 +- packages/core/src/dom/store/slices/buffer.ts | 4 +- packages/core/src/dom/tsconfig.json | 3 +- packages/core/tsconfig.json | 3 +- packages/html/package.json | 2 +- packages/html/tsdown.config.ts | 4 +- packages/icons/package.json | 2 +- packages/react/package.json | 2 +- packages/react/tsdown.config.ts | 4 +- packages/store/package.json | 2 +- packages/store/src/core/queue.ts | 104 +------------------ packages/store/src/core/request.ts | 46 -------- packages/store/src/core/store.ts | 15 --- packages/store/src/dom/schedulers.ts | 56 +--------- packages/store/src/react/create-store.tsx | 43 +------- packages/store/src/react/hooks.ts | 28 +---- packages/store/tsdown.config.ts | 4 +- packages/utils/package.json | 2 +- packages/utils/src/dom/animation-frame.ts | 13 +-- packages/utils/src/dom/event.ts | 63 +---------- packages/utils/src/dom/idle-callback.ts | 21 +--- packages/utils/src/dom/listen.ts | 70 +------------ packages/utils/src/dom/supports.ts | 10 -- packages/utils/src/dom/time-ranges.ts | 7 +- packages/utils/src/events/disposer.ts | 16 +-- packages/utils/src/function/try-catch.ts | 4 - packages/utils/src/object/selector.ts | 12 +-- packages/utils/src/object/tests/pick.test.ts | 4 +- packages/utils/tsdown.config.ts | 4 +- pnpm-lock.yaml | 12 +-- tsconfig.base.json | 2 +- 32 files changed, 137 insertions(+), 512 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 85ad2799..7f25c92a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -199,8 +199,8 @@ Before writing new helpers, check `@videojs/utils` for existing utilities. Always return `value is Type` for proper type narrowing: ```ts -function isStoreError(error: unknown): error is StoreError { - return error instanceof StoreError; +function isStoreError(value: unknown): value is StoreError { + return value instanceof StoreError; } ``` @@ -243,3 +243,84 @@ destroy(): void { this.#subscribers.clear(); } ``` + +### No Hungarian Type Notation + +Never prefix type parameters with `T`. Use descriptive names instead: + +```ts +// Bad +type Mixin = ... +function createStore(...) { ... } + +// Good +type Mixin = ... +function createStore(...) { ... } +``` + +### No Obvious Comments + +Don't write comments that restate what the code does. Comments should explain _why_, not _what_: + +```ts +// Bad +// Create the store +const store = createStore(config); + +// Loop through items +for (const item of items) { ... } + +// Good +// Create store before rendering to allow pre-hydration +const store = createStore(config); +``` + +### No Pointless Type Casts + +Avoid casts that don't add value. If TypeScript can infer the type, don't cast: + +```ts +// Bad - already typed +const value = someFunction() as SomeType; + +// Bad - use generic type argument +const media = node.querySelector('video, audio') as HTMLMediaElement | null; +``` + +### Minimal JSDoc + +JSDoc should add value, not restate what TypeScript already shows: + +**No redundant @param/@returns** — TypeScript signatures are the documentation: + +```ts +// Bad +/** + * @param callback - The callback to invoke + * @returns A cleanup function + */ +export function animationFrame(callback: FrameRequestCallback): () => void + +// Good +/** Request an animation frame with cleanup. */ +export function animationFrame(callback: FrameRequestCallback): () => void +``` + +**Single JSDoc for overloads** — Document the first overload only: + +```ts +/** Wait for an event to occur on a target. */ +export function onEvent(...): Promise<...>; +export function onEvent(...): Promise<...>; +``` + +**One example per function** — Consolidate into a single representative example. + +**No JSDoc for self-documenting code** — Skip JSDoc when names are clear: + +```ts +// No JSDoc needed +export function supportsIdleCallback(): boolean { ... } +get size(): number { ... } +add(cleanup: CleanupFn): void { ... } +``` \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index 11c5c0b7..8ae3e461 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "jsdom": "^26.1.0", - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3", "vitest": "^3.2.4" }, diff --git a/packages/core/src/dom/store/slices/buffer.ts b/packages/core/src/dom/store/slices/buffer.ts index 232be5af..1e9b679e 100644 --- a/packages/core/src/dom/store/slices/buffer.ts +++ b/packages/core/src/dom/store/slices/buffer.ts @@ -11,9 +11,9 @@ import { listen, serializeTimeRanges } from '@videojs/utils/dom'; export const bufferSlice = createSlice()({ initialState: { /** Buffered time ranges as [start, end] tuples. */ - buffered: [] as Array<[number, number]>, + buffered: [] as [number, number][], /** Seekable time ranges as [start, end] tuples. */ - seekable: [] as Array<[number, number]>, + seekable: [] as [number, number][], }, getSnapshot: ({ target }) => ({ diff --git a/packages/core/src/dom/tsconfig.json b/packages/core/src/dom/tsconfig.json index c11400ae..7525a101 100644 --- a/packages/core/src/dom/tsconfig.json +++ b/packages/core/src/dom/tsconfig.json @@ -3,8 +3,7 @@ "compilerOptions": { "composite": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], - "declarationDir": "../../types/dom", - "isolatedDeclarations": false + "declarationDir": "../../types/dom" }, "references": [{ "path": "../.." }], "include": ["./**/*.ts"] diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index cdaacc94..41391597 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -2,8 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true, - "declarationDir": "types", - "isolatedDeclarations": false + "declarationDir": "types" }, "references": [{ "path": "../utils" }, { "path": "../store" }], "include": ["src/core/**/*.ts"] diff --git a/packages/html/package.json b/packages/html/package.json index c04741da..6124411b 100644 --- a/packages/html/package.json +++ b/packages/html/package.json @@ -37,7 +37,7 @@ "@videojs/utils": "workspace:*" }, "devDependencies": { - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3" }, "publishConfig": { diff --git a/packages/html/tsdown.config.ts b/packages/html/tsdown.config.ts index 371e6191..c387a2ae 100644 --- a/packages/html/tsdown.config.ts +++ b/packages/html/tsdown.config.ts @@ -21,7 +21,5 @@ export default defineConfig({ alias: { '@': new URL('./src', import.meta.url).pathname, }, - dts: { - oxc: true, - }, + dts: true, }); diff --git a/packages/icons/package.json b/packages/icons/package.json index 79845322..15564769 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "files": [], "devDependencies": { - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3" }, "publishConfig": { diff --git a/packages/react/package.json b/packages/react/package.json index 6c671590..0b6b66b3 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -42,7 +42,7 @@ "devDependencies": { "@types/react": "^18.0.0", "react": "^18.0.0", - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3" }, "publishConfig": { diff --git a/packages/react/tsdown.config.ts b/packages/react/tsdown.config.ts index ccbd11d8..32cb06ce 100644 --- a/packages/react/tsdown.config.ts +++ b/packages/react/tsdown.config.ts @@ -13,7 +13,5 @@ export default defineConfig({ alias: { '@': new URL('./src', import.meta.url).pathname, }, - dts: { - oxc: true, - }, + dts: true, }); diff --git a/packages/store/package.json b/packages/store/package.json index a8bf457e..e99af2c3 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -70,7 +70,7 @@ "jsdom": "^26.1.0", "react": "^19.2.1", "react-dom": "^19.2.1", - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3", "vitest": "^3.2.4" }, diff --git a/packages/store/src/core/queue.ts b/packages/store/src/core/queue.ts index d57e80ef..d5fcf6e5 100644 --- a/packages/store/src/core/queue.ts +++ b/packages/store/src/core/queue.ts @@ -20,26 +20,14 @@ export type EnsureTaskKey = T extends string | symbol ? T : never; */ export type TaskScheduler = (flush: () => void) => (() => void) | void; -/** - * Map of task key -> input/output types. - */ export type TaskRecord = { [K in TaskKey]: Request; }; -/** - * Default loose task types. - */ export type DefaultTaskRecord = Record>; -/** - * Ensure T is a TaskRecord. - */ export type EnsureTaskRecord = T extends TaskRecord ? T : never; -/** - * Base fields shared by all task states. - */ export interface TaskBase { id: symbol; name: string; @@ -49,17 +37,11 @@ export interface TaskBase { meta: RequestMeta | null; } -/** - * Pending task - request in flight. - */ export interface PendingTask extends TaskBase { status: 'pending'; abort: AbortController; } -/** - * Success task - completed successfully. - */ export interface SuccessTask extends TaskBase< Key, Input @@ -69,9 +51,6 @@ export interface SuccessTask extends TaskBase { status: 'error'; settledAt: number; @@ -79,32 +58,20 @@ export interface ErrorTask exten cancelled: boolean; } -/** - * Task with status discriminator. - */ export type Task = | PendingTask | SuccessTask | ErrorTask; -/** - * Settled task (success or error). - */ export type SettledTask = | SuccessTask | ErrorTask; -/** - * Context passed to task handler. - */ export interface TaskContext { input: Input; signal: AbortSignal; } -/** - * Task to enqueue. - */ export interface QueueTask { name: string; key: Key; @@ -114,9 +81,6 @@ export interface QueueTask) => Promise; } -/** - * Queued task waiting to execute. - */ interface QueuedTask { id: symbol; name: string; @@ -127,7 +91,6 @@ interface QueuedTask) => Promise; resolve: (value: Output) => void; reject: (error: unknown) => void; - /* Cancel scheduled execution. */ invalidate?: () => void; } @@ -151,18 +114,10 @@ export type QueuedRecord = { [K in keyof Tasks]?: QueuedTask>; }; -/** - * Map of task key -> task (pending, success, or error). - */ export type TasksRecord = { [K in keyof Tasks]?: Task, Tasks[K]['input'], Tasks[K]['output']>; }; -/** - * Listener callback for task state changes. - * - * Called when tasks are dispatched, settled, or reset. - */ export type QueueListener = (tasks: TasksRecord) => void; // ---------------------------------------- @@ -226,9 +181,6 @@ export class Queue { return Object.freeze({ ...this.#queued }); } - /** - * Map of task key -> task (pending, success, or error). - */ get tasks(): Readonly> { return Object.freeze({ ...this.#tasks }); } @@ -237,35 +189,21 @@ export class Queue { return this.#destroyed; } - /** - * Check if a task with the given key is currently pending (executing). - */ isPending(key: keyof Tasks): boolean { return this.#tasks[key]?.status === 'pending'; } - /** - * Check if a task with the given key is currently queued (waiting to execute). - */ isQueued(key: keyof Tasks): boolean { return key in this.#queued; } - /** - * Check if a task with the given key is settled (success or error). - */ isSettled(key: keyof Tasks): boolean { const task = this.#tasks[key]; return task?.status === 'success' || task?.status === 'error'; } /** - * Clear settled task(s). - * - * - If key provided: clears that specific settled task (no-op if pending or doesn't exist) - * - If no key: clears all settled tasks (pending tasks are preserved) - * - * @param key - Optional task key to reset. If omitted, resets all settled tasks. + * Clear settled task(s). If key provided, clears that task. If no key, clears all settled. */ reset(key?: keyof Tasks): void { if (!isUndefined(key)) { @@ -278,7 +216,6 @@ export class Queue { return; } - // Reset all settled tasks let cleared = false; for (const key of Reflect.ownKeys(this.#tasks)) { const task = this.#tasks[key]; @@ -293,14 +230,6 @@ export class Queue { } } - /** - * Subscribe to task state changes. - * - * Fires when tasks are dispatched, settled, or reset. - * - * @param listener - Callback receiving the current tasks map - * @returns Unsubscribe function - */ subscribe(listener: QueueListener): () => void { this.#subscribers.add(listener); return () => { @@ -330,19 +259,16 @@ export class Queue { return Promise.reject(new StoreError('DESTROYED')); } - // Cancel any queued task with same key const queued = this.#queued[key]; queued?.invalidate?.(); queued?.reject(new StoreError('SUPERSEDED')); delete this.#queued[key]; - // Abort any pending task with same key const existing = this.#tasks[key]; if (existing?.status === 'pending') { existing.abort.abort(new StoreError('SUPERSEDED')); } - // Clear any settled task for this key (new request replaces it) delete this.#tasks[key]; return new Promise((resolve, reject) => { @@ -365,7 +291,6 @@ export class Queue { try { const scheduleFlush = schedule ?? this.#scheduler; - // Guard against multiple flushes const safeFlush = () => { if (flushed) return; flushed = true; @@ -374,7 +299,6 @@ export class Queue { const cancel = scheduleFlush(safeFlush); - // Only set invalidate if we haven't already flushed if (!flushed && isFunction(cancel)) { task.invalidate = cancel; } @@ -389,13 +313,7 @@ export class Queue { } /** - * Cancel queued task(s) waiting to execute. - * - * - If key provided: cancels that specific queued task - * - If no key: cancels all queued tasks - * - * @param key - Optional task key to cancel - * @returns true if any task was cancelled + * Cancel queued task(s). If key provided, cancels that task. If no key, cancels all. */ cancel(key?: keyof Tasks): boolean { if (!isUndefined(key)) { @@ -409,7 +327,6 @@ export class Queue { return true; } - // Cancel all queued const hadQueued = Object.keys(this.#queued).length > 0; for (const queued of Object.values(this.#queued)) { queued.invalidate?.(); @@ -427,28 +344,20 @@ export class Queue { return; } - // Flush all const keys = Reflect.ownKeys(this.#queued); await Promise.allSettled(keys.map(k => this.#flushKey(k))); } /** - * Abort task(s) - both queued (waiting) and pending (executing). - * - * - If key provided: aborts that specific task - * - If no key: aborts all tasks - * - * @param key - Optional task key to abort + * Abort task(s). If key provided, aborts that task. If no key, aborts all. */ abort(key?: keyof Tasks): void { if (!isUndefined(key)) { - // Reject queued const queued = this.#queued[key]; queued?.invalidate?.(); queued?.reject(new StoreError('ABORTED')); delete this.#queued[key]; - // Abort pending task const task = this.#tasks[key]; if (task?.status === 'pending') { task.abort.abort(new StoreError('ABORTED')); @@ -457,10 +366,8 @@ export class Queue { return; } - // Abort all const error = new StoreError('ABORTED'); - // Reject all queued for (const queued of Object.values(this.#queued)) { queued.invalidate?.(); queued.reject(error); @@ -468,7 +375,6 @@ export class Queue { this.#queued = {}; - // Abort all pending tasks for (const task of Object.values(this.#tasks)) { if (task?.status === 'pending') { task.abort.abort(error); @@ -540,7 +446,7 @@ export class Queue { }; // Only update if we're still the current task for this key - if (this.#tasks[key as keyof Tasks] === pendingTask) { + if (this.#tasks[key] === pendingTask) { this.#tasks[key as keyof Tasks] = successTask; this.#notifySubscribers(); } @@ -558,7 +464,7 @@ export class Queue { }; // Only update if we're still the current task for this key - if (this.#tasks[key as keyof Tasks] === pendingTask) { + if (this.#tasks[key] === pendingTask) { this.#tasks[key as keyof Tasks] = errorTask; this.#notifySubscribers(); } diff --git a/packages/store/src/core/request.ts b/packages/store/src/core/request.ts index db89e0f7..d563df76 100644 --- a/packages/store/src/core/request.ts +++ b/packages/store/src/core/request.ts @@ -23,41 +23,23 @@ export type RequestRecord = { [K in string]: Request; }; -/** - * Default loose request types. - */ export type DefaultRequestRecord = Record; -/** - * Context passed to request handlers. - */ export interface RequestContext { target: Target; signal: AbortSignal; meta: RequestMeta | null; } -/** - * Request key - static or derived from input. - */ export type RequestKey = TaskKey | ((input: Input) => TaskKey); -/** - * Request cancel config. - */ export type RequestCancel = TaskKey | TaskKey[] | ((input: Input) => TaskKey | TaskKey[]); -/** - * Request handler function. - */ export type RequestHandler = ( input: Input, ctx: RequestContext, ) => Output | Promise; -/** - * Full request config. - */ export interface RequestConfig { key?: RequestKey; schedule?: TaskScheduler; @@ -66,9 +48,6 @@ export interface RequestConfig { handler: RequestHandler; } -/** - * Resolved request config (after normalization). - */ export interface ResolvedRequestConfig { key: RequestKey; schedule?: TaskScheduler | undefined; @@ -81,19 +60,12 @@ export type RequestHandlerRecord = { [K in string]: RequestHandler; }; -/** - * Map of request names to handlers or configs. This is the config passed to `createSlice`. - */ export type RequestConfigMap }> = { [K in keyof Requests]: Requests[K] extends Request ? RequestHandler | RequestConfig : never; }; -/** - * Map of request config objects to resolved configs. This is the config stored internally in - * the store. - */ export type ResolvedRequestConfigMap }> = { [K in keyof Requests]: Requests[K] extends Request ? ResolvedRequestConfig : never; }; @@ -102,9 +74,6 @@ export type ResolvedRequestConfigMap = Handler extends () => any ? void : Handler extends (input: infer I, ctx?: any) => any @@ -115,25 +84,16 @@ export type InferRequestHandlerInput = Handler extends () => any ? I : void; -/** - * Infer the output type of a RequestHandler. - */ export type InferRequestHandlerOutput = Handler extends (...args: any[]) => infer O ? Awaited : Handler extends { handler: (...args: any[]) => infer O } ? Awaited : void; -/** - * Resolve a RequestHandlerRecord to a RequestRecord. - */ export type ResolveRequestMap = { [K in keyof Requests]: Request, InferRequestHandlerOutput>; }; -/** - * Resolve a Request (input/output) to its function signature. - */ export type ResolveRequestHandler = R extends Request ? [I] extends [void] @@ -203,16 +163,10 @@ export function createRequestMeta(init: RequestMetaInit( event: EventLike, context?: Context, diff --git a/packages/store/src/core/store.ts b/packages/store/src/core/store.ts index 9cc61cae..e43c1d55 100644 --- a/packages/store/src/core/store.ts +++ b/packages/store/src/core/store.ts @@ -30,7 +30,6 @@ export class Store[] = AnySlice[ this.#queue = config.queue ?? new Queue>(); - // Use provided factory or default const factory = config.state ?? (initial => new State(initial)); this.#state = factory(this.#createInitialState()); @@ -157,12 +156,10 @@ export class Store[] = AnySlice[ maybeListener?: (selected: Selected) => void, options?: SubscribeOptions, ): () => void { - // Full state subscription (single argument) if (!maybeListener) { return this.#state.subscribe(selectorOrListener); } - // Selector-based subscription const selector = selectorOrListener as Selector, Selected>; const listener = maybeListener; const equalityFn = options?.equalityFn ?? Object.is; @@ -176,11 +173,9 @@ export class Store[] = AnySlice[ } }; - // Optimization: use key-based subscription if selector returns object const keys = getSelectorKeys(selector, this.#state.value); if (keys) { - // Note: subscribeKeys listener receives full state at runtime, type is narrowed for safety return this.#state.subscribeKeys( keys as (keyof UnionSliceState)[], handler as (state: Pick, keyof UnionSliceState>) => void, @@ -359,19 +354,9 @@ export function createStore( export type AnyStore = Store[]>; -/** - * A selector function that extracts a subset of state. - */ export type Selector = (state: State) => Selected; -/** - * Options for selector-based subscriptions. - */ export interface SubscribeOptions { - /** - * Custom equality function for comparing selected values. - * Defaults to `Object.is`. - */ equalityFn?: (a: T, b: T) => boolean; } diff --git a/packages/store/src/dom/schedulers.ts b/packages/store/src/dom/schedulers.ts index dd05c949..0f15eb98 100644 --- a/packages/store/src/dom/schedulers.ts +++ b/packages/store/src/dom/schedulers.ts @@ -3,28 +3,11 @@ import type { TaskScheduler } from '../core/queue'; import { animationFrame, idleCallback } from '@videojs/utils/dom'; /** - * Create a scheduler that delays task execution until the next animation frame. - * - * Uses `requestAnimationFrame` under the hood. Ideal for UI updates that should - * sync with the browser's repaint cycle. - * - * @returns A TaskScheduler for use with the queue + * Scheduler using `requestAnimationFrame`. Ideal for UI updates. * * @example * ```ts - * import { createQueue } from '@videojs/store'; - * import { raf } from '@videojs/store/dom'; - * - * const queue = createQueue(); - * - * queue.enqueue({ - * name: 'update-ui', - * key: 'ui', - * schedule: raf(), - * handler: async () => { - * // This runs on the next animation frame - * }, - * }); + * queue.enqueue({ key: 'ui', schedule: raf(), handler: async () => {} }); * ``` */ export function raf(): TaskScheduler { @@ -32,42 +15,11 @@ export function raf(): TaskScheduler { } /** - * Create a scheduler that delays task execution until the browser is idle. - * - * Uses `requestIdleCallback` under the hood (with `setTimeout` fallback for Safari). - * Ideal for non-critical background work. - * - * @param options - Optional idle callback options (e.g., timeout) - * @returns A TaskScheduler for use with the queue + * Scheduler using `requestIdleCallback`. Ideal for background work. * * @example * ```ts - * import { createQueue } from '@videojs/store'; - * import { idle } from '@videojs/store/dom'; - * - * const queue = createQueue(); - * - * queue.enqueue({ - * name: 'analytics', - * key: 'analytics', - * schedule: idle(), - * handler: async () => { - * // This runs when the browser is idle - * }, - * }); - * ``` - * - * @example - * ```ts - * // With timeout to ensure execution within 2 seconds - * queue.enqueue({ - * name: 'critical-background', - * key: 'bg', - * schedule: idle({ timeout: 2000 }), - * handler: async () => { - * // Runs when idle, or after 2 seconds - * }, - * }); + * queue.enqueue({ key: 'bg', schedule: idle({ timeout: 2000 }), handler: async () => {} }); * ``` */ export function idle(options?: IdleRequestOptions): TaskScheduler { diff --git a/packages/store/src/react/create-store.tsx b/packages/store/src/react/create-store.tsx index 57923bb4..3a24ea53 100644 --- a/packages/store/src/react/create-store.tsx +++ b/packages/store/src/react/create-store.tsx @@ -15,9 +15,6 @@ import { useRequest as useRequestBase, useSelector as useSelectorBase, useTasks // Types // ---------------------------------------- -/** - * Configuration for `createStore`. - */ export interface CreateStoreConfig extends StoreConfig, Slices> { /** * Display name for React DevTools. @@ -25,9 +22,6 @@ export interface CreateStoreConfig extends StoreConfi displayName?: string; } -/** - * Props for the Provider component returned by `createStore`. - */ export interface ProviderProps { children: ReactNode; /** @@ -44,9 +38,6 @@ export interface ProviderProps { inherit?: boolean; } -/** - * Result of `createStore`. - */ export interface CreateStoreResult { /** * Provider component that creates and manages the store lifecycle. @@ -100,21 +91,6 @@ export interface CreateStoreResult { * const { Provider, useStore, useSelector, useRequest, useTasks, create } = createStore({ * slices: [playbackSlice, presentationSlice], * }); - * - * function App() { - * return ( - * - * - * ); - * } - * - * function Controls() { - * const paused = useSelector((s) => s.paused); - * const play = useRequest((r) => r.play); - * return ; - * } * ``` */ export function createStore(config: CreateStoreConfig): CreateStoreResult { @@ -124,9 +100,6 @@ export function createStore(config: CreateStoreConfig type Tasks = UnionSliceTasks; type StoreType = Store; - /** - * Creates a new store instance. - */ function create(): StoreType { return new Store(config); } @@ -174,31 +147,20 @@ export function createStore(config: CreateStoreConfig Provider.displayName = `${config.displayName}.Provider`; } - /** - * Returns the typed store instance from context. - */ function useStore(): StoreType { return useStoreContext() as StoreType; } - /** - * Subscribes to a selected portion of state. - */ function useSelector(selector: (state: State) => T): T { const store = useStore(); return useSelectorBase(store, selector); } - /** - * Returns the request map or a selected request. - */ function useRequest(): Requests; function useRequest(selector: (requests: Requests) => T): T; function useRequest(selector?: (requests: Requests) => T): Requests | T { const store = useStore(); - // useRequestBase doesn't use React hooks internally, but we always call it - // to maintain consistent hook call order (even though it's technically not required) - const requests = useRequestBase(store) as Requests; + const requests = useRequestBase(store); if (isUndefined(selector)) { return requests; @@ -207,9 +169,6 @@ export function createStore(config: CreateStoreConfig return selector(requests); } - /** - * Subscribes to task state changes. - */ function useTasks(): TasksRecord { const store = useStore(); return useTasksBase(store); diff --git a/packages/store/src/react/hooks.ts b/packages/store/src/react/hooks.ts index 7abec5ad..58c3e290 100644 --- a/packages/store/src/react/hooks.ts +++ b/packages/store/src/react/hooks.ts @@ -6,12 +6,7 @@ import { isUndefined } from '@videojs/utils/predicate'; import { useCallback, useRef, useSyncExternalStore } from 'react'; /** - * Subscribes to a selected portion of state. - * Re-renders only when the selected value changes. - * - * @param store - The store instance - * @param selector - Function to select a portion of state - * @returns The selected value + * Subscribe to selected state. Re-renders only when selected value changes. */ export function useSelector(store: S, selector: (state: InferStoreState) => T): T { const subscribe = useCallback( @@ -26,22 +21,10 @@ export function useSelector(store: S, selector: (state: I } /** - * Returns the request map from the store. - * - * @param store - The store instance - * @returns The request map + * Get request map or select a specific request. */ export function useRequest(store: S): InferStoreRequests; - -/** - * Returns a selected request from the store. - * - * @param store - The store instance - * @param selector - Function to select a request - * @returns The selected request - */ export function useRequest(store: S, selector: (requests: InferStoreRequests) => T): T; - export function useRequest( store: S, selector?: (requests: InferStoreRequests) => T, @@ -56,14 +39,9 @@ export function useRequest( } /** - * Subscribes to task state changes. - * Returns the current tasks map from the queue. - * - * @param store - The store instance - * @returns The tasks record + * Subscribe to task state changes. */ export function useTasks(store: S): TasksRecord> { - // Cache the tasks snapshot to ensure referential stability const tasksRef = useRef(store.queue.tasks); const subscribe = useCallback( diff --git a/packages/store/tsdown.config.ts b/packages/store/tsdown.config.ts index 3fe977ab..3b13088f 100644 --- a/packages/store/tsdown.config.ts +++ b/packages/store/tsdown.config.ts @@ -14,7 +14,5 @@ export default defineConfig({ alias: { '@': new URL('./src/core', import.meta.url).pathname, }, - dts: { - oxc: true, - }, + dts: true, }); diff --git a/packages/utils/package.json b/packages/utils/package.json index f10794b5..a8dd4216 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -51,7 +51,7 @@ "clean": "rm -rf dist types" }, "devDependencies": { - "tsdown": "^0.15.9", + "tsdown": "^0.15.12", "typescript": "^5.9.3", "vitest": "^3.2.4" }, diff --git a/packages/utils/src/dom/animation-frame.ts b/packages/utils/src/dom/animation-frame.ts index fc2860a4..f2c9ea8c 100644 --- a/packages/utils/src/dom/animation-frame.ts +++ b/packages/utils/src/dom/animation-frame.ts @@ -1,17 +1,10 @@ /** - * Request an animation frame and return a cleanup function to cancel it. - * - * @param callback - The callback to invoke on the next animation frame - * @returns A cleanup function that cancels the animation frame request + * Request an animation frame with cleanup. * * @example * ```ts - * const cancel = animationFrame((time) => { - * console.log('Frame at', time); - * }); - * - * // Later, cancel if needed - * cancel(); + * const cancel = animationFrame((time) => console.log('Frame at', time)); + * cancel(); // Cancel if needed * ``` */ export function animationFrame(callback: FrameRequestCallback): () => void { diff --git a/packages/utils/src/dom/event.ts b/packages/utils/src/dom/event.ts index cc8b5b0c..9382257d 100644 --- a/packages/utils/src/dom/event.ts +++ b/packages/utils/src/dom/event.ts @@ -10,10 +10,10 @@ export interface OnEventOptions extends AddEventListenerOptions { /** * Wait for an event to occur on a target. * - * @param target - The event target (HTMLMediaElement) - * @param type - The event type to wait for - * @param options - Optional event options including AbortSignal - * @returns A promise that resolves with the event + * @example + * ```ts + * const event = await onEvent(video, 'seeked'); + * ``` */ export function onEvent( target: HTMLMediaElement, @@ -21,79 +21,24 @@ export function onEvent( options?: OnEventOptions, ): Promise; -/** - * Wait for an event to occur on a target. - * - * @param target - The event target (HTMLElement) - * @param type - The event type to wait for - * @param options - Optional event options including AbortSignal - * @returns A promise that resolves with the event - */ export function onEvent( target: HTMLElement, type: K, options?: OnEventOptions, ): Promise; -/** - * Wait for an event to occur on a target. - * - * @param target - The event target (Window) - * @param type - The event type to wait for - * @param options - Optional event options including AbortSignal - * @returns A promise that resolves with the event - */ export function onEvent( target: Window, type: K, options?: OnEventOptions, ): Promise; -/** - * Wait for an event to occur on a target. - * - * @param target - The event target (Document) - * @param type - The event type to wait for - * @param options - Optional event options including AbortSignal - * @returns A promise that resolves with the event - */ export function onEvent( target: Document, type: K, options?: OnEventOptions, ): Promise; -/** - * Wait for an event to occur on a target. - * - * @param target - The event target - * @param type - The event type to wait for - * @param options - Optional event options including AbortSignal - * @returns A promise that resolves with the event - * - * @example - * ```ts - * // Wait for video to be seeked - * const event = await onEvent(video, 'seeked'); - * ``` - * - * @example - * ```ts - * // With AbortSignal for cancellation - * const controller = new AbortController(); - * - * try { - * const event = await onEvent(video, 'seeked', { signal: controller.signal }); - * } catch (e) { - * if (e.name === 'AbortError') { - * console.log('Cancelled waiting for event'); - * } - * } - * - * // Cancel from elsewhere - * controller.abort(); - * ``` - */ export function onEvent(target: EventTarget, type: string, options?: OnEventOptions): Promise; export function onEvent(target: EventTarget, type: string, options?: OnEventOptions): Promise { diff --git a/packages/utils/src/dom/idle-callback.ts b/packages/utils/src/dom/idle-callback.ts index 60bed0b9..d871e6ad 100644 --- a/packages/utils/src/dom/idle-callback.ts +++ b/packages/utils/src/dom/idle-callback.ts @@ -1,29 +1,12 @@ import { supportsIdleCallback } from './supports'; /** - * Request an idle callback and return a cleanup function to cancel it. - * - * Falls back to `setTimeout` with 1ms delay in environments that don't - * support `requestIdleCallback` (e.g., Safari). - * - * @param callback - The callback to invoke when the browser is idle - * @param options - Optional idle callback options (timeout, etc.) - * @returns A cleanup function that cancels the idle callback request + * Request an idle callback with cleanup. Falls back to setTimeout for Safari. * * @example * ```ts - * const cancel = idleCallback((deadline) => { - * console.log('Time remaining:', deadline.timeRemaining()); - * }); - * - * // Later, cancel if needed - * cancel(); - * ``` - * - * @example - * ```ts - * // With timeout option * const cancel = idleCallback(doWork, { timeout: 1000 }); + * cancel(); // Cancel if needed * ``` */ export function idleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): () => void { diff --git a/packages/utils/src/dom/listen.ts b/packages/utils/src/dom/listen.ts index ee49a66c..24aa1ca7 100644 --- a/packages/utils/src/dom/listen.ts +++ b/packages/utils/src/dom/listen.ts @@ -1,11 +1,11 @@ /** * Add an event listener and return a cleanup function to remove it. * - * @param target - The event target (HTMLMediaElement) - * @param type - The event type - * @param listener - The event listener - * @param options - Optional event listener options - * @returns A cleanup function that removes the event listener + * @example + * ```ts + * const cleanup = listen(video, 'play', () => console.log('playing')); + * cleanup(); // Remove listener + * ``` */ export function listen( target: HTMLMediaElement, @@ -14,15 +14,6 @@ export function listen( options?: AddEventListenerOptions, ): () => void; -/** - * Add an event listener and return a cleanup function to remove it. - * - * @param target - The event target (HTMLElement) - * @param type - The event type - * @param listener - The event listener - * @param options - Optional event listener options - * @returns A cleanup function that removes the event listener - */ export function listen( target: HTMLElement, type: K, @@ -30,15 +21,6 @@ export function listen( options?: AddEventListenerOptions, ): () => void; -/** - * Add an event listener and return a cleanup function to remove it. - * - * @param target - The event target (Window) - * @param type - The event type - * @param listener - The event listener - * @param options - Optional event listener options - * @returns A cleanup function that removes the event listener - */ export function listen( target: Window, type: K, @@ -46,15 +28,6 @@ export function listen( options?: AddEventListenerOptions, ): () => void; -/** - * Add an event listener and return a cleanup function to remove it. - * - * @param target - The event target (Document) - * @param type - The event type - * @param listener - The event listener - * @param options - Optional event listener options - * @returns A cleanup function that removes the event listener - */ export function listen( target: Document, type: K, @@ -62,39 +35,6 @@ export function listen( options?: AddEventListenerOptions, ): () => void; -/** - * Add an event listener and return a cleanup function to remove it. - * - * @param target - The event target - * @param type - The event type - * @param listener - The event listener - * @param options - Optional event listener options - * @returns A cleanup function that removes the event listener - * - * @example - * ```ts - * const cleanup = listen(video, 'play', () => console.log('playing')); - * - * // Later, remove the listener - * cleanup(); - * ``` - * - * @example - * ```ts - * // With options - * const cleanup = listen(video, 'play', handler, { once: true, passive: true }); - * ``` - * - * @example - * ```ts - * // With AbortSignal (native browser support) - * const controller = new AbortController(); - * listen(video, 'play', handler, { signal: controller.signal }); - * - * // Later, abort to remove the listener - * controller.abort(); - * ``` - */ export function listen( target: EventTarget, type: string, diff --git a/packages/utils/src/dom/supports.ts b/packages/utils/src/dom/supports.ts index 62848a44..b7c0bc1b 100644 --- a/packages/utils/src/dom/supports.ts +++ b/packages/utils/src/dom/supports.ts @@ -1,17 +1,7 @@ -/** - * Check if `requestIdleCallback` is supported. - * - * @returns `true` if `requestIdleCallback` is available - */ export function supportsIdleCallback(): boolean { return typeof requestIdleCallback === 'function'; } -/** - * Check if `requestAnimationFrame` is supported. - * - * @returns `true` if `requestAnimationFrame` is available - */ export function supportsAnimationFrame(): boolean { return typeof requestAnimationFrame === 'function'; } diff --git a/packages/utils/src/dom/time-ranges.ts b/packages/utils/src/dom/time-ranges.ts index d4e1d350..72f0f433 100644 --- a/packages/utils/src/dom/time-ranges.ts +++ b/packages/utils/src/dom/time-ranges.ts @@ -1,9 +1,4 @@ -/** - * Converts a TimeRanges object to an array of [start, end] tuples. - * - * @param ranges - The TimeRanges object to serialize - * @returns An array of [start, end] tuples - */ +/** Converts a TimeRanges object to an array of [start, end] tuples. */ export function serializeTimeRanges(ranges: TimeRanges): Array<[number, number]> { const result: Array<[number, number]> = []; diff --git a/packages/utils/src/events/disposer.ts b/packages/utils/src/events/disposer.ts index 390a5baa..e7cf7323 100644 --- a/packages/utils/src/events/disposer.ts +++ b/packages/utils/src/events/disposer.ts @@ -25,26 +25,15 @@ export type CleanupFn = () => void | Promise; export class Disposer { #cleanups = new Set(); - /** - * Number of registered cleanup functions. - */ get size(): number { return this.#cleanups.size; } - /** - * Add a cleanup function to the collection. - */ add(cleanup: CleanupFn): void { this.#cleanups.add(cleanup); } - /** - * Run all cleanup functions synchronously. - * - * Note: If any cleanup functions return promises, they will not be awaited. - * Use `disposeAsync()` if you have async cleanup functions. - */ + /** Run all cleanups sync. Use `disposeAsync()` for async cleanups. */ dispose(): void { for (const cleanup of this.#cleanups) { cleanup(); @@ -52,9 +41,6 @@ export class Disposer { this.#cleanups.clear(); } - /** - * Run all cleanup functions, awaiting any promises. - */ async disposeAsync(): Promise { await Promise.all([...this.#cleanups].map(cleanup => cleanup())); this.#cleanups.clear(); diff --git a/packages/utils/src/function/try-catch.ts b/packages/utils/src/function/try-catch.ts index cb2eb72a..0f65a1df 100644 --- a/packages/utils/src/function/try-catch.ts +++ b/packages/utils/src/function/try-catch.ts @@ -1,10 +1,6 @@ /** * Wrap a function to catch and handle errors instead of throwing. * - * @param fn - Function to wrap (can be undefined) - * @param onError - Error handler (defaults to console.error) - * @returns Wrapped function that never throws, or undefined if fn is undefined - * * @example * ```ts * const safeFn = tryCatch(riskyFn, (e) => logger.error(e)); diff --git a/packages/utils/src/object/selector.ts b/packages/utils/src/object/selector.ts index ab44e22d..67bd8067 100644 --- a/packages/utils/src/object/selector.ts +++ b/packages/utils/src/object/selector.ts @@ -6,18 +6,10 @@ import { isObject } from '../predicate/predicate'; export type Selector = (state: State) => Selected; /** - * Extracts the state keys a selector depends on by running it once - * and inspecting the result object's keys. - * - * Returns `null` if the selector returns a primitive or array - * (keys cannot be determined). + * Extract state keys a selector depends on. Returns null for primitives/arrays. * * @example - * const selector = (s: State) => ({ volume: s.volume, muted: s.muted }); - * getSelectorKeys(selector, state); // ['volume', 'muted'] - * - * const primitiveSelector = (s: State) => s.volume; - * getSelectorKeys(primitiveSelector, state); // null + * getSelectorKeys((s) => ({ volume: s.volume }), state); // ['volume'] */ export function getSelectorKeys( selector: Selector, diff --git a/packages/utils/src/object/tests/pick.test.ts b/packages/utils/src/object/tests/pick.test.ts index d0aa74de..1cf14cbe 100644 --- a/packages/utils/src/object/tests/pick.test.ts +++ b/packages/utils/src/object/tests/pick.test.ts @@ -19,8 +19,8 @@ describe('pick', () => { }); it('ignores non-existent keys', () => { - const obj = { a: 1, b: 2 } as Record; - expect(pick(obj, ['a', 'nonexistent'] as (keyof typeof obj)[])).toEqual({ a: 1 }); + const obj = { a: 1, b: 2 }; + expect(pick(obj, ['a', 'nonexistent' as keyof typeof obj])).toEqual({ a: 1 }); }); it('handles nested objects (shallow copy)', () => { diff --git a/packages/utils/tsdown.config.ts b/packages/utils/tsdown.config.ts index 168ee69f..ca4b72a0 100644 --- a/packages/utils/tsdown.config.ts +++ b/packages/utils/tsdown.config.ts @@ -17,7 +17,5 @@ export default defineConfig({ alias: { '@': new URL('./src', import.meta.url).pathname, }, - dts: { - oxc: true, - }, + dts: true, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdf92b13..60ccc342 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -319,7 +319,7 @@ importers: specifier: ^26.1.0 version: 26.1.0 tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 @@ -341,7 +341,7 @@ importers: version: link:../utils devDependencies: tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 @@ -350,7 +350,7 @@ importers: packages/icons: devDependencies: tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 @@ -375,7 +375,7 @@ importers: specifier: ^18.0.0 version: 18.3.1 tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 @@ -409,7 +409,7 @@ importers: specifier: ^19.2.1 version: 19.2.3(react@19.2.3) tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 @@ -421,7 +421,7 @@ importers: packages/utils: devDependencies: tsdown: - specifier: ^0.15.9 + specifier: ^0.15.12 version: 0.15.12(typescript@5.9.3) typescript: specifier: ^5.9.3 diff --git a/tsconfig.base.json b/tsconfig.base.json index 4466781e..c38bbe72 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -25,7 +25,7 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "isolatedDeclarations": true, + "isolatedDeclarations": false, "isolatedModules": true, "verbatimModuleSyntax": true, "skipLibCheck": true