This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2021 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+113
View File
@@ -0,0 +1,113 @@
<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 😎
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] [![synk-image]][synk-url]
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 -->
## Table of contents
- [Traditional approach](#traditional-approach)
- [Using macroable](#using-macroable)
- [Defining singleton getters](#defining-singleton-getters)
- [Hydrating the class](#hydrating-the-class)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Traditional approach
```js
class Foo {}
module.exports = Foo
```
Someone can extend it follows.
```js
const Foo = require('./Foo')
Foo.prototype.greet = function () {
return 'Hello!'
}
// or add getter as follow
Object.defineProperty(Foo.prototype, 'username', {
get: function () {
return 'virk'
},
})
```
## Using macroable
```ts
import { Macroable } from 'macroable'
class Foo extends Macroable {}
Foo.macros = {}
Foo.getters = {}
export default Foo
```
```ts
import Foo from './Foo'
Foo.macro('greet', function () {
return 'Hello!'
})
Foo.getter('username', function () {
return 'virk'
})
```
You can see the API is simpler and less verbose. However, there are couple of extra benefits of using Macroable.
### Defining singleton getters
Singleton getters are evaluated only once and then cached value is returned.
```js
Foo.getter('baseUrl', function () {
return lazilyEvaluateAndReturnUrl()
}, true) 👈
```
### Hydrating the class
Using the `hydrate` method, you can remove macros and getters added on a given class.
```js
Foo.macro('greet', function (name) {
return `Hello ${name}!`
})
Foo.getter('username', function () {
return 'virk'
})
Foo.hydrate() 👈
Foo.greet // undefined
Foo.username // undefined
```
[gh-workflow-image]: https://img.shields.io/github/workflow/status/poppinss/macroable/test?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/macroable/actions/workflows/test.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/macroable.svg?style=for-the-badge&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'
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/poppinss/manager?label=Synk%20Vulnerabilities&style=for-the-badge
[synk-url]: https://snyk.io/test/github/poppinss/manager?targetFile=package.json "synk"
+104
View File
@@ -0,0 +1,104 @@
/**
* Shape of the Macro function
*/
declare type MacroFn<T, Args extends any[], ReturnValue extends any> = (this: T, ...args: Args) => ReturnValue;
/**
* Shape of the Getter function
*/
declare type GetterFn<T, ReturnValue extends any> = (this: T) => ReturnValue;
/**
* Returns the typed variation of the macro by inspecting the
* interface declaration
*/
declare type GetMacroFn<T, K> = K extends keyof T ? T[K] extends (...args: infer A) => infer B ? MacroFn<T, A, B> : MacroFn<T, any[], any> : MacroFn<T, any[], any>;
/**
* Returns the typed variation of the getter by inspecting the
* interface declaration
*/
declare type GetGetterFn<T, K> = K extends keyof T ? GetterFn<T, T[K]> : GetterFn<T, any>;
/**
* Shape of the macroable constructor
*/
export interface MacroableConstructorContract<T extends any> {
macro<K extends string>(name: K, callback: GetMacroFn<T, K>): void;
getter<K extends string>(name: K, callback: GetGetterFn<T, K>, 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: {
[key: string]: MacroFn<any, any[], any>;
};
protected static getters: {
[key: string]: GetterFn<any, any>;
};
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, K extends string>(this: {
new (...args: any): T;
}, name: string, callback: GetMacroFn<T, K>): void;
/**
* Return the existing macro or null if it doesn't exists
*/
static getMacro(name: string): MacroFn<any, any[], 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, K extends string>(this: {
new (...args: any): T;
}, name: string, callback: GetGetterFn<T, K>, singleton?: boolean): void;
/**
* Return the existing getter or null if it doesn't exists
*/
static getGetter(name: string): GetterFn<any, 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 {};
+115
View File
@@ -0,0 +1,115 @@
"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 });
exports.Macroable = void 0;
/**
* 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['macros'] || !this.constructor['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) {
const self = this;
self.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];
}
/**
* 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) {
const wrappedCallback = singleton
? function wrappedCallback() {
const value = callback.bind(this)();
Object.defineProperty(this, name, { value, configurable: true });
return value;
}
: callback;
const self = this;
self.getters[name] = wrappedCallback;
Object.defineProperty(this.prototype, name, {
get: wrappedCallback,
configurable: true,
enumerable: true,
});
}
/**
* Return the existing getter or null if it doesn't exists
*/
static getGetter(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 = {};
}
}
exports.Macroable = Macroable;
+118
View File
@@ -0,0 +1,118 @@
{
"name": "macroable",
"version": "7.0.2",
"description": "A simple ES6 class that can be extended to provide macros and getters functionality",
"main": "build/index.js",
"files": [
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "node .bin/test.js",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"commit": "git-cz",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"prepublishOnly": "npm run build",
"format": "prettier --write .",
"lint": "eslint . --ext=.ts",
"sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json poppinss/macroable"
},
"keywords": [
"resetable"
],
"author": "amanvirk,adonisjs",
"license": "MIT",
"devDependencies": {
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.13",
"@types/node": "^18.8.5",
"commitizen": "^4.2.5",
"cz-conventional-changelog": "^3.3.0",
"del-cli": "^5.0.0",
"doctoc": "^2.2.1",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^4.2.1",
"github-label-sync": "^2.2.0",
"husky": "^8.0.1",
"japa": "^4.0.0",
"mrm": "^4.1.6",
"np": "^7.6.2",
"prettier": "^2.7.1",
"typescript": "^4.8.4"
},
"nyc": {
"exclude": [
"test"
],
"extension": [
".ts"
]
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/macroable.git"
},
"bugs": {
"url": "https://github.com/poppinss/macroable/issues"
},
"homepage": "https://github.com/poppinss/macroable#readme",
"np": {
"contents": ".",
"anyBranch": false
},
"mrmConfig": {
"core": false,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "16.13.1",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": false
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}