mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
229 lines
8.8 KiB
Markdown
229 lines
8.8 KiB
Markdown
# Util Reference Conventions
|
|
|
|
Conventions for the util reference system that documents React hooks/utilities and HTML controllers/mixins.
|
|
|
|
## Architecture
|
|
|
|
Util references use **convention-based auto-discovery** from package index files. The builder scans entry points, resolves local module paths, and includes exports matching naming conventions or annotated with `@public`.
|
|
|
|
## Auto-Discovery Pipeline
|
|
|
|
The builder (`site/scripts/api-docs-builder/src/util-handler.ts`) scans entry points:
|
|
|
|
```ts
|
|
packages/react/src/index.ts → framework: 'react'
|
|
packages/store/src/react/hooks/index.ts → framework: 'react'
|
|
packages/html/src/index.ts → framework: 'html'
|
|
packages/store/src/html/controllers/index.ts → framework: 'html'
|
|
packages/core/src/dom/store/selectors.ts → framework: null (agnostic)
|
|
packages/store/src/core/selector.ts → framework: null (agnostic)
|
|
```
|
|
|
|
Framework-agnostic entries (`framework: null`) produce JSON without a `frameworks` field, meaning they apply to all frameworks. Framework-specific entries get `frameworks: ['react']` or `frameworks: ['html']` in the JSON.
|
|
|
|
**Phase 1 — Resolve local modules.** Raw TS AST reads export declarations from each index file, keeping only local paths (`./...`), skipping external packages (`@videojs/...`).
|
|
|
|
**Phase 2 — Filter by convention.** Each local module is parsed with TAE (typescript-api-extractor) or raw TS AST. Exports are included if they match naming conventions or have `@public`.
|
|
|
|
### Adding a New Util
|
|
|
|
1. Export it from the appropriate package index file
|
|
2. Add JSDoc with a description
|
|
3. If it doesn't match a naming convention (see below), add `@public` to the JSDoc
|
|
4. Run `pnpm api-docs` to generate its JSON
|
|
4. Create an MDX page with `<UtilReference util="{Name}" />`
|
|
5. Add to the sidebar in `docs.config.ts`
|
|
|
|
No code changes needed in the builder itself — convention over configuration.
|
|
|
|
## JSDoc Conventions
|
|
|
|
The builder extracts JSDoc from source exports to populate reference pages. These rules override the root CLAUDE.md "Minimal JSDoc" guidelines for API reference exports.
|
|
|
|
### Summary description (required)
|
|
|
|
Every util export needs a JSDoc summary. This becomes the description in the generated JSON:
|
|
|
|
```ts
|
|
/** Subscribe to the player's volume state. */
|
|
export function useVolume(...): VolumeResult;
|
|
```
|
|
|
|
### `@param` descriptions (required for non-obvious params)
|
|
|
|
Unlike internal code, API reference exports need `@param` tags so the builder can populate parameter tables. Describe intent and defaults, not types:
|
|
|
|
```ts
|
|
/**
|
|
* Subscribe to derived state with customizable equality check.
|
|
*
|
|
* @param subscribe - Subscribe function that returns an unsubscribe callback.
|
|
* @param selector - Derives a value from the snapshot.
|
|
* @param isEqual - Custom equality function. Defaults to `shallowEqual`.
|
|
*/
|
|
export function useSelector<S, R>(...): R;
|
|
```
|
|
|
|
Format: `@param name - description` (dash after name).
|
|
|
|
### No `@returns`
|
|
|
|
Return types are inferred from the TypeScript signature. Don't add `@returns`.
|
|
|
|
### `@label` for multi-overload functions
|
|
|
|
When a function has multiple overload signatures with different return types, each overload gets its own JSDoc block with an `@label` tag. The label becomes a heading in the docs:
|
|
|
|
```ts
|
|
/**
|
|
* Create a player instance with typed store, Provider, and hooks.
|
|
*
|
|
* @label Video
|
|
* @param config - Player configuration with features.
|
|
*/
|
|
export function createPlayer(config: CreatePlayerConfig<VideoFeatures>): CreatePlayerResult<VideoPlayerStore>;
|
|
|
|
/**
|
|
* Create a player for audio media.
|
|
*
|
|
* @label Audio
|
|
* @param config - Player configuration with features.
|
|
*/
|
|
export function createPlayer(config: CreatePlayerConfig<AudioFeatures>): CreatePlayerResult<AudioPlayerStore>;
|
|
```
|
|
|
|
Without `@label`, overloads render as "Overload 1", "Overload 2", etc.
|
|
|
|
### `@label` for constructor overloads
|
|
|
|
Same pattern applies to controller constructors:
|
|
|
|
```ts
|
|
/**
|
|
* @label Without Selector
|
|
* @param host - The host element that owns this controller.
|
|
* @param state - The State container to subscribe to.
|
|
*/
|
|
constructor(host: ReactiveControllerHost, state: State<T>);
|
|
|
|
/**
|
|
* @label With Selector
|
|
* @param host - The host element that owns this controller.
|
|
* @param state - The State container to subscribe to.
|
|
* @param selector - Derives a value from the state.
|
|
*/
|
|
constructor(host: ReactiveControllerHost, state: State<T>, selector: Selector<T, R>);
|
|
```
|
|
|
|
### `@public` for non-convention exports
|
|
|
|
Exports that don't match a naming convention (`use*`, `*Controller`, `create*`, `select*`) need `@public` to be discovered:
|
|
|
|
```ts
|
|
/** @public The default player context for consuming the player store. */
|
|
export const playerContext = createContext<PlayerContextValue>(...);
|
|
```
|
|
|
|
## Inclusion Conventions
|
|
|
|
Exports are auto-included when they match these patterns:
|
|
|
|
| Pattern | Match Rule | Examples |
|
|
|---------|-----------|----------|
|
|
| Hooks | Name starts with `use`, is a function | `usePlayer`, `useStore` |
|
|
| Controllers | Name ends with `Controller` | `PlayerController`, `StoreController` |
|
|
| Factories | Name starts with `create`, is a function | `createPlayer` |
|
|
| Mixin factories | Name starts with `create` + contains `Mixin` | `createProviderMixin` |
|
|
| `@public` | Has `@public` JSDoc tag | `playerContext`, `mergeProps`, `renderElement` |
|
|
|
|
Exports that don't match any convention are excluded (UI components, types, internal helpers).
|
|
|
|
## Slug Conventions
|
|
|
|
- Slugs are kebab-case: `use-player`, `player-controller`, `merge-props`
|
|
- All slugs must be unique across both frameworks
|
|
- When the same name exists in both React and HTML (e.g., `createPlayer`), prefix the HTML slug: `html-create-player`
|
|
|
|
## Frameworks
|
|
|
|
| Framework | Sidebar Section | Source Packages |
|
|
|-----------|----------------|-----------------|
|
|
| `react` | Hooks & Utilities | `@videojs/react`, `@videojs/store/react` |
|
|
| `html` | Controllers & Mixins | `@videojs/html`, `@videojs/store/html` |
|
|
| `null` (agnostic) | Selectors | `@videojs/core/dom`, `@videojs/store` |
|
|
|
|
Agnostic utils omit the `frameworks` field in JSON, meaning they're available to all frameworks.
|
|
|
|
## Overloads
|
|
|
|
Use multiple overloads when the return type genuinely differs between signatures:
|
|
|
|
```ts
|
|
// Two overloads — different return types
|
|
usePlayer() → PlayerStore
|
|
usePlayer(selector) → T (selected value)
|
|
```
|
|
|
|
For simple param-count differences with the same return type, use a single overload with optional params instead.
|
|
|
|
## Generated JSON
|
|
|
|
Output: `site/src/content/generated-util-reference/{slug}.json`
|
|
|
|
Schema: `UtilReferenceSchema` from `site/src/types/util-reference.ts`
|
|
|
|
```json
|
|
{
|
|
"name": "usePlayer",
|
|
"description": "...",
|
|
"overloads": [
|
|
{
|
|
"description": "...",
|
|
"parameters": { "selector": { "type": "...", "required": true } },
|
|
"returnValue": { "type": "...", "description": "..." }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Content Collection
|
|
|
|
The `utilReference` collection in `site/src/content.config.ts` loads from `generated-util-reference/` and validates against `UtilReferenceSchema`.
|
|
|
|
## Astro Components
|
|
|
|
| Component | Purpose |
|
|
|-----------|---------|
|
|
| `UtilReference.astro` | Main component — loads JSON, renders sections |
|
|
| `UtilParamsTable.astro` | Parameter table (reuses `PropRow.astro`) |
|
|
| `UtilReturnTable.astro` | Return value table (reuses `StateRow.astro` for object returns) |
|
|
|
|
## Common Failures
|
|
|
|
| Symptom | Cause |
|
|
|---------|-------|
|
|
| No JSON generated | Export not in a scanned index file, or doesn't match convention / lack `@public` |
|
|
| Zod validation error | Schema mismatch — check field names and types |
|
|
| Missing from sidebar | Not added to `docs.config.ts` in correct section |
|
|
| Page 404 | MDX file missing or slug mismatch |
|
|
| Wrong framework section | Export in wrong entry point — check which index file re-exports it |
|
|
| TAE crash on index file | Known issue with `UniqueESSymbol` types — raw TS AST fallback handles this |
|
|
|
|
## Discovery vs MDX Pages
|
|
|
|
Auto-discovery generates JSON files in `site/src/content/generated-util-reference/`. A JSON file without a corresponding MDX page is harmless — it sits unused.
|
|
|
|
Only utils with **both** generated JSON **and** a manually-created MDX page appear in the docs. This is intentional: discovery casts a wide net using naming conventions, while MDX pages are curated to document the public API surface.
|
|
|
|
For example, `SubscriptionController` is discovered (matches `*Controller`) but has no MDX page because it's an internal building block not intended for direct consumer use.
|
|
|
|
When adding a new util to the docs:
|
|
1. Ensure the builder discovers it (check with `pnpm api-docs`)
|
|
2. Create the MDX page at `site/src/content/docs/reference/{slug}.mdx`
|
|
3. Add to the sidebar in `docs.config.ts`
|
|
|
|
## Tests
|
|
|
|
- `site/scripts/api-docs-builder/src/tests/util-handler.test.ts` — fixture-based tests for discovery, slug uniqueness, frameworks, overloads
|
|
- `site/src/utils/tests/utilReferenceModel.test.ts` — validates model structure and TOC headings
|