Skip to content

useService

Instantiate a service class extending BaseService and return a proxy whose every method is wrapped with guarded. Additional wrap functions can be passed to chain extra decorators (logging, retry, telemetry, ...).

useService discovers methods by walking serviceClass.prototype with Object.getOwnPropertyNames — every prototype property except constructor is included.

Importing

ts
import { useService } from '@basmilius/common';

Usage

ts
import { useService } from '@basmilius/common';
import { OrderService } from '@/services/OrderService';

const orders = useService(OrderService);

await orders.list(0, 25);
await orders.get(123);

Add custom wrappers — for example, a logger:

ts
import { useService } from '@basmilius/common';
import { OrderService } from '@/services/OrderService';

const withLogging = (fn: Function) => {
    return async (...args: unknown[]) => {
        console.time(fn.name);
        try {
            return await fn(...args);
        } finally {
            console.timeEnd(fn.name);
        }
    };
};

const orders = useService(OrderService, withLogging);

Wrappers are applied left-to-right — the rightmost wrapper ends up as the outermost call. The base guarded wrapper is always applied first.

Why guarded by default?

Service methods typically perform HTTP requests and are vulnerable to authorization errors and aborted requests. Wrapping them with guarded translates HTTP 403 responses to a ForbiddenException, unsanctioned requests (cancelled / unauthenticated) to an UnauthorizedException, and lets you supply an onError callback that converts arbitrary errors into a HandledException for centralized error UX.

Type signature

ts
type ServiceClass<T extends BaseService> = { new(): T; };
type Wrap = (fn: Function) => Function;

declare function useService<T extends BaseService>(
    serviceClass: ServiceClass<T>,
    ...wrap: Wrap[]
): T;

See also