Declare HTTP endpoints as a typed class with `@ahoo-wang/fetcher-decorator`: `@api`, `@get`/`@post`/`@put`/`@patch`/`@del`, `@path`/`@query`/`@header`/`@body`, return types, result extractors, `beforeExecute`/`afterExecute`. Use when writing or debugging a decorated service class. Direct `fetcher.get/post` calls, URLs built at runtime, client setup or interceptors (even for decorated services): fetcher-integration; React state: fetcher-react-hooks.
Installs into .claude/skills of the current project.
Are you the author of Fetcher Decorator Service?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ahoo-wang-fetcher-decorator-service)
---
name: fetcher-decorator-service
description: >
Declare HTTP endpoints as a typed class with `@ahoo-wang/fetcher-decorator`: `@api`, `@get`/`@post`/`@put`/`@patch`/`@del`, `@path`/`@query`/`@header`/`@body`, return types, result extractors, `beforeExecute`/`afterExecute`. Use when writing or debugging a decorated service class. Direct `fetcher.get/post` calls, URLs built at runtime, client setup or interceptors (even for decorated services): fetcher-integration; React state: fetcher-react-hooks.
---
# fetcher-decorator-service
## Decisions
- **Decorator service vs direct calls**: use a class when endpoints are stable and discoverable; call the fetcher directly (`$fetcher-integration`) when URLs or bodies are built dynamically.
- **Which fetcher**: `@api(basePath, { fetcher: 'api' })` resolves a `NamedFetcher` by name (or takes a `Fetcher` instance) on every call, falling back to `fetcherRegistrar.default`, which throws if nothing is named `'default'`. An instance `apiMetadata` field is shallow-merged over the class options on every call, so replacing or mutating it applies to the next call.
- **Cross-cutting behaviour** (auth, retry, 401 redirects) stays in fetcher interceptors (`$fetcher-integration`). `afterExecute` runs only after a successful (or interceptor-recovered) exchange — it is not a `finally`, and under the default status validation a 401 rejects with `HttpStatusValidationError` before it.
## Gotchas a capable model gets wrong
- Legacy decorators: the app needs `experimentalDecorators: true` (not `emitDecoratorMetadata`); compiled as standard decorators, `@api` throws a TypeError saying so. `reflect-metadata` ships with the package and is imported by it; no entry-point import is needed.
- Method bodies are placeholders: `throw autoGeneratedError(...args)`. If that error is actually thrown at runtime, `@api` or the method decorator is missing.
- DELETE is `@del` (not `@delete`); there is no `@trace` — use `@endpoint(HttpMethod.TRACE, path)`.
- Decorated methods default to `JsonResultExtractor` and `EndpointReturnType.RESULT`, unlike core `fetcher.get()` which returns a `Response`. Use `{ returnType: EndpointReturnType.EXCHANGE }` or a `resultExtractor` per endpoint to change it.
- Parameter names are read from the function source when omitted, which minifiers break: always pass names (`@path('id')`). `null`/`undefined` arguments are skipped. A plain object passed to `@query`/`@path`/`@header` is spread into keys; an array, `Date` or other value is bound to the parameter name (`@query('ids') ids: number[]` → `ids=1&ids=2`; an array header is comma-joined).
- An `AbortSignal` or `AbortController` argument is detected automatically; it applies together with the fetcher timeout (whichever fires first aborts).
- A subclass inherits endpoints but uses its own `@api` base path — inherited paths must still resolve there. Overriding an inherited endpoint without an endpoint decorator keeps the override's own code.
- A method called without its instance (`const { getUser } = service`) throws a TypeError; bind it or call it on the service.
## Minimal example
```ts
import { NamedFetcher } from '@ahoo-wang/fetcher';
import {
api,
autoGeneratedError,
body,
get,
path,
post,
query,
} from '@ahoo-wang/fetcher-decorator';
new NamedFetcher('api', { baseURL: 'https://api.example.com' });
@api('/users', { fetcher: 'api' })
export class UserService {
@get('/{id}')
getUser(@path('id') id: string): Promise<User> {
throw autoGeneratedError(id);
}
@get('')
list(@query('page') page?: number): Promise<User[]> {
throw autoGeneratedError(page);
}
@post('')
create(@body() user: Omit<User, 'id'>): Promise<User> {
throw autoGeneratedError(user);
}
}
```
## References
- `references/api.md`: decorator signatures, parameter binding rules, execution flow, lifecycle hooks, inheritance and CRUD examples. Load it for anything beyond the example above.
## Related Skills
- $fetcher-integration: the `Fetcher`/`NamedFetcher` and interceptors underneath.
- $fetcher-llm-streaming: endpoints that return SSE streams.
- $fetcher-react-hooks: `createExecuteApiHooks` / `createQueryApiHooks` turn a service into hooks.