Comparing version 5.3.0 to 5.4.0
@@ -5,2 +5,9 @@ # Changelog | ||
## [5.4.0](https://github.com/tannerntannern/ts-mixer/compare/v5.3.0...v5.4.0) (2020-11-18) | ||
### Features | ||
* deep decorator inheritance ([6daabc5](https://github.com/tannerntannern/ts-mixer/commit/6daabc5d340d20c8eda4fe96b635a54f6a7e18fb)) | ||
## [5.3.0](https://github.com/tannerntannern/ts-mixer/compare/v5.3.0-beta.0...v5.3.0) (2020-06-01) | ||
@@ -7,0 +14,0 @@ |
import { Class } from './types'; | ||
declare type ObjectOfDecorators<T extends PropertyDecorator | MethodDecorator> = { | ||
[key: string]: T[]; | ||
}; | ||
export declare type PropertyAndMethodDecorators = { | ||
property?: { | ||
[key: string]: PropertyDecorator[]; | ||
}; | ||
method?: { | ||
[key: string]: MethodDecorator[]; | ||
}; | ||
property?: ObjectOfDecorators<PropertyDecorator>; | ||
method?: ObjectOfDecorators<MethodDecorator>; | ||
}; | ||
@@ -15,4 +14,6 @@ declare type Decorators = { | ||
}; | ||
export declare const decorators: Map<Class, Decorators>; | ||
export declare const deepDecoratorSearch: (...classes: Class[]) => Decorators; | ||
export declare const directDecoratorSearch: (...classes: Class[]) => Decorators; | ||
export declare const getDecoratorsForClass: (clazz: Class) => Decorators; | ||
export declare const decorate: <T extends PropertyDecorator | MethodDecorator | ClassDecorator>(decorator: T) => T; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.decorate = exports.decorators = void 0; | ||
exports.decorators = new Map(); | ||
const getDecoratorsForClass = (clazz) => { | ||
let decoratorsForClass = exports.decorators.get(clazz); | ||
exports.decorate = exports.getDecoratorsForClass = exports.directDecoratorSearch = exports.deepDecoratorSearch = void 0; | ||
const util_1 = require("./util"); | ||
const mixin_tracking_1 = require("./mixin-tracking"); | ||
const mergeObjectsOfDecorators = (o1, o2) => { | ||
var _a, _b; | ||
const allKeys = util_1.unique([...Object.getOwnPropertyNames(o1), ...Object.getOwnPropertyNames(o2)]); | ||
const mergedObject = {}; | ||
for (let key of allKeys) | ||
mergedObject[key] = util_1.unique([...((_a = o1 === null || o1 === void 0 ? void 0 : o1[key]) !== null && _a !== void 0 ? _a : []), ...((_b = o2 === null || o2 === void 0 ? void 0 : o2[key]) !== null && _b !== void 0 ? _b : [])]); | ||
return mergedObject; | ||
}; | ||
const mergePropertyAndMethodDecorators = (d1, d2) => { | ||
var _a, _b, _c, _d; | ||
return ({ | ||
property: mergeObjectsOfDecorators((_a = d1 === null || d1 === void 0 ? void 0 : d1.property) !== null && _a !== void 0 ? _a : {}, (_b = d2 === null || d2 === void 0 ? void 0 : d2.property) !== null && _b !== void 0 ? _b : {}), | ||
method: mergeObjectsOfDecorators((_c = d1 === null || d1 === void 0 ? void 0 : d1.method) !== null && _c !== void 0 ? _c : {}, (_d = d2 === null || d2 === void 0 ? void 0 : d2.method) !== null && _d !== void 0 ? _d : {}), | ||
}); | ||
}; | ||
const mergeDecorators = (d1, d2) => { | ||
var _a, _b, _c, _d, _e, _f; | ||
return ({ | ||
class: util_1.unique([...(_a = d1 === null || d1 === void 0 ? void 0 : d1.class) !== null && _a !== void 0 ? _a : [], ...(_b = d2 === null || d2 === void 0 ? void 0 : d2.class) !== null && _b !== void 0 ? _b : []]), | ||
static: mergePropertyAndMethodDecorators((_c = d1 === null || d1 === void 0 ? void 0 : d1.static) !== null && _c !== void 0 ? _c : {}, (_d = d2 === null || d2 === void 0 ? void 0 : d2.static) !== null && _d !== void 0 ? _d : {}), | ||
instance: mergePropertyAndMethodDecorators((_e = d1 === null || d1 === void 0 ? void 0 : d1.instance) !== null && _e !== void 0 ? _e : {}, (_f = d2 === null || d2 === void 0 ? void 0 : d2.instance) !== null && _f !== void 0 ? _f : {}), | ||
}); | ||
}; | ||
const decorators = new Map(); | ||
const findAllConstituentClasses = (...classes) => { | ||
var _a; | ||
const allClasses = new Set(); | ||
const frontier = new Set([...classes]); | ||
while (frontier.size > 0) { | ||
for (let clazz of frontier) { | ||
const protoChainClasses = util_1.protoChain(clazz.prototype).map(proto => proto.constructor); | ||
const mixinClasses = (_a = mixin_tracking_1.getMixinsForClass(clazz)) !== null && _a !== void 0 ? _a : []; | ||
const potentiallyNewClasses = [...protoChainClasses, ...mixinClasses]; | ||
const newClasses = potentiallyNewClasses.filter(c => !allClasses.has(c)); | ||
for (let newClass of newClasses) | ||
frontier.add(newClass); | ||
allClasses.add(clazz); | ||
frontier.delete(clazz); | ||
} | ||
} | ||
return [...allClasses]; | ||
}; | ||
exports.deepDecoratorSearch = (...classes) => { | ||
const decoratorsForClassChain = findAllConstituentClasses(...classes) | ||
.map(clazz => decorators.get(clazz)) | ||
.filter(decorators => !!decorators); | ||
if (decoratorsForClassChain.length == 0) | ||
return {}; | ||
if (decoratorsForClassChain.length == 1) | ||
return decoratorsForClassChain[0]; | ||
return decoratorsForClassChain.reduce((d1, d2) => mergeDecorators(d1, d2)); | ||
}; | ||
exports.directDecoratorSearch = (...classes) => { | ||
const classDecorators = classes.map(clazz => exports.getDecoratorsForClass(clazz)); | ||
if (classDecorators.length === 0) | ||
return {}; | ||
if (classDecorators.length === 1) | ||
return classDecorators[1]; | ||
return classDecorators.reduce((d1, d2) => mergeDecorators(d1, d2)); | ||
}; | ||
exports.getDecoratorsForClass = (clazz) => { | ||
let decoratorsForClass = decorators.get(clazz); | ||
if (!decoratorsForClass) { | ||
decoratorsForClass = {}; | ||
exports.decorators.set(clazz, decoratorsForClass); | ||
decorators.set(clazz, decoratorsForClass); | ||
} | ||
@@ -14,3 +75,3 @@ return decoratorsForClass; | ||
const decorateClass = (decorator) => ((clazz) => { | ||
const decoratorsForClass = getDecoratorsForClass(clazz); | ||
const decoratorsForClass = exports.getDecoratorsForClass(clazz); | ||
let classDecorators = decoratorsForClass.class; | ||
@@ -28,3 +89,3 @@ if (!classDecorators) { | ||
const clazz = decoratorTargetType === 'static' ? object : object.constructor; | ||
const decoratorsForClass = getDecoratorsForClass(clazz); | ||
const decoratorsForClass = exports.getDecoratorsForClass(clazz); | ||
let decoratorsForTargetType = decoratorsForClass === null || decoratorsForClass === void 0 ? void 0 : decoratorsForClass[decoratorTargetType]; | ||
@@ -31,0 +92,0 @@ if (!decoratorsForTargetType) { |
@@ -0,2 +1,4 @@ | ||
import { Class } from './types'; | ||
export declare const getMixinsForClass: (clazz: Class) => Function[] | undefined; | ||
export declare const registerMixins: (mixedClass: any, constituents: Function[]) => Map<any, Function[]>; | ||
export declare const hasMixin: <M>(instance: any, mixin: new (...args: any[]) => M) => instance is M; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.hasMixin = exports.registerMixins = void 0; | ||
/** | ||
* Keeps track of constituent classes for every mixin class created by ts-mixer. | ||
*/ | ||
exports.hasMixin = exports.registerMixins = exports.getMixinsForClass = void 0; | ||
const util_1 = require("./util"); | ||
// Keeps track of constituent classes for every mixin class created by ts-mixer. | ||
const mixins = new Map(); | ||
exports.getMixinsForClass = (clazz) => mixins.get(clazz); | ||
exports.registerMixins = (mixedClass, constituents) => mixins.set(mixedClass, constituents); | ||
@@ -10,0 +9,0 @@ exports.hasMixin = (instance, mixin) => { |
@@ -10,2 +10,3 @@ "use strict"; | ||
function Mixin(...constructors) { | ||
var _a, _b, _c; | ||
const prototypes = constructors.map(constructor => constructor.prototype); | ||
@@ -40,13 +41,10 @@ // Here we gather up the init functions of the ingredient prototypes, combine them into one init function, and | ||
let DecoratedMixedClass = MixedClass; | ||
for (let constructor of constructors) { | ||
const classDecorators = decorator_1.decorators.get(constructor); | ||
if (classDecorators) { | ||
if (classDecorators.class) | ||
for (let decorator of classDecorators.class) | ||
DecoratedMixedClass = decorator(DecoratedMixedClass); | ||
if (classDecorators.static) | ||
applyPropAndMethodDecorators(classDecorators.static, DecoratedMixedClass); | ||
if (classDecorators.instance) | ||
applyPropAndMethodDecorators(classDecorators.instance, DecoratedMixedClass.prototype); | ||
} | ||
if (settings_1.settings.decoratorInheritance !== 'none') { | ||
const classDecorators = settings_1.settings.decoratorInheritance === 'deep' | ||
? decorator_1.deepDecoratorSearch(...constructors) | ||
: decorator_1.directDecoratorSearch(...constructors); | ||
for (let decorator of (_a = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.class) !== null && _a !== void 0 ? _a : []) | ||
DecoratedMixedClass = decorator(DecoratedMixedClass); | ||
applyPropAndMethodDecorators((_b = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.static) !== null && _b !== void 0 ? _b : {}, DecoratedMixedClass); | ||
applyPropAndMethodDecorators((_c = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.instance) !== null && _c !== void 0 ? _c : {}, DecoratedMixedClass.prototype); | ||
} | ||
@@ -53,0 +51,0 @@ mixin_tracking_1.registerMixins(DecoratedMixedClass, constructors); |
@@ -5,3 +5,3 @@ /** | ||
*/ | ||
export declare const getIngredientWithProp: (prop: string | number | symbol, ingredients: any[]) => object; | ||
export declare const getIngredientWithProp: (prop: string | number | symbol, ingredients: any[]) => object | undefined; | ||
/** | ||
@@ -8,0 +8,0 @@ * "Mixes" ingredients by wrapping them in a Proxy. The optional prototype argument allows the mixed object to sit |
@@ -5,3 +5,4 @@ export declare type Settings = { | ||
prototypeStrategy: 'copy' | 'proxy'; | ||
decoratorInheritance: 'deep' | 'direct' | 'none'; | ||
}; | ||
export declare const settings: Settings; |
@@ -8,2 +8,3 @@ "use strict"; | ||
prototypeStrategy: 'copy', | ||
decoratorInheritance: 'deep', | ||
}; |
@@ -15,3 +15,3 @@ /** | ||
*/ | ||
export declare const nearestCommonProto: (...objs: object[]) => Function; | ||
export declare const nearestCommonProto: (...objs: object[]) => object | undefined; | ||
/** | ||
@@ -26,3 +26,3 @@ * Creates a new prototype object that is a mixture of the given prototypes. The mixing is achieved by first | ||
*/ | ||
export declare const hardMixProtos: (ingredients: any[], constructor: Function, exclude?: string[]) => object; | ||
export declare const hardMixProtos: (ingredients: any[], constructor: Function | null, exclude?: string[]) => object; | ||
/** | ||
@@ -34,1 +34,3 @@ * Creates a new proxy-prototype object that is a "soft" mixture of the given prototypes. The mixing is achieved by | ||
export declare const softMixProtos: (ingredients: any[], constructor: Function) => object; | ||
export declare const unique: <T>(arr: T[]) => T[]; | ||
export declare const flatten: <T>(arr: T[][]) => T[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.softMixProtos = exports.hardMixProtos = exports.nearestCommonProto = exports.protoChain = exports.copyProps = void 0; | ||
exports.flatten = exports.unique = exports.softMixProtos = exports.hardMixProtos = exports.nearestCommonProto = exports.protoChain = exports.copyProps = void 0; | ||
const proxy_1 = require("./proxy"); | ||
@@ -54,3 +54,4 @@ /** | ||
exports.hardMixProtos = (ingredients, constructor, exclude = []) => { | ||
const base = exports.nearestCommonProto(...ingredients); | ||
var _a; | ||
const base = (_a = exports.nearestCommonProto(...ingredients)) !== null && _a !== void 0 ? _a : Object.prototype; | ||
const mixedProto = Object.create(base); | ||
@@ -81,3 +82,9 @@ // Keeps track of prototypes we've already visited to avoid copying the same properties multiple times. We init the | ||
exports.softMixProtos = (ingredients, constructor) => { | ||
return proxy_1.proxyMix([...ingredients, { constructor }], null); | ||
return proxy_1.proxyMix([...ingredients, { constructor }]); | ||
}; | ||
exports.unique = (arr) => arr.filter((e, i) => arr.indexOf(e) == i); | ||
exports.flatten = (arr) => arr.length === 0 | ||
? [] | ||
: arr.length === 1 | ||
? arr[0] | ||
: arr.reduce((a1, a2) => [...a1, ...a2]); |
{ | ||
"name": "ts-mixer", | ||
"version": "5.3.0", | ||
"version": "5.4.0", | ||
"description": "A very small TypeScript library that provides tolerable Mixin functionality.", | ||
@@ -16,3 +16,3 @@ "main": "dist/index.js", | ||
"test": "nyc mocha", | ||
"coveralls": "cat coverage/lcov.info | coveralls", | ||
"codegen": "node ./codegen.js", | ||
"release": "standard-version" | ||
@@ -33,2 +33,3 @@ }, | ||
"husky": "^4.2.5", | ||
"js-yaml": "^3.14.0", | ||
"mocha": "^7.2.0", | ||
@@ -40,3 +41,4 @@ "nyc": "14.1.1", | ||
"ts-node": "^8.10.2", | ||
"typescript": "^3.9.3" | ||
"typescript": "^3.9.3", | ||
"yarn-add-no-save": "^1.0.3" | ||
}, | ||
@@ -43,0 +45,0 @@ "homepage": "https://github.com/tannerntannern/ts-mixer#readme", |
# ts-mixer | ||
[![npm version](https://badgen.net/npm/v/ts-mixer)](https://npmjs.com/package/ts-mixer) | ||
[![Build Status](https://travis-ci.org/tannerntannern/ts-mixer.svg?branch=master)](https://travis-ci.org/tannerntannern/ts-mixer) | ||
[![Coverage Status](https://coveralls.io/repos/github/tannerntannern/ts-mixer/badge.svg?branch=master)](https://coveralls.io/github/tannerntannern/ts-mixer?branch=master) | ||
[version-badge]: https://badgen.net/npm/v/ts-mixer | ||
[version-link]: https://npmjs.com/package/ts-mixer | ||
[build-badge]: https://img.shields.io/github/workflow/status/tannerntannern/ts-mixer/ts-mixer%20CI | ||
[build-link]: https://github.com/tannerntannern/ts-mixer/actions | ||
[ts-versions]: https://badgen.net/badge/icon/3.8,3.9,4.0?icon=typescript&label&list=| | ||
[node-versions]: https://badgen.net/badge/node/10%2C12%2C14/blue/?list=| | ||
[![npm version][version-badge]][version-link] | ||
[![github actions][build-badge]][build-link] | ||
[![TS Versions][ts-versions]][build-link] | ||
[![Node.js Versions][node-versions]][build-link] | ||
[![Minified Size](https://badgen.net/bundlephobia/min/ts-mixer)](https://bundlephobia.com/result?p=ts-mixer) | ||
@@ -34,3 +41,3 @@ [![Conventional Commits](https://badgen.net/badge/conventional%20commits/1.0.0/yellow)](https://conventionalcommits.org) | ||
5. `ts-mixer` does not support `instanceof` for mixins, but it does offer a replacement. See the [hasMixin function](#hasmixin) for more details. | ||
6. Certain features (specifically, `@dectorator` and `hasMixin`) make use of ES6 `Map`s, which means you must either use ES6+ or polyfill `Map` to use them. If you don't need these features, you should be fine without. | ||
6. Certain features (specifically, `@decorator` and `hasMixin`) make use of ES6 `Map`s, which means you must either use ES6+ or polyfill `Map` to use them. If you don't need these features, you should be fine without. | ||
@@ -286,2 +293,9 @@ ## Quick Start | ||
### `settings.decoratorInheritance` | ||
* Determines how decorators are inherited from classes passed to `Mixin(...)` | ||
* Possible values: | ||
- `'deep'` (default) - Deeply inherits decorators from all given classes and their ancestors | ||
- `'direct'` - Only inherits decorators defined directly on the given classes | ||
- `'none'` - Skips decorator inheritance | ||
# Author | ||
@@ -288,0 +302,0 @@ Tanner Nielsen <tannerntannern@gmail.com> |
49795
526
303
21