SuperAPI AI Directory

typescript-frameworks

Coding & development

View repository

Updated 10-15 20:31

About this skill

TypeScript Frameworks

A framework calls the user's code. Convention, lifecycle, and extension points beat a large "kit of helpers". Publishable packages and exports: skill typescript-libraries. Types: typescript-strict.

Lib vs framework

Library Framework
Control Consumer calls Framework calls (IoC)
Shape Small functions/types App shape, lifecycle, plugins
Config Arguments Convention + typed config + escape hatches
Success Minimal and stable API DX: right defaults, actionable errors, safe extension

If the user only needs functions, don't invent a framework.

Typical stack

Piece Role
TypeScript Public contracts, conscious module augmentation
Plugin API setup / hooks / resolveId — the domain one
Config schema Zod/Valibot/TypeBox at boot
CLI (optional) create-*, dev, build
Test harness Minimal app + fake plugin

npm delivery follows typescript-libraries (exports, peers, semver).

Organization

src/
  index.ts              # API pública: createApp, definePlugin, tipos
  core/                 # lifecycle, DI, graph
  plugins/              # plugins oficiais (opcionais via subpath)
  config.ts             # load + validate
  errors.ts             # erros com hint
packages/               # monorepo: core, cli, plugin-*
create-foo/             # scaffold
  • Small core. Official features are plugins (even if first-party).
  • The user does not import core/internal.
  • File conventions (app/, plugins/, routes/) documented and stable.

Non-negotiable rules

  • Lifecycle order documented and tested (register → boot → ready → close).
  • Plugins are composition, not monkey-patching of internals.
  • Config validated at boot; fails with field path and expected value.
  • Public API vs experimental vs @internal — no gray area.
  • Breaking a hook/config = major. Prefer adding a hook to changing the payload.
  • Errors say what to do (file, option, link). Not undefined is not a function at the edge.
  • Explicit escape hatch (hooks, raw options) better than "read the core source".
  • Defaults that work locally; no 40 mandatory flags in the create.

Lifecycle and IoC

The framework owns the flow. The user fills slots.

export interface Plugin<TOptions = unknown> {
  name: string
  setup?: (ctx: FrameworkContext, options: TOptions) => void | Promise<void>
}

export function definePlugin<T>(plugin: Plugin<T>): Plugin<T> {
  return plugin
}

// createApp() registra plugins, valida config, dispara hooks na ordem.
  • definePlugin / defineConfig exist for inference, not for magic.
  • Context (app, config, hooks, logger) is the unit of extension — not globals.
  • Hooks: stable names (build:done), versioned payload. Avoid on('*') as public API.
  • Async: await hooks in series when order matters; parallel only if independent and documented.
  • Shutdown: close/dispose in reverse boot order; plugins don't leak handles.

Convention and config

  • Convention over configuration: default folders and names; config only for deviation.
  • One config object typed (FooConfig); nested by domain, not 200 keys at the root.
  • satisfies FooConfig / defineConfig() for autocomplete without any.
  • Framework feature flags are stable or experimental — not “eternal beta” in the default API.
  • Path resolution relative to the project root, not to the accidental cwd .

Types as contract

  • The user extends via generics of the createApp or via documented module augmentation (declare module 'foo').
  • Don't abuse declaration merging for implicit global state.
  • Plugins that add fields to the app must type that augmentation (the plugin's opt-in declaration merging).
  • Avoid any in the ctx; a poorly typed plugin contaminates the entire app.
  • Generics flow from config → app → plugins; sensible defaults.

DX

  • Messages: problem + location + fix. E.g.: “Plugin auth duplicated. Use a unique name or enforce: 'pre'.”
  • Stack traces point to the user's file, not just to the minified core (source maps in the CLI).
  • Scaffold (create-foo) generates the happy path: tsconfig, script dev, an example plugin.
  • Docs: lifecycle, plugin order, “how to write a plugin”, what is stable.
  • Fast dev mode; graph cache with explicit invalidation.

Official plugins vs ecosystem

  • First-party: reference quality (types, tests, errors).
  • Third-party: stable contract; no importing internals (foo/dist/core.js).
  • Don't version a plugin against the core's * — peer ^ on the current major.
  • Isolation: plugin A doesn't break B; order enforce: 'pre' | 'post' only when the graph requires it.

Tests

  • Harness: createTestApp({ plugins }) in-process.
  • Cover: hook order, config failure, duplicate plugin, close/dispose, type extension (compilation test).
  • Snapshot of error messages stable (without absolute machine paths).

Anti-patterns

  • God config: Record<string, any>
  • Plugin that imports the core's internal files
  • Undocumented lifecycle / different order in dev vs prod
  • Turning the framework into a language (heavy DSL) without need
  • Silent breaking of hooks in minor
  • Global singleton (getApp()) as the only form of extension
  • Scaffold that generates deprecated code
  • “80-helper lib” sold as a framework

Integration with other skills

  • Package and publish: typescript-libraries
  • Strict types and schemas: typescript-strict
  • App CLI vs lib: preserve the repo's bundler
  • HTTP if the framework is for server: api-design
  • UI kit: react-component-engineering / design-system — do not mix with the runtime core

Completion criteria

  • create* + definePlugin/defineConfig typed
  • Documented, tested lifecycle, with shutdown
  • Config validated with actionable errors
  • Stable vs experimental surface marked
  • Plugins do not depend on internals
  • Scaffold or minimal example boots the happy path
  • Packaging aligned with typescript-libraries