LobeHub TypeScript style and type-safety guide. Use when editing TS/TSX/MTS, fixing types, choosing interface vs type, avoiding any/object, import type, async flow, or ts-expect-error.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
LobeHub TypeScript style and type-safety guide. Use when editing TS/TSX/MTS, fixing types, choosing interface vs type, avoiding any/object, import type, async flow, or ts-expect-error.
user-invocable
false
TypeScript Code Style Guide
Types and Type Safety
Avoid explicit type annotations when TypeScript can infer
Avoid implicitly any; explicitly type when necessary
Use accurate types: prefer Record<PropertyKey, unknown> over object or any
Prefer interface for object shapes (e.g., React props); use type for unions/intersections
Prefer as const satisfies XyzInterface over plain as const
Prefer @ts-expect-error over @ts-ignore over as any
Avoid meaningless null/undefined parameters; design strict function contracts
Prefer ES module augmentation (declare module '...') over namespace; do not introduce namespace-based extension patterns
When a type needs extensibility, expose a small mergeable interface at the source type and let each feature/plugin augment it locally instead of centralizing all extension fields in one registry file
For package-local extensibility patterns like PipelineContext.metadata, define the metadata fields next to the processor/provider/plugin that reads or writes them
Async Patterns
Prefer async/await over callbacks or .then() chains
Prefer async APIs over sync ones (avoid *Sync)
Use promise-based variants: import { readFile } from 'fs/promises'
Use Promise.all, Promise.race for concurrent operations where safe
Imports
This project uses simple-import-sort/imports and consistent-type-imports (fixStyle: 'separate-type-imports')
Separate type imports: always use import type { ... } for type-only imports, NOT import { type ... } inline syntax
When a file already has import type { ... } from a package and you need to add a value import, keep them as two separate statements:
Within each import statement, specifiers are sorted alphabetically by name
Code Structure
Prefer object destructuring
Use consistent, descriptive naming; avoid obscure abbreviations
Replace magic numbers/strings with well-named constants
Defer formatting to tooling
Prefer named exports over export default — keeps refactor renames and IDE auto-import in sync, and avoids the default re-naming drift you get with import Foo from './foo'. Reserve export default for files where the framework requires it (Next.js page/route/layout, React.lazy targets, config files like vitest.config.ts). The codebase still has many export default occurrences — that's historical debt, not a pattern to copy; do not model new code on existing export default usage outside the framework-required cases above
Before adding local helpers for common guards/parsing/normalization (record checks, string extraction, empty-string handling, timing helpers, JSON-safe utilities, etc.), search packages/utils first. If the helper already exists or clearly belongs there, import it from @lobechat/utils (or the relevant @lobechat/utils/* subpath) instead of duplicating tiny helpers across feature files.
UI and Theming
Use @lobehub/ui, Ant Design components instead of raw HTML tags
Design for dark mode and mobile responsiveness
Use antd-style token system instead of hard-coded colors
Performance
Query only required columns from database
Reusability
Reuse existing utils in packages/utils or installed npm packages
Do not hand-roll reusable record/object-map guards such as typeof value === 'object' && value !== null; import helpers like isRecord, isPlainRecord, isObjectLike, toRecord, pickString, UnknownRecord, etc. from @lobechat/utils/object.
Assign Date.now() to a constant once and reuse for consistency
Logging
Never log user private information (API keys, etc.)
Don't use import { log } from 'debug' directly (logs to console)
Use console.error in catch blocks instead of debug package
Always log the error in .catch() callbacks — silent .catch(() => fallback) swallows failures and makes debugging impossible