+63
-5
@@ -6,17 +6,75 @@ declare type MacroableFn<T> = (this: T, ...args: any[]) => any; | ||
| export interface MacroableConstructorContract<T extends any> { | ||
| macro(name: string, callback: MacroableFn<T>): any; | ||
| getter(name: string, callback: MacroableFn<T>, singleton?: boolean): any; | ||
| hydrate(): any; | ||
| macro(name: string, callback: MacroableFn<T>): void; | ||
| getter(name: string, callback: MacroableFn<T>, singleton?: boolean): void; | ||
| hydrate(): void; | ||
| } | ||
| /** | ||
| * Macroable is an abstract class to add ability to extend your class | ||
| * prototype using better syntax. | ||
| * | ||
| * Macroable has handful of benefits over using traditional `prototype` approach. | ||
| * | ||
| * 1. Methods or properties added dynamically to the class can be removed using `hydrate` method. | ||
| * 2. Can define singleton getters. | ||
| */ | ||
| export declare abstract class Macroable { | ||
| protected static _macros: MacroableMap; | ||
| protected static _getters: MacroableMap; | ||
| protected static macros: MacroableMap; | ||
| protected static getters: MacroableMap; | ||
| constructor(); | ||
| /** | ||
| * Add a macro to the class. This method is a better to manually adding | ||
| * to `class.prototype.method`. | ||
| * | ||
| * Also macros added using `Macroable.macro` can be cleared anytime | ||
| * | ||
| * @example | ||
| * ```js | ||
| * Macroable.macro('getUsername', function () { | ||
| * return 'virk' | ||
| * }) | ||
| * ``` | ||
| */ | ||
| static macro<T extends any = any>(name: string, callback: MacroableFn<T>): void; | ||
| /** | ||
| * Return the existing macro or null if it doesn't exists | ||
| */ | ||
| static getMacro(name: string): MacroableFn<any> | undefined; | ||
| /** | ||
| * Returns a boolean telling if a macro exists | ||
| */ | ||
| static hasMacro(name: string): boolean; | ||
| /** | ||
| * Define a getter, which is invoked everytime the value is accessed. This method | ||
| * also allows adding single getters, whose value is cached after first time | ||
| * | ||
| * @example | ||
| * ```js | ||
| * Macroable.getter('time', function () { | ||
| * return new Date().getTime() | ||
| * }) | ||
| * | ||
| * console.log(new Macroable().time) | ||
| * | ||
| * // Singletons | ||
| * Macroable.getter('time', function () { | ||
| * return new Date().getTime() | ||
| * }, true) | ||
| * | ||
| * console.log(new Macroable().time) | ||
| * ``` | ||
| */ | ||
| static getter<T extends any = any>(name: string, callback: MacroableFn<T>, singleton?: boolean): void; | ||
| /** | ||
| * Return the existing getter or null if it doesn't exists | ||
| */ | ||
| static getGetter(name: string): MacroableFn<any> | undefined; | ||
| /** | ||
| * Returns a boolean telling if a getter exists | ||
| */ | ||
| static hasGetter(name: string): boolean; | ||
| /** | ||
| * Cleanup getters and macros from the class | ||
| */ | ||
| static hydrate(): void; | ||
| } | ||
| export {}; |
+80
-10
| "use strict"; | ||
| /* | ||
| * @poppinss/macroable | ||
| * | ||
| * (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 }); | ||
| /** | ||
| * Macroable is an abstract class to add ability to extend your class | ||
| * prototype using better syntax. | ||
| * | ||
| * Macroable has handful of benefits over using traditional `prototype` approach. | ||
| * | ||
| * 1. Methods or properties added dynamically to the class can be removed using `hydrate` method. | ||
| * 2. Can define singleton getters. | ||
| */ | ||
| class Macroable { | ||
| constructor() { | ||
| if (!this.constructor.hasOwnProperty('macros') || !this.constructor.hasOwnProperty('getters')) { | ||
| throw new Error('Set static properties "macros = {}" and "getters = {}" on the class for the macroable to work.'); | ||
| } | ||
| } | ||
| /** | ||
| * Add a macro to the class. This method is a better to manually adding | ||
| * to `class.prototype.method`. | ||
| * | ||
| * Also macros added using `Macroable.macro` can be cleared anytime | ||
| * | ||
| * @example | ||
| * ```js | ||
| * Macroable.macro('getUsername', function () { | ||
| * return 'virk' | ||
| * }) | ||
| * ``` | ||
| */ | ||
| static macro(name, callback) { | ||
| this._macros[name] = callback; | ||
| this.macros[name] = callback; | ||
| this.prototype[name] = callback; | ||
| } | ||
| /** | ||
| * Return the existing macro or null if it doesn't exists | ||
| */ | ||
| static getMacro(name) { | ||
| return this._macros[name]; | ||
| return this.macros[name]; | ||
| } | ||
| /** | ||
| * Returns a boolean telling if a macro exists | ||
| */ | ||
| static hasMacro(name) { | ||
| return !!this.getMacro(name); | ||
| } | ||
| /** | ||
| * Define a getter, which is invoked everytime the value is accessed. This method | ||
| * also allows adding single getters, whose value is cached after first time | ||
| * | ||
| * @example | ||
| * ```js | ||
| * Macroable.getter('time', function () { | ||
| * return new Date().getTime() | ||
| * }) | ||
| * | ||
| * console.log(new Macroable().time) | ||
| * | ||
| * // Singletons | ||
| * Macroable.getter('time', function () { | ||
| * return new Date().getTime() | ||
| * }, true) | ||
| * | ||
| * console.log(new Macroable().time) | ||
| * ``` | ||
| */ | ||
| static getter(name, callback, singleton = false) { | ||
@@ -20,3 +81,3 @@ const wrappedCallback = singleton ? function wrappedCallback() { | ||
| } : callback; | ||
| this._getters[name] = wrappedCallback; | ||
| this.getters[name] = wrappedCallback; | ||
| Object.defineProperty(this.prototype, name, { | ||
@@ -28,17 +89,26 @@ get: wrappedCallback, | ||
| } | ||
| /** | ||
| * Return the existing getter or null if it doesn't exists | ||
| */ | ||
| static getGetter(name) { | ||
| return this._getters[name]; | ||
| return this.getters[name]; | ||
| } | ||
| /** | ||
| * Returns a boolean telling if a getter exists | ||
| */ | ||
| static hasGetter(name) { | ||
| return !!this.getGetter(name); | ||
| } | ||
| /** | ||
| * Cleanup getters and macros from the class | ||
| */ | ||
| static hydrate() { | ||
| Object.keys(this._macros).forEach((key) => Reflect.deleteProperty(this.prototype, key)); | ||
| Object.keys(this._getters).forEach((key) => Reflect.deleteProperty(this.prototype, key)); | ||
| this._macros = {}; | ||
| this._getters = {}; | ||
| Object.keys(this.macros).forEach((key) => Reflect.deleteProperty(this.prototype, key)); | ||
| Object.keys(this.getters).forEach((key) => Reflect.deleteProperty(this.prototype, key)); | ||
| this.macros = {}; | ||
| this.getters = {}; | ||
| } | ||
| } | ||
| exports.Macroable = Macroable; | ||
| Macroable._macros = {}; | ||
| Macroable._getters = {}; | ||
| Macroable.macros = {}; | ||
| Macroable.getters = {}; |
+17
-21
| { | ||
| "name": "macroable", | ||
| "version": "3.0.0", | ||
| "version": "4.0.0", | ||
| "description": "A simple ES6 class that can be extended to provide macros and getters functionality", | ||
| "main": "build/index.js", | ||
| "files": [ | ||
| "build/index.js", | ||
| "build/index.d.ts" | ||
| "build/index.d.ts", | ||
| "build/index.js" | ||
| ], | ||
@@ -13,11 +13,11 @@ "scripts": { | ||
| "pretest": "npm run lint", | ||
| "test": "nyc node japaFile.js", | ||
| "lint": "tslint --project tsconfig.json", | ||
| "test": "node japaFile.js", | ||
| "clean": "del build", | ||
| "compile": "npm run lint && npm run clean && tsc", | ||
| "build": "npm run compile", | ||
| "coverage": "nyc report --reporter=text-lcov | coveralls", | ||
| "commit": "git-cz", | ||
| "release": "np", | ||
| "version": "npm run build" | ||
| "version": "npm run build", | ||
| "lint": "eslint . --ext=.ts", | ||
| "prepublishOnly": "npm run build" | ||
| }, | ||
@@ -30,24 +30,20 @@ "keywords": [ | ||
| "devDependencies": { | ||
| "@adonisjs/mrm-preset": "^2.1.0", | ||
| "@types/node": "^12.12.11", | ||
| "@adonisjs/mrm-preset": "^2.2.3", | ||
| "@types/node": "^12.12.21", | ||
| "commitizen": "^4.0.3", | ||
| "coveralls": "^3.0.8", | ||
| "cz-conventional-changelog": "^3.0.2", | ||
| "del-cli": "^3.0.0", | ||
| "eslint": "^6.7.2", | ||
| "eslint-plugin-adonis": "^1.0.4", | ||
| "husky": "^3.1.0", | ||
| "japa": "^3.0.1", | ||
| "mrm": "^1.2.2", | ||
| "np": "^5.1.3", | ||
| "nyc": "^14.1.1", | ||
| "ts-node": "^8.5.2", | ||
| "tslint": "^5.20.1", | ||
| "tslint-eslint-rules": "^5.4.0", | ||
| "typescript": "^3.7.2" | ||
| "mrm": "^2.0.2", | ||
| "np": "^5.2.1", | ||
| "ts-node": "^8.5.4", | ||
| "typescript": "^3.7.3" | ||
| }, | ||
| "dependencies": { | ||
| "node-exceptions": "^4.0.1" | ||
| }, | ||
| "dependencies": {}, | ||
| "nyc": { | ||
| "exclude": [ | ||
| "test.ts" | ||
| "test" | ||
| ], | ||
@@ -54,0 +50,0 @@ "extension": [ |
+32
-88
@@ -0,12 +1,15 @@ | ||
| <div align="center"><img src="https://res.cloudinary.com/adonisjs/image/upload/q_100/v1557762307/poppinss_iftxlt.jpg" width="600px"></div> | ||
| # Macroable | ||
| > Extend `class` prototype in style 😎 | ||
| [![travis-image]][travis-url] | ||
| [![appveyor-image]][appveyor-url] | ||
| [![coveralls-image]][coveralls-url] | ||
| [![npm-image]][npm-url] | ||
|  | ||
| [![circleci-image]][circleci-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] | ||
| Macroable is a simple class that your classes can extend in order to expose an API for extending the class. Let's see how a class can be extended without Macroable first. | ||
| Base class for exposing external API to extend the class prototype in a more declarative way. | ||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
| ## Traditional approach | ||
@@ -35,3 +38,3 @@ | ||
| ## Using macroable it's simpler | ||
| ## Using macroable | ||
@@ -44,4 +47,4 @@ ```js | ||
| Foo._macros = {} | ||
| Foo._getters = {} | ||
| Foo.macros = {} | ||
| Foo.getters = {} | ||
@@ -63,30 +66,16 @@ module.exports = Foo | ||
| You can see the API is simpler and less verbose. However, their are couple more benefits to using Macroable. | ||
| You can see the API is simpler and less verbose. However, there are couple of extra benefits of using Macroable. | ||
| 1. You can add singleton getters, which are evaluated only once and then cached value is returned. | ||
| 2. Cleanup all `macros` and `getters` added using Macroable. | ||
| ### Defining singleton getters | ||
| Singleton getters are evaluated only once and then cached value is returned. | ||
| ## Installation | ||
| ```bash | ||
| npm i macroable | ||
| ``` | ||
| ## Usage | ||
| ```js | ||
| const { Macroable } from 'macroable' | ||
| class Foo extends Macroable { | ||
| } | ||
| Foo._macros = {} | ||
| Foo._getters = {} | ||
| module.exports = Foo | ||
| Foo.getter('baseUrl', function () { | ||
| return lazilyEvaluateAndReturnUrl() | ||
| }, true) 👈 | ||
| ``` | ||
| ## API | ||
| ### Hydrating the class | ||
| Using the `hydrate` method, you can remove macros and getters added on a given class. | ||
| #### macro(name, callback) => void | ||
| Add a function to the prototype | ||
| ```js | ||
@@ -96,67 +85,22 @@ Foo.macro('greet', function (name) { | ||
| }) | ||
| ``` | ||
| #### hasMacro(name) => boolean | ||
| Find if macro exists. | ||
| ```js | ||
| Foo.hasMacro('greet') | ||
| ``` | ||
| #### getter(name, callback, isSingleton?) => void | ||
| Add getter to the prototype and optionally make it singleton. | ||
| ```js | ||
| Foo.getter('username', function () { | ||
| return 'virk' | ||
| }, true) | ||
| ``` | ||
| }) | ||
| #### hasGetter(name) => boolean | ||
| Find if getter exists. | ||
| ```js | ||
| Foo.hasGetter('greet') | ||
| Foo.hydrate() 👈 | ||
| Foo.greet // undefined | ||
| Foo.username // undefined | ||
| ``` | ||
| #### hydrate | ||
| Remove all macros and getters added using `Macroable`. | ||
| [circleci-image]: https://img.shields.io/circleci/project/github/poppinss/macroable/master.svg?style=for-the-badge&logo=circleci | ||
| [circleci-url]: https://circleci.com/gh/poppinss/macroable "circleci" | ||
| ```js | ||
| Foo.getter('username', function () { | ||
| return 'virk' | ||
| }, true) | ||
| [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript | ||
| [typescript-url]: "typescript" | ||
| Foo.hydrate() | ||
| [npm-image]: https://img.shields.io/npm/v/macroable.svg?style=for-the-badge&logo=npm | ||
| [npm-url]: https://npmjs.org/package/macroable "npm" | ||
| Foo.hasGetter('username') // false | ||
| ``` | ||
| ## Change log | ||
| The change log can be found in the [CHANGELOG.md](CHANGELOG.md) file. | ||
| ## Contributing | ||
| Everyone is welcome to contribute. Please go through the following guides, before getting started. | ||
| 1. [Contributing](https://adonisjs.com/contributing) | ||
| 2. [Code of conduct](https://adonisjs.com/code-of-conduct) | ||
| ## Authors & License | ||
| [thetutlage](https://github.com/thetutlage) and [contributors](https://github.com/poppinss/macroable/graphs/contributors). | ||
| MIT License, see the included [MIT](LICENSE.md) file. | ||
| [travis-image]: https://img.shields.io/travis/poppinss/macroable/master.svg?style=flat-square&logo=travis | ||
| [travis-url]: https://travis-ci.org/poppinss/macroable "travis" | ||
| [appveyor-image]: https://img.shields.io/appveyor/ci/thetutlage/macroable/master.svg?style=flat-square&logo=appveyor | ||
| [appveyor-url]: https://ci.appveyor.com/project/thetutlage/macroable "appveyor" | ||
| [coveralls-image]: https://img.shields.io/coveralls/poppinss/macroable/master.svg?style=flat-square | ||
| [coveralls-url]: https://coveralls.io/github/poppinss/macroable "coveralls" | ||
| [npm-image]: https://img.shields.io/npm/v/macroable.svg?style=flat-square&logo=npm | ||
| [npm-url]: https://npmjs.org/package/macroable "npm" | ||
| [license-image]: https://img.shields.io/npm/l/macroable?color=blueviolet&style=for-the-badge | ||
| [license-url]: LICENSE.md "license" |
12020
27.22%0
-100%13
-13.33%191
203.17%103
-35.22%- Removed
- Removed