@poppinss/manager
Advanced tools
Comparing version 2.1.8 to 3.0.0
"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 @@ /** |
164
package.json
{ | ||
"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": {} | ||
} |
357
README.md
@@ -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" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21452
291
17
291