Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@poppinss/manager

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@poppinss/manager - npm Package Compare versions

Comparing version 2.1.8 to 3.0.0

16

build/index.js
"use strict";
/*
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {

@@ -19,3 +19,3 @@ if (k2 === undefined) k2 = k;

for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p);
}
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -22,0 +22,0 @@ var Manager_1 = require("./src/Manager");

/**
* Shape of the extend callback
*/
export declare type ExtendCallback<Driver extends any> = (container: any, mappingName: string, config: any) => Driver;
export declare type ExtendCallback<Manager extends ManagerContract<any, any>, Driver extends any> = (manager: Manager, mappingName: string, config: any) => Driver;
/**
* Manager class shape
*/
export interface ManagerContract<DriverContract extends any, ReturnValueContract extends any = DriverContract, MappingsList extends {
[key: string]: ReturnValueContract;
} = {
[key: string]: ReturnValueContract;
}, DefaultItem extends ReturnValueContract = ReturnValueContract> {
export interface ManagerContract<Container extends any, DriverContract extends any, MappingValue extends any = DriverContract, MappingsList extends {
[key: string]: MappingValue;
} = any> {
container: Container;
/**
* Returns concrete type when binding name is from the mappings list
*/
use<K extends keyof MappingsList>(name: K): MappingsList[K];
use(name: string): DefaultItem;
use(): DefaultItem;
extend(name: string, callback: ExtendCallback<DriverContract>): void;
/**
* Returns mapping value when an unknown key is defined. This will be done, when someone
* is trying to bypass static analysis
*/
use(name: string): MappingValue;
/**
* Return a overload of mapping when no key is defined
*/
use(): {
[K in keyof MappingsList]: MappingsList[K];
}[keyof MappingsList];
/**
* Extend by adding a new custom driver
*/
extend(name: string, callback: ExtendCallback<this, DriverContract>): void;
/**
* Release bindings from cache
*/
release<K extends keyof MappingsList>(name: K): void;

@@ -18,0 +35,0 @@ release(name: string): void;

"use strict";
/*
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -9,8 +9,6 @@ import { ManagerContract, ExtendCallback } from './Contracts';

*/
export declare abstract class Manager<DriverContract extends any, ReturnValueContract extends any = DriverContract, MappingsList extends {
[key: string]: ReturnValueContract;
} = {
[key: string]: ReturnValueContract;
}, DefaultItem extends ReturnValueContract = ReturnValueContract> implements ManagerContract<DriverContract, ReturnValueContract, MappingsList, DefaultItem> {
protected container: any;
export declare abstract class Manager<Container extends any, DriverContract extends any, MappingValue extends any = DriverContract, MappingsList extends {
[key: string]: MappingValue;
} = any> implements ManagerContract<Container, DriverContract, MappingValue, MappingsList> {
container: Container;
/**

@@ -27,3 +25,3 @@ * Mappings cache (if caching is enabled)

*/
protected abstract $cacheMappings: boolean;
protected abstract singleton: boolean;
/**

@@ -43,3 +41,3 @@ * Getting the default mapping name, incase a mapping

protected abstract getMappingDriver(mappingName: string): string | undefined;
constructor(container: any);
constructor(container: Container);
/**

@@ -70,3 +68,3 @@ * Returns the value saved inside cache, this method will check for

*/
protected wrapDriverResponse(_: string, value: DriverContract): ReturnValueContract;
protected wrapDriverResponse(_: string, value: DriverContract): MappingValue;
/**

@@ -77,4 +75,6 @@ * Returns the instance of a given driver. If `name` is not defined

use<K extends keyof MappingsList & string>(name: K): MappingsList[K];
use(name: string): ReturnValueContract;
use(): DefaultItem;
use(name: string): MappingValue;
use(): {
[K in keyof MappingsList]: MappingsList[K];
}[keyof MappingsList];
/**

@@ -89,3 +89,3 @@ * Removes the mapping from internal cache.

*/
extend(name: string, callback: ExtendCallback<DriverContract>): void;
extend(name: string, callback: ExtendCallback<this, DriverContract>): void;
}
"use strict";
/*
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
* @poppinss/manager
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });

@@ -43,3 +43,3 @@ exports.Manager = void 0;

saveToCache(name, value) {
if (this.$cacheMappings) {
if (this.singleton) {
this.mappingsCache.set(name, value);

@@ -52,3 +52,3 @@ }

makeExtendedDriver(mappingName, driver, config) {
const value = this.wrapDriverResponse(mappingName, this.extendedDrivers[driver](this.container, mappingName, config));
const value = this.wrapDriverResponse(mappingName, this.extendedDrivers[driver](this, mappingName, config));
this.saveToCache(mappingName, value);

@@ -69,4 +69,4 @@ return value;

*/
if (typeof (this[driverCreatorName]) !== 'function') {
throw new Error(`${mappingName} driver is not supported by ${this.constructor.name}`);
if (typeof this[driverCreatorName] !== 'function') {
throw new Error(`"${mappingName}" driver is not supported by "${this.constructor.name}"`);
}

@@ -84,3 +84,3 @@ const value = this.wrapDriverResponse(mappingName, this[driverCreatorName](mappingName, config));

use(name) {
name = (name || this.getDefaultMappingName());
name = name || this.getDefaultMappingName();
const cached = this.getFromCache(name);

@@ -95,3 +95,3 @@ if (cached) {

if (!driver) {
throw new Error(`Make sure to define driver for ${name} mapping`);
throw new Error(`Make sure to define driver for "${name}" mapping`);
}

@@ -98,0 +98,0 @@ /**

{
"name": "@poppinss/manager",
"version": "2.1.8",
"description": "The builder (Manager) pattern implementation",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "node japaFile.js",
"lint": "eslint . --ext=.ts",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"commit": "git-cz",
"release": "np",
"version": "npm run build"
},
"keywords": [
"builder-pattern",
"adonisjs"
],
"author": "virk,poppinss",
"license": "MIT",
"devDependencies": {
"@adonisjs/mrm-preset": "^2.3.0",
"@types/node": "^14.0.1",
"commitizen": "^4.1.2",
"cz-conventional-changelog": "^3.2.0",
"del-cli": "^3.0.0",
"doctoc": "^1.4.0",
"eslint": "^7.0.0",
"eslint-plugin-adonis": "^1.0.10",
"husky": "^4.2.5",
"japa": "^3.0.1",
"mrm": "^2.3.0",
"np": "^5.2.1",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"husky": {
"hooks": {
"pre-commit": "doctoc README.md --title='## Table of contents' && git add README.md",
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"np": {
"contents": ".",
"anyBranch": false
},
"directories": {
"doc": "docs",
"example": "example",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/manager.git"
},
"bugs": {
"url": "https://github.com/poppinss/manager/issues"
},
"homepage": "https://github.com/poppinss/manager#readme",
"dependencies": {}
"name": "@poppinss/manager",
"version": "3.0.0",
"description": "The builder (Manager) pattern implementation",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "node japaFile.js",
"lint": "eslint . --ext=.ts",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"commit": "git-cz",
"release": "np",
"version": "npm run build",
"format": "prettier --write ."
},
"keywords": [
"builder-pattern",
"adonisjs"
],
"author": "virk,poppinss",
"license": "MIT",
"devDependencies": {
"@adonisjs/mrm-preset": "^2.3.6",
"@types/node": "^14.0.14",
"commitizen": "^4.1.2",
"cz-conventional-changelog": "^3.2.0",
"del-cli": "^3.0.1",
"doctoc": "^1.4.0",
"eslint": "^7.3.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-adonis": "^1.0.14",
"eslint-plugin-prettier": "^3.1.4",
"husky": "^4.2.5",
"japa": "^3.1.1",
"mrm": "^2.3.3",
"np": "^6.2.5",
"prettier": "^2.0.5",
"ts-node": "^8.10.2",
"typescript": "^3.9.6"
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"husky": {
"hooks": {
"pre-commit": "doctoc README.md --title='## Table of contents' && git add README.md",
"commit-msg": "node ./node_modules/@adonisjs/mrm-preset/validateCommit/conventional/validate.js"
}
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"np": {
"contents": ".",
"anyBranch": false
},
"directories": {
"doc": "docs",
"example": "example",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/manager.git"
},
"bugs": {
"url": "https://github.com/poppinss/manager/issues"
},
"homepage": "https://github.com/poppinss/manager#readme",
"dependencies": {}
}

@@ -5,20 +5,9 @@ <div align="center">

# Manager/Builder pattern
> Abstract class to incapsulate the behavior of constructing and re-using named objects.
# Manager Pattern
> Implementation of the Manager pattern used by AdonisJS
[![circleci-image]][circleci-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
The module is used heavily by AdonisJs to build it's driver based features for components like `hash`, `mail`, `auth`, `social auth` and so on.
Manager pattern is a way to ease the construction of objects of similar nature. To understand it better, we will follow an imaginary example through out this document.
If you are a user of AdonisJs, then you will be familiar with the following API.
```ts
mail.use('smtp').send()
// or
hash.use('argon2').hash('')
```
The `use` method here constructs the driver for the mapping defined in the config file with the ability to cache the constructed drivers and add new drivers using the `extend` API.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->

@@ -28,47 +17,67 @@ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

- [Installation](#installation)
- [Scanerio](#scanerio)
- [Basic implementation](#basic-implementation)
- [Using a Manager Class](#using-a-manager-class)
- [Step1: Define a sample config object.](#step1-define-a-sample-config-object)
- [Step 2: Create manager class and accept mappings config](#step-2-create-manager-class-and-accept-mappings-config)
- [Step 3: Using the config to constructor drivers](#step-3-using-the-config-to-constructor-drivers)
- [Step 4: Move drivers construction into the manager class](#step-4-move-drivers-construction-into-the-manager-class)
- [Usage](#usage)
- [Release mapping from cache](#release-mapping-from-cache)
- [Extending drivers](#extending-drivers)
- [What is container?](#what-is-container)
- [Typescript types](#typescript-types)
- [Driver interface](#driver-interface)
- [`use` method intellisense](#use-method-intellisense)
- [Typed config](#typed-config)
- [Updating mapping list generic for Manager](#updating-mapping-list-generic-for-manager)
- [Extending from outside-in](#extending-from-outside-in)
- [Driver Interface](#driver-interface)
- [Defining Drivers Interface](#defining-drivers-interface)
- [Passing interface to Manager](#passing-interface-to-manager)
- [Mappings Type](#mappings-type)
- [Mapping suggestions](#mapping-suggestions)
- [Return type](#return-type)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
Install the package from npm registry as follows:
## Scanerio
```sh
npm i @poppinss/manager
Let's imagine we are creating a mailing library and it supports multiple drivers like: **SMTP**, **Mailgun**, **PostMark** and so on. Also, we want the users of our library to use each driver for multiple times using different configuration. For example:
# yarn
yarn add @poppinss/manager
Using the **Mailgun driver with different accounts**. Maybe one account for sending promotional emails and another account for sending transactional emails.
## Basic implementation
The simplest way to expose the drivers, is to export them directly and let the consumer construct instances of them. For example:
```ts
import { Mailgun } from 'my-mailer-library'
const promotional = new Mailgun(configForPromtional)
const transactional = new Mailgun(configForTransactional)
promotional.send()
transactional.send()
```
## Usage
The above approach works perfect, but has few drawbacks
Let's imagine we are building a mailer (with dummy implementation) that supports multiple drivers and each driver can be used for multiple times. Example config for same
- If the construction of the drivers needs more than one constructor arguments, then it will become cumbersome for the consumer to satisfy all those dependencies.
- They will have to manually manage the lifecycle of the constructed objects. Ie `promotional` and `transactional` in this case.
```js
{
mailer: 'transactional',
## Using a Manager Class
What we really need is a Manager to manage and construct these objects in the most ergonomic way.
### Step1: Define a sample config object.
First step is to move the mappings to a configuration file. The config mimics the same behavior we were trying to achieve earlier (in the Basic example), but defines it declaratively this time.
```ts
const mailersConfig = {
default: 'transactional',
mailers: {
list: {
transactional: {
👇 // driver is important
driver: 'smtp',
host: '',
user: '',
password: ''
driver: 'mailgun'
},
promotional: {
driver: 'mailchimp',
apiKey: ''
}
driver: 'mailgun'
},
}

@@ -78,143 +87,134 @@ }

Our module supports two drivers `smtp` and `mailchimp` and here's how we can construct them.
### Step 2: Create manager class and accept mappings config
```ts
class SmtpDriver {
constructor (config) {}
send () {}
}
```
import { Manager } from '@poppinss/manager'
```ts
class MailchimpDriver {
constructor (config) {}
send () {}
class MailManager implements Manager {
protected singleton = true
constructor (private config) {
super({})
}
}
```
The consumer of our code can import and use these drivers manually by constructing a new instance every time. However, we can improve the developer experience, by creating the following manager class.
### Step 3: Using the config to constructor drivers
The **Base Manager** class will do all the heavy lifting for you. However, you will have to define certain methods to resolve the values from the config file.
```ts
import { Manager } from '@poppinss/manager'
class Mailer extends Manager {
constructor (container, private config) {
super(container)
class MailManager implements Manager {
protected singleton = true
protected getDefaultMappingName() {
return this.config.default
}
protected $cacheMappings = true
protected getDefaultMappingName (): string {
return this.config.mailer
}
protected getMappingConfig (name): any {
return this.config.mailers[name]
protected getMappingConfig(mappingName: string) {
return this.config.list[mappingName]
}
protected getMappingDriver (name): any {
return this.config.mailers[name].driver
protected getMappingDriver(mappingName: string) {
return this.config.list[mappingName].driver
}
protected createSmtp (name, config) {
return new Smtp(config)
}
protected createMailchimp (name, config) {
return new MailchimpDriver(config)
constructor (private config) {
super({})
}
}
```
```
The `Manager` class forces the parent class to define some of the required methods/properties.
### Step 4: Move drivers construction into the manager class
- **$cacheMappings**: When set to true, the manager will internally cache the instance of drivers and will re-use them.
- **getDefaultMappingName**: The name of the default mapping. In this case, it is the name of the `mailer` set in the config
- **getMappingConfig**: Returning the config for a mapping. We pull it from the `mailers` object defined in the config.
- **getMappingDriver**: Returning the driver for a mapping inside config.
The final step is to write the code for constructing drivers. The **Base Manager** uses a convention for this. Anytime the consumer will ask for the `mailgun` driver, it will invoke `createMailgun` method. So the convention here is to prefix `create` followed by the camelCase driver name.
The other two methods `createSmtp` and `createMailchimp` are the methods invoked when the end user will access a driver. This is how it works.
```ts
import { Manager } from '@poppinss/manager'
```ts
const mailer = new Mailer({}, config)
mailer.use('smtp').send()
class MailManager implements Manager {
// ... The existing code
public createMailgun (mappingName, config) {
return new Mailgun(config)
}
public createSmtp (mappingName, config) {
return new Smtp(config)
}
}
```
The `mailer.use('transactional')` will invoke `createTransactional` as part of the following convention.
## Usage
- `create` + `PascalCaseDriverName`
- `create` + `Smtp` = `createSmtp`
- `create` + `Mailchimp` = `createMailchimp`
Once done, the consumer of the Mailer class just needs to define the mappings config and they are good to go.
> **NOTE**: You need one method for each driver and not the mapping. The mapping names can be anything the user wants to keep in their config file.
```ts
const mailersConfig = {
default: 'transactional',
list: {
transactional: {
driver: 'mailgun'
},
promotional: {
driver: 'mailgun'
},
}
}
## Release mapping from cache
The `release` method can be used release the mappings from cache.
const mailer = new MailManager(mailersConfig)
```ts
// Removes smtp from cache. Next call to `use('smtp')` will create a new instance
mailer.release('smtp')
mailer.use('transactional').send()
mailer.use('promotional').send()
```
## Extending drivers
- The lifecycle of the mailers is now encapsulated within the manager class. The consumer can call `mailer.use()` as many times as they want, without worrying about creating too many un-used objects.
- They just need to define the mailers config once and get rid of any custom code required to construct individual drivers.
## Extending from outside-in
The manager class also exposes the API to add new drivers from outside in and this is done using the `extend` method.
The Base Manager class comes with first class support for adding custom drivers from outside-in using the `extend` method.
```ts
mailer.extend('postmark', (container, name, config) => {
return new Postmark(config)
const mailer = new MailManager(mailersConfig)
mailer.extend('postmark', (container, mappingName, config) => {
return new PostMark(config)
})
```
## What is container?
The `extend` method receives a total of three arguments:
The `Manager` class needs a container value, which is then passed to the `extended` drivers. This can be anything from application state to an empty object.
- The container object is the value you passed to the Base Manager class using the `super({})` keyword. In case of AdonisJS, it is reference to the IoC container.
- The name of the mapping inside the config file.
- The actual configuration object.
- The `callback` should return an instance of the Driver.
In case of AdonisJs, it is the instance of the IoC container, so that the outside world (extended drivers) can pull dependencies from the container.
## Driver Interface
## Typescript types
The Manager class can also leverage static Typescript types to have better intellisense support and also ensure that the drivers added using the `extend` method adhere to a given interface.
In order for intellisense to work, you have to do some ground work of defining additional types. This in-fact is a common theme with static languages, that you have to rely on loose coupling when creating or using extensible objects.
### Defining Drivers Interface
### Driver interface
Following is a dummy interface, we expect all drivers to adhere too
The first thing you must have in place is the interface that every driver adheres to.
```ts
interface MailDriverContract {
send (): void
interface DriverContract {
send (): Promise<void>
}
```
And then pass it as a generic to the `Manager` constructor
```ts
class Mailer extends Manager<MailDriverContract> {
// rest of the code
}
```
### Passing interface to Manager
Now, `extend` method will ensure that all drivers adhere to the `‌MailDriverContract`.
### `use` method intellisense
Currently the output of `use` method will be typed to `MailDriverContract` and not the actual implementation class. This is fine, **when all drivers have the same properties, methods and output**. However, you can define a list of mappings as follows
```ts
type MappingsList = {
transactional: SmtpDriver,
promotional: MailchimpDriver,
}
```
import { Manager } from '@poppinss/manager'
import { DriverContract } from './Contracts'
And then pass this mapping as a 2nd argument to the `Manager` constructor.
```ts
class Mailer extends Manager<
MailDriverContract,
MailDriverContract,
MappingsList
class MailManager implements Manager<
DriverContract
> {

@@ -224,70 +224,47 @@ }

The `use` method will now return the concrete type. You can also use this same mapping to enforce correct configuration.
## Mappings Type
The mappings config currently has `any` type and hence, the `mailer.use` method cannot infer correct return types.
In order to improve intellisense for the `use` method. You will have to define a type for the mappings too.
```ts
type SmtpConfig = {
driver: 'smtp',
host: string,
user: string,
password: string,
port?: number,
type MailerMappings = {
transactional: Mailgun,
promotional: Mailgun
}
type MailchimpConfig = {
driver: 'mailchimp',
apiKey: string,
type MailerConfig = {
default: keyof MailerMappings,
list: {
[K in keyof MailerMappings]: any
}
}
type MappingsList = {
transactional: {
config: SmtpConfig,
implementation: SmtpDriver,
},
promotional: {
config: MailchimpConfig,
implementation: MailchimpDriver,
},
}
```
const mailerConfig: MailerConfig = {
default: 'transactional',
The master `MappingsList` will ensure that static types and the configuration is always referring to the same driver instance.
#### Typed config
```ts
import { ExtractConfig } from '@poppinss/manager'
type Config<Mailer extends keyof MappingsList> = {
mailer: Mailer,
mailers: ExtractConfig<MappingsList>,
}
const config: Config<'transactional'> = {
mailer: 'transactional',
mailers: {
list: {
transactional: {
driver: 'smtp',
host: '',
user: '',
password: '',
driver: 'mailgun',
// ...
},
promotional: {
driver: 'mailchimp',
apiKey: '',
},
},
driver: 'mailgun',
// ...
},
}
}
```
#### Updating mapping list generic for Manager
Finally, pass the `MailerMappings` to the Base Manager class
```ts
import { ExtractImplementations, Manager } from '@poppinss/manager'
import { DriverContract, MailerMappings } from './Contracts'
class Mailer extends Manager<
MailDriverContract,
MailDriverContract,
ExtractImplementations<MappingsList>
class MailManager implements Manager<
DriverContract,
DriverContract,
MailerMappings
> {

@@ -297,4 +274,12 @@ }

Finally, you have runtime functionality to switch between multiple mailers and also have type safety for same.
Once mailer mappings have been defined, the `use` method will have proper return types.
### Mapping suggestions
![](assets/mappings.png)
### Return type
![](assets/return-type.png)
[circleci-image]: https://img.shields.io/circleci/project/github/poppinss/manager/master.svg?style=for-the-badge&logo=circleci

@@ -301,0 +286,0 @@ [circleci-url]: https://circleci.com/gh/poppinss/manager "circleci"

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc