# @poppinss/macroable > Extend classes from outside in using Macros and getters [![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] Macroable offers a simple API for adding properties and getters to the class prototype. You might not even need this package, if you are happy writing `Object.defineProperty` calls yourself. ## Usage Install the package from npm packages registry as follows. ```sh npm i @poppinss/macroable # yarn lovers yarn add @poppinss/macroable ``` And import the `Macroable` class. ```ts import Macroable from '@poppinss/macroable' export class Route extends Macroable {} ``` Now, you can add properties to the Route class from outside-in. This is usually needed, when you want the consumer of your classes to be able to extend them by adding custom properties. ## Macros Getters are added to the class prototype directly. ```ts Route.macro('head', function (uri, callback) { return this.route(['HEAD'], uri, callback) }) ``` And now, you can will be use the `head` method from an instance of the `Route` class. ```ts const route = new Route() route.head('/', () => {}) ``` Adding a macro is same as writing the following code in JavaScript. ```ts Route.prototype.head = function () { } ``` ## Instance properties Since, macros are defined on the prototype of the class and therefore they loose the `this` context when destructured from the class instance. For example: ```ts HttpContext.macro('getUser', function (this: HttpContext) { return this.auth.user }) const { getUser } = ctx getUser() // ❌ Error: Cannot read property auth of undefined ``` In order to fix this issue, the properties that can be destructured must be defined as instance properties on the class. ```ts HttpContext.instanceProperty('getUser', function (this: HttpContext) { return this.auth.user }) const { getUser } = ctx getUser() // ✅ Works fine ``` ## Getters Getters are added to the class prototype using the `Object.defineProperty`. The implementation of a getter is always a function. ```ts Route.getter('version', function () { return 'v1' }) ``` And now access the version as follows. ```ts const route = new Route() route.version // v1 ``` Adding a getter is same as writing the following code in JavaScript. ```ts Object.defineProperty(Route.prototype, 'version', { get() { const value = callback() return value }, configurable: false, enumerable: false, }) ``` ## Singleton getters Singleton getters are also defined on the class prototype. However, their values are cached after the first access. ```ts const singleton = true Mysql.getter('version', function () { return this.config.driver.split('-')[1] }, singleton) ``` Adding a singleton getter is same as writing the following code in JavaScript. ```ts Object.defineProperty(Mysql.prototype, 'version', { get() { const value = callback() // Cache value on the class instance Object.defineProperty(this, 'version', { configurable: false, enumerable: false, value: value, writable: false, }) return value }, configurable: false, enumerable: false, }) ``` ## TypeScript types You will have to use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) in order to define the types for the dynamically added properties. ## Contributing One of our primary goals is to have a vibrant community of users and contributors who believes in the principles of the framework. We encourage you to read the [contribution guide](https://github.com/poppinss/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework. ## Code of Conduct In order to ensure that the community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/poppinss/.github/blob/main/docs/CODE_OF_CONDUCT.md). ## License Poppinss macroable is open-sourced software licensed under the [MIT license](LICENSE.md). [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/macroable/checks.yml?style=for-the-badge [gh-workflow-url]: https://github.com/poppinss/macroable/actions/workflows/checks.yml "Github action" [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript [typescript-url]: "typescript" [npm-image]: https://img.shields.io/npm/v/@poppinss/macroable.svg?style=for-the-badge&logo=npm [npm-url]: https://npmjs.org/package/@poppinss/macroable 'npm' [license-image]: https://img.shields.io/npm/l/@poppinss/macroable?color=blueviolet&style=for-the-badge [license-url]: LICENSE.md 'license'