@assemblerjs/fetch
v0.9.90
Published
Lightweight TypeScript decorator to attach HTTP request behavior to class methods for AssemblerJS.
Readme
@assemblerjs/fetch
Lightweight TypeScript decorator to attach HTTP request behavior to class methods for AssemblerJS.
This package exposes a Fetch method-decorator that builds and executes HTTP requests based on method arguments and parameter decorators (placeholders, params, queries). It resolves dynamic headers/body, runs a fetch, parses the response according to an optional parse decorator, and finally calls the original method with the network result.
Exported decorator
Fetch(method: string, path: string, options?: Omit<RequestInit, 'headers' | 'body'> & { headers?: HeadersInit | ((target: any) => HeadersInit | Promise); body?: any | ((target: any) => any | Promise) }, debug?: boolean): MethodDecorator
- method: HTTP method (e.g. 'GET', 'POST', 'PUT', 'PATCH', 'DELETE' ...). The decorator uppercases it internally.
- path: request path or URL. Path supports placeholders that will be replaced before the request (see placeholder/param/query below).
- options: partial RequestInit but with custom
headersandbodytyping:- headers can be a HeadersInit object or a function (target => HeadersInit | Promise) evaluated at call time.
- body can be a value or a function (target => body | Promise) evaluated at call time.
- other RequestInit keys (mode, credentials, cache...) are forwarded.
- debug: when true the decorator logs internal steps to console.
Parameter decorators (used by Fetch)
The fetch decorator relies on parameter-level metadata collected by parameter decorators. The implementation reads three categories:
- Placeholder: transforms an initial path using placeholder decorators (used before param substitution).
- Param: replaces named path segments (e.g.
:id) with parameter values. - Query: serializes arguments into query string parts appended to the path.
(See your package's parameter.decorator implementations for the exact decorator names; the fetch decorator expects metadata under three groups: placeholder, param, query.)
Response parsing
If a parse decorator is applied (metadata read via ReflectParse.ExpectedType), the Fetch pipeline will call parsing helpers:
- parseResponseWithType(response, type) when an expected ResponseMethod is set.
- parseResponseWithUnknownType(response) when no expected type is present.
These helpers are used to populate the data value passed to the original method.
Body selection rules
- If options.body is provided to Fetch and is a function, it will be invoked with the target to resolve the body; if it's a plain value it is used as-is.
- Otherwise, for body methods (POST, UPDATE, PATCH, PUT), the decorator picks the body value from the arguments at index
decoratedParametersLength(i.e., after all decorated parameters).
Asynchronous bodies (Promise or async value) are awaited before calling fetch.
Headers resolution
If options.headers is a function it will be awaited with the target to produce final headers; otherwise options.headers is used.
Error handling & result
- If fetch returns a non-OK response, an Error is attached to the
errorfield. - The decorator builds a result object containing: body, response, status (code + text), data, and error.
- After parsing, the original method is invoked with the final argument list: all original args (with undefined inserted for missing optional decorated parameters), then
data,error,status,finalPath.- Example final call: original.apply(this, [...resolvedArgs, data, error, status, finalPath])
Debugging
When the debug flag is true, the decorator logs internal pipeline steps (argument listing, placeholder/param/query transformations, resolved options, errors, final path).
@assemblerjs/fetch
Lightweight TypeScript decorators to attach HTTP request behavior to class methods for AssemblerJS.
This package provides a single method decorator Fetch plus a few parameter decorators and a Parse method decorator. The Fetch decorator builds a request URL using parameter decorators, resolves dynamic headers and body (functions are supported), performs a fetch call, parses the response (using an optional @Parse hint), and finally calls the original method with the network result appended to the arguments.
This README documents the public behaviour implemented in src/decorators/* and illustrated by the tests in src/decorators/decorator.spec.ts.
Install
npm install @assemblerjs/fetch
# or
yarn add @assemblerjs/fetchPublic API
All exports are re-exported from src/decorators/index.ts.
Fetch(method: string, path: string, options?: FetchOptions, debug?: boolean): MethodDecoratorQuery(name: string | symbol): ParameterDecoratorParam(name: string | symbol): ParameterDecoratorPlaceholder(token: string | symbol): ParameterDecoratorParse(type: ResponseMethod): MethodDecorator
Types (high level):
FetchOptions— same asRequestInitexcept:headers?: HeadersInit | ((target: any) => HeadersInit | Promise<HeadersInit>)body?: FetchResult['body'] | ((target: any) => FetchResult['body'] | Promise<FetchResult['body']>)
ResponseMethod— string union for response parsers:'text' | 'json' | 'blob' | 'arrayBuffer' | 'bytes' | 'formData'(seeParsedecorator)FetchStatus— { code: number; text: string }
See the source types in src/decorators/fetch.decorator.ts for full definitions.
Behaviour overview
Fetchconstructs a request pipeline composed of small transformation steps:- Resolve initial body: if
options.bodyis provided it is used (and may be a function invoked with the class instance), otherwise the body is taken from the method arguments after all decorated parameters. - Resolve placeholders (
@Placeholder) — these are replaced before param substitution. - Resolve path parameters (
@Param) —:nametokens are replaced by the corresponding argument value. - Resolve query parameters (
@Query) — the library serializes decorated arguments into the URL query string. - Resolve headers: if
options.headersis a function it is invoked with the class instance and awaited. - Await
bodyif it is async, then call globalfetch(resolvedPath, { ...options, method, body }). - If the response is not
ok, anErroris attached to the result (but the decorator still continues to parse the body). - Parse the response body using either the
@Parsehint (registeredResponseMethod) or the unknown-type parser. The helpers used by the decorator areparseResponseWithTypeandparseResponseWithUnknownType(implementation insrc/utils). - Finally the original method implementation is invoked with the original arguments (with
undefinedinserted for missing optional decorated params) and the following extra arguments appended:data, error, status, finalPath.
- Resolve initial body: if
Important notes derived from the implementation and tests:
- There is no
@Bodyparameter decorator. The body is either:- Provided via
options.bodywhen applying@Fetch(value or function), or - Inferred from the method arguments: the first non-decorated argument after all decorated parameters is used as the body for methods that typically carry a body (POST, UPDATE, PATCH, PUT).
- Provided via
@Paramvalues are replaced into the path; if you pass a parameter name without a leading:, the decorator ensures it matches:namein the path.@Placeholderaccepts arbitrary tokens (e.g.%kind) and replaces them before@Paramsubstitution; when the argument isundefined, the placeholder token is removed from the path.@Queryaccepts a name and maps the argument at the decorated index to a query parameter. Arrays are joined with commas.- Headers and body may be synchronous values or functions that receive the class instance; functions may return a Promise.
- The decorator uses the global
fetchavailable at runtime. There is no built-in dependency injection for an alternate fetch implementation in the code — for Node you should polyfillglobal.fetch(e.g.node-fetchorundici) before using the decorator.
Examples (taken from tests)
import { Fetch, Query, Param, Placeholder, Parse } from '@assemblerjs/fetch';
const apiHost = 'https://dummyjson.com';
class MyDummyUsersService {
@Fetch('get', `${apiHost}/users`, { mode: 'no-cors' })
@Parse('json')
public async getUsers(
@Query('limit') limit: number,
@Query('skip') skip: number,
@Query('select') select?: string[],
data?: any,
err?: Error
) {
// Decorator will append parsed `data` and `err` to call.
if (data && !err) return data;
throw err;
}
@Fetch('get', `${apiHost}/users/:id/carts`)
public async getUserCart(@Param('id') id: number, data?: any) {
if (data) return data;
}
@Fetch('get', `${apiHost}/users/:id/%kind`)
public async getSomethingFromUser(
@Param(':id') id: number,
@Placeholder('%kind') kind?: string,
data?: any
) {
return data;
}
@Fetch('post', `${apiHost}/users/add`, { headers: { Accept: 'application/json', 'Content-Type': 'application/json' } })
public addUser(body: string, data?: any) {
if (data) return data;
}
@Fetch('post', `${apiHost}/users/add`, {
headers: (target) => ({
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Random-Number': String(target.getRandomNumber()),
}),
body: (target) => JSON.stringify({ firstName: 'Dynamic', lastName: 'Header', age: 42 })
})
public async addDynamicUser(data?: any) {
return data;
}
}
const svc = new MyDummyUsersService();
await svc.getUsers(10, 0); // query params appended
await svc.getUserCart(6); // path param replaced
await svc.addUser(JSON.stringify({ firstName: 'Owen' })); // body inferredReturn/invocation shape
When the decorator invokes the original method it appends four additional arguments at the end of the call:
data— parsed response body (any)error— Error instance iffetchreturned non-ok status or if an internal error occurredstatus—{ code: number, text: string }finalPath— the final resolved URL used for the request
So if your original method signature is (...args), the decorator will call original.apply(this, [...resolvedArgs, data, error, status, finalPath]).
Error handling
- If
fetchreturns a non-ok response, the decorator will set anErroron theerrorvariable but still attempt to parse the response body. - The decorated method can either throw the
erroror returndataif appropriate (see tests for examples).
Tests & utilities
- The tests under
src/decorators/decorator.spec.tsillustrate expected behaviour and provide usage examples againsthttps://dummyjson.com. - Utility functions used by the decorator (response parsers and method registration) are in
src/utilsand are referenced by the tests (e.g. registering response method names for mime types).
Contributing
- Open a PR with tests for new behaviour in
src/decorators/decorator.spec.ts. - Keep the parameter decorators and
Fetchbehaviour consistent: parameter decorators (Query,Param,Placeholder) must continue to populate the parameter metadata used byFetch.
License
MIT
