fetcher-decorator-service ยท diff
git:20260924.71dc2fb to git:20260924.b36c6c8
50 added, 14 removed. Audit A to A.
---
name: fetcher-decorator-service
description: >
- Build declarative TypeScript API services with `@ahoo-wang/fetcher-decorator`: `@api`, HTTP and parameter decorators, result extractors, lifecycle hooks, inheritance, and generated method stubs. Use when a task needs decorator-based services instead of hand-written Fetcher calls.
+ Declare typed API service classes with `@ahoo-wang/fetcher-decorator`: `@api`, `@get`/`@post`/`@put`/`@patch`/`@del`, `@path`/`@query`/`@header`/`@body`, per-endpoint result extractors and `ExecuteLifeCycle` hooks. Use when endpoints are stable and should read as a class. For client setup or interceptors use fetcher-integration; for React state use fetcher-react-hooks.
---
# fetcher-decorator-service
- ## Workflow
+ ## Decisions
- 1. `reflect-metadata` ships with the package and is imported automatically; an explicit entry-point import is only needed for other decorator libraries.
- 2. Define a service class with `@api()` and method decorators; leave method bodies as generated-error stubs.
- 3. Bind path, query, header, body, request, and attribute values with parameter decorators.
- 4. Choose the result extractor or endpoint return type that matches the caller contract.
- 5. Load `references/api.md` for decorator signatures, lifecycle hooks, inheritance, and CRUD examples.
+ - **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 at call time, falling back to `fetcherRegistrar.default`. An instance `apiMetadata` field overrides class options, but is read once per method on its first call.
+ - **Cross-cutting behaviour** (auth, retry, recovery) stays in fetcher interceptors. `afterExecute` runs only after a successful exchange โ with the default status validation a 401 throws before it.
- ## Key Practices
+ ## Gotchas a capable model gets wrong
- - Prefer decorator services when endpoint shape is stable and discoverability matters.
- - Use core Fetcher directly for highly dynamic request construction.
- - Keep `reflect-metadata` setup out of individual service modules unless the app has no shared entry point.
+ - Legacy decorators: the app needs `experimentalDecorators: true`. `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; object (and array) arguments to `@query`/`@path`/`@header` are spread into keys.
+ - An `AbortSignal` or `AbortController` argument is detected automatically; a signal disables the fetcher timeout for that call.
+ - A subclass inherits endpoints but uses its own `@api` base path โ inherited paths must still resolve there.
+ ## 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`: Detailed package API, examples, and edge-case guidance. Load it only when the task needs decorator signatures, parameter mapping, lifecycle hooks, inheritance rules, error stubs, and complete CRUD service examples.
+ - `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: Use for the underlying Fetcher and NamedFetcher setup.
- - $fetcher-llm-streaming: Use when a decorator endpoint returns SSE or streaming data.
+ - $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.