@tsxper/service-manager
TypeScript icon, indicating that this package has built-in type declarations

2.0.0 • Public • Published

Service-Manager

NPM Version License: MIT npm type definitions NPM Downloads

@tsxper/service-manager is a TypeScript extension to the Service Locator design pattern, that helps with managing application dependencies.

Service manager can be helpful for apps and services that do not require big frameworks with complex dependency injection setup.

It's designed to be used in browser and NodeJS apps.

Advantages of using ServiceManager:

  • Easy setup.
  • Comfortable integration testing.
  • Small size (ServiceManager is implemented as one class).
  • CommonJS and ESM modules support.
  • Aimed to be used in all JavaScript projects (NodeJS and browsers).
  • Doesn't use code generators.
  • Doesn't rely on experimental technology, like decorators.

Examples

See other examples in GitHub.

With using registered factories you can easily inject required dependencies in your services.

Imaging you have a service "DownstreamService" which depends on "LoggerService" and "VaultService".

// define your applications services
export class DownstreamService {
  constructor(logger: LoggerService, vault: VaultService) {
    // ...
  }
}


// sm.ts - add Service Manager configuration with factories
export const sm = new ServiceManager({
  'logger': () => new LoggerService(),
  'vault': () => {
    const url = process.env.VAULT_URL;
    if (!url) throw new Error('VAULT_URL is not set');
    return new VaultService(url);
  },
}).add('downstream', async (sm) => {
  // place for some async calls
  return new DownstreamService(sm.get('logger'), sm.get('vault'));
});


// app.ts - use Service Manager in your app
const downstream = await sm.get('downstream'); // we use "await" because associated factory is async function
await downstream.sendData(records);
sm.get('logger').log('Success');


// __tests__/app.test.ts - easily change behavior in tests
sm.replace(
  'vault',
  () => new VaultFake('http://test/')
);

Caching Instances

By default, all created instances are cached (shared instances). To retrieve a private instance of a service, pass "true" as second argument to "get()" method. See example below.

const privateInstance = sm.get('logger', true);

Disable/Enable Caching

Service instances cache is enabled by default.

Calling disableCache()/enableCache() does not remove existing cached instances.

sm.disableCache();
sm.enableCache();

Clear Services Cache

Call "cleanCache()" method to clear service cache.

Call "destroy()" method to clear service cache and registered factories.

sm.cleanCache(); // clean cache
// or 
sm.destroy(); // clean cache and registered factories

Local vs Global Cache

ServiceManager supports 2 types of cache: "global" (for runtime) and "local" (for particular app instance). Good use case for a "local" cache is tests isolation. To control cache type, ServiceManager constructor has a "useGlobalCache" as 2nd parameter (default is "true").

const useGlobalCache = false;
new ServiceManager({...}, useGlobalCache);

See builder example.

Types Inference

Types inference for registered services is supported.

// Checks for retrieving only registered services
const vault = sm.get('unregistered');
// TypeScript Error: Argument of type '"unregistered"' is not assignable to parameter of type '"logger" | "vault"'.

const vault = sm.get('vault'); 
// vault: VaultService

Known Limitations

  1. Using strings instead of literals. Example: TypeScript does not derive literal from a class name.
// this code will work
const serviceName = 'LoggerService';
const sm = new ServiceManager({[serviceName]: () => new LoggerService()});
const loggerService = sm.get(serviceName); // serviceName is string literal

// this code will NOT work in TypeScript
const sm = new ServiceManager({ [Fruit.name]: () => new Fruit(1), 'Article': () => new Article('title') });
sm.get(Fruit.name).weight; // Fruit.name is a "string" and can't be associated with a concrete service
// Property 'weight' does not exist on type 'Fruit | Article'.

To solve this issue, define a literal property on your class, for example:

class Fruit {
  static service: 'Fruit' = 'Fruit';
}
const sm = new ServiceManager({[Fruit.service]: () => new Fruit()});
  1. Retrieve services in JavaScript.

In plain JavaScript, sm.get('logger') will not check that 'logger' was set or that 'logger' is associated with Logger service factory. What you can do is to add type guards.

Note. In VS Code you can enable types check for JavaScript files with adding a line // @ts-check ;

// @ts-check 
const sm = new ServiceManager({'logger': () => new Logger()});
const logger = sm.get('logger');
if (!(logger instanceof Logger)) throw new Error('"logger" is not Logger');
logger.log();

License

MIT license.

Package Sidebar

Install

npm i @tsxper/service-manager

Weekly Downloads

1

Version

2.0.0

License

MIT

Unpacked Size

11.8 kB

Total Files

10

Last publish

Collaborators

  • vbabak