[d24f17c] | 1 | /**
|
---|
| 2 | * Utility function that works like `Object.apply`, but copies getters and setters properly as well. Additionally gives
|
---|
| 3 | * the option to exclude properties by name.
|
---|
| 4 | */
|
---|
| 5 | const copyProps = (dest, src, exclude = []) => {
|
---|
| 6 | const props = Object.getOwnPropertyDescriptors(src);
|
---|
| 7 | for (let prop of exclude)
|
---|
| 8 | delete props[prop];
|
---|
| 9 | Object.defineProperties(dest, props);
|
---|
| 10 | };
|
---|
| 11 | /**
|
---|
| 12 | * Returns the full chain of prototypes up until Object.prototype given a starting object. The order of prototypes will
|
---|
| 13 | * be closest to farthest in the chain.
|
---|
| 14 | */
|
---|
| 15 | const protoChain = (obj, currentChain = [obj]) => {
|
---|
| 16 | const proto = Object.getPrototypeOf(obj);
|
---|
| 17 | if (proto === null)
|
---|
| 18 | return currentChain;
|
---|
| 19 | return protoChain(proto, [...currentChain, proto]);
|
---|
| 20 | };
|
---|
| 21 | /**
|
---|
| 22 | * Identifies the nearest ancestor common to all the given objects in their prototype chains. For most unrelated
|
---|
| 23 | * objects, this function should return Object.prototype.
|
---|
| 24 | */
|
---|
| 25 | const nearestCommonProto = (...objs) => {
|
---|
| 26 | if (objs.length === 0)
|
---|
| 27 | return undefined;
|
---|
| 28 | let commonProto = undefined;
|
---|
| 29 | const protoChains = objs.map(obj => protoChain(obj));
|
---|
| 30 | while (protoChains.every(protoChain => protoChain.length > 0)) {
|
---|
| 31 | const protos = protoChains.map(protoChain => protoChain.pop());
|
---|
| 32 | const potentialCommonProto = protos[0];
|
---|
| 33 | if (protos.every(proto => proto === potentialCommonProto))
|
---|
| 34 | commonProto = potentialCommonProto;
|
---|
| 35 | else
|
---|
| 36 | break;
|
---|
| 37 | }
|
---|
| 38 | return commonProto;
|
---|
| 39 | };
|
---|
| 40 | /**
|
---|
| 41 | * Creates a new prototype object that is a mixture of the given prototypes. The mixing is achieved by first
|
---|
| 42 | * identifying the nearest common ancestor and using it as the prototype for a new object. Then all properties/methods
|
---|
| 43 | * downstream of this prototype (ONLY downstream) are copied into the new object.
|
---|
| 44 | *
|
---|
| 45 | * The resulting prototype is more performant than softMixProtos(...), as well as ES5 compatible. However, it's not as
|
---|
| 46 | * flexible as updates to the source prototypes aren't captured by the mixed result. See softMixProtos for why you may
|
---|
| 47 | * want to use that instead.
|
---|
| 48 | */
|
---|
| 49 | const hardMixProtos = (ingredients, constructor, exclude = []) => {
|
---|
| 50 | var _a;
|
---|
| 51 | const base = (_a = nearestCommonProto(...ingredients)) !== null && _a !== void 0 ? _a : Object.prototype;
|
---|
| 52 | const mixedProto = Object.create(base);
|
---|
| 53 | // Keeps track of prototypes we've already visited to avoid copying the same properties multiple times. We init the
|
---|
| 54 | // list with the proto chain below the nearest common ancestor because we don't want any of those methods mixed in
|
---|
| 55 | // when they will already be accessible via prototype access.
|
---|
| 56 | const visitedProtos = protoChain(base);
|
---|
| 57 | for (let prototype of ingredients) {
|
---|
| 58 | let protos = protoChain(prototype);
|
---|
| 59 | // Apply the prototype chain in reverse order so that old methods don't override newer ones.
|
---|
| 60 | for (let i = protos.length - 1; i >= 0; i--) {
|
---|
| 61 | let newProto = protos[i];
|
---|
| 62 | if (visitedProtos.indexOf(newProto) === -1) {
|
---|
| 63 | copyProps(mixedProto, newProto, ['constructor', ...exclude]);
|
---|
| 64 | visitedProtos.push(newProto);
|
---|
| 65 | }
|
---|
| 66 | }
|
---|
| 67 | }
|
---|
| 68 | mixedProto.constructor = constructor;
|
---|
| 69 | return mixedProto;
|
---|
| 70 | };
|
---|
| 71 | const unique = (arr) => arr.filter((e, i) => arr.indexOf(e) == i);
|
---|
| 72 |
|
---|
| 73 | /**
|
---|
| 74 | * Finds the ingredient with the given prop, searching in reverse order and breadth-first if searching ingredient
|
---|
| 75 | * prototypes is required.
|
---|
| 76 | */
|
---|
| 77 | const getIngredientWithProp = (prop, ingredients) => {
|
---|
| 78 | const protoChains = ingredients.map(ingredient => protoChain(ingredient));
|
---|
| 79 | // since we search breadth-first, we need to keep track of our depth in the prototype chains
|
---|
| 80 | let protoDepth = 0;
|
---|
| 81 | // not all prototype chains are the same depth, so this remains true as long as at least one of the ingredients'
|
---|
| 82 | // prototype chains has an object at this depth
|
---|
| 83 | let protosAreLeftToSearch = true;
|
---|
| 84 | while (protosAreLeftToSearch) {
|
---|
| 85 | // with the start of each horizontal slice, we assume this is the one that's deeper than any of the proto chains
|
---|
| 86 | protosAreLeftToSearch = false;
|
---|
| 87 | // scan through the ingredients right to left
|
---|
| 88 | for (let i = ingredients.length - 1; i >= 0; i--) {
|
---|
| 89 | const searchTarget = protoChains[i][protoDepth];
|
---|
| 90 | if (searchTarget !== undefined && searchTarget !== null) {
|
---|
| 91 | // if we find something, this is proof that this horizontal slice potentially more objects to search
|
---|
| 92 | protosAreLeftToSearch = true;
|
---|
| 93 | // eureka, we found it
|
---|
| 94 | if (Object.getOwnPropertyDescriptor(searchTarget, prop) != undefined) {
|
---|
| 95 | return protoChains[i][0];
|
---|
| 96 | }
|
---|
| 97 | }
|
---|
| 98 | }
|
---|
| 99 | protoDepth++;
|
---|
| 100 | }
|
---|
| 101 | return undefined;
|
---|
| 102 | };
|
---|
| 103 | /**
|
---|
| 104 | * "Mixes" ingredients by wrapping them in a Proxy. The optional prototype argument allows the mixed object to sit
|
---|
| 105 | * downstream of an existing prototype chain. Note that "properties" cannot be added, deleted, or modified.
|
---|
| 106 | */
|
---|
| 107 | const proxyMix = (ingredients, prototype = Object.prototype) => new Proxy({}, {
|
---|
| 108 | getPrototypeOf() {
|
---|
| 109 | return prototype;
|
---|
| 110 | },
|
---|
| 111 | setPrototypeOf() {
|
---|
| 112 | throw Error('Cannot set prototype of Proxies created by ts-mixer');
|
---|
| 113 | },
|
---|
| 114 | getOwnPropertyDescriptor(_, prop) {
|
---|
| 115 | return Object.getOwnPropertyDescriptor(getIngredientWithProp(prop, ingredients) || {}, prop);
|
---|
| 116 | },
|
---|
| 117 | defineProperty() {
|
---|
| 118 | throw new Error('Cannot define new properties on Proxies created by ts-mixer');
|
---|
| 119 | },
|
---|
| 120 | has(_, prop) {
|
---|
| 121 | return getIngredientWithProp(prop, ingredients) !== undefined || prototype[prop] !== undefined;
|
---|
| 122 | },
|
---|
| 123 | get(_, prop) {
|
---|
| 124 | return (getIngredientWithProp(prop, ingredients) || prototype)[prop];
|
---|
| 125 | },
|
---|
| 126 | set(_, prop, val) {
|
---|
| 127 | const ingredientWithProp = getIngredientWithProp(prop, ingredients);
|
---|
| 128 | if (ingredientWithProp === undefined)
|
---|
| 129 | throw new Error('Cannot set new properties on Proxies created by ts-mixer');
|
---|
| 130 | ingredientWithProp[prop] = val;
|
---|
| 131 | return true;
|
---|
| 132 | },
|
---|
| 133 | deleteProperty() {
|
---|
| 134 | throw new Error('Cannot delete properties on Proxies created by ts-mixer');
|
---|
| 135 | },
|
---|
| 136 | ownKeys() {
|
---|
| 137 | return ingredients
|
---|
| 138 | .map(Object.getOwnPropertyNames)
|
---|
| 139 | .reduce((prev, curr) => curr.concat(prev.filter(key => curr.indexOf(key) < 0)));
|
---|
| 140 | },
|
---|
| 141 | });
|
---|
| 142 | /**
|
---|
| 143 | * Creates a new proxy-prototype object that is a "soft" mixture of the given prototypes. The mixing is achieved by
|
---|
| 144 | * proxying all property access to the ingredients. This is not ES5 compatible and less performant. However, any
|
---|
| 145 | * changes made to the source prototypes will be reflected in the proxy-prototype, which may be desirable.
|
---|
| 146 | */
|
---|
| 147 | const softMixProtos = (ingredients, constructor) => proxyMix([...ingredients, { constructor }]);
|
---|
| 148 |
|
---|
| 149 | const settings = {
|
---|
| 150 | initFunction: null,
|
---|
| 151 | staticsStrategy: 'copy',
|
---|
| 152 | prototypeStrategy: 'copy',
|
---|
| 153 | decoratorInheritance: 'deep',
|
---|
| 154 | };
|
---|
| 155 |
|
---|
| 156 | // Keeps track of constituent classes for every mixin class created by ts-mixer.
|
---|
| 157 | const mixins = new Map();
|
---|
| 158 | const getMixinsForClass = (clazz) => mixins.get(clazz);
|
---|
| 159 | const registerMixins = (mixedClass, constituents) => mixins.set(mixedClass, constituents);
|
---|
| 160 | const hasMixin = (instance, mixin) => {
|
---|
| 161 | if (instance instanceof mixin)
|
---|
| 162 | return true;
|
---|
| 163 | const constructor = instance.constructor;
|
---|
| 164 | const visited = new Set();
|
---|
| 165 | let frontier = new Set();
|
---|
| 166 | frontier.add(constructor);
|
---|
| 167 | while (frontier.size > 0) {
|
---|
| 168 | // check if the frontier has the mixin we're looking for. if not, we can say we visited every item in the frontier
|
---|
| 169 | if (frontier.has(mixin))
|
---|
| 170 | return true;
|
---|
| 171 | frontier.forEach(item => visited.add(item));
|
---|
| 172 | // build a new frontier based on the associated mixin classes and prototype chains of each frontier item
|
---|
| 173 | const newFrontier = new Set();
|
---|
| 174 | frontier.forEach(item => {
|
---|
| 175 | var _a;
|
---|
| 176 | const itemConstituents = (_a = mixins.get(item)) !== null && _a !== void 0 ? _a : protoChain(item.prototype).map(proto => proto.constructor).filter(item => item !== null);
|
---|
| 177 | if (itemConstituents)
|
---|
| 178 | itemConstituents.forEach(constituent => {
|
---|
| 179 | if (!visited.has(constituent) && !frontier.has(constituent))
|
---|
| 180 | newFrontier.add(constituent);
|
---|
| 181 | });
|
---|
| 182 | });
|
---|
| 183 | // we have a new frontier, now search again
|
---|
| 184 | frontier = newFrontier;
|
---|
| 185 | }
|
---|
| 186 | // if we get here, we couldn't find the mixin anywhere in the prototype chain or associated mixin classes
|
---|
| 187 | return false;
|
---|
| 188 | };
|
---|
| 189 |
|
---|
| 190 | const mergeObjectsOfDecorators = (o1, o2) => {
|
---|
| 191 | var _a, _b;
|
---|
| 192 | const allKeys = unique([...Object.getOwnPropertyNames(o1), ...Object.getOwnPropertyNames(o2)]);
|
---|
| 193 | const mergedObject = {};
|
---|
| 194 | for (let key of allKeys)
|
---|
| 195 | mergedObject[key] = 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 : [])]);
|
---|
| 196 | return mergedObject;
|
---|
| 197 | };
|
---|
| 198 | const mergePropertyAndMethodDecorators = (d1, d2) => {
|
---|
| 199 | var _a, _b, _c, _d;
|
---|
| 200 | return ({
|
---|
| 201 | 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 : {}),
|
---|
| 202 | 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 : {}),
|
---|
| 203 | });
|
---|
| 204 | };
|
---|
| 205 | const mergeDecorators = (d1, d2) => {
|
---|
| 206 | var _a, _b, _c, _d, _e, _f;
|
---|
| 207 | return ({
|
---|
| 208 | class: 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 : []]),
|
---|
| 209 | 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 : {}),
|
---|
| 210 | 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 : {}),
|
---|
| 211 | });
|
---|
| 212 | };
|
---|
| 213 | const decorators = new Map();
|
---|
| 214 | const findAllConstituentClasses = (...classes) => {
|
---|
| 215 | var _a;
|
---|
| 216 | const allClasses = new Set();
|
---|
| 217 | const frontier = new Set([...classes]);
|
---|
| 218 | while (frontier.size > 0) {
|
---|
| 219 | for (let clazz of frontier) {
|
---|
| 220 | const protoChainClasses = protoChain(clazz.prototype).map(proto => proto.constructor);
|
---|
| 221 | const mixinClasses = (_a = getMixinsForClass(clazz)) !== null && _a !== void 0 ? _a : [];
|
---|
| 222 | const potentiallyNewClasses = [...protoChainClasses, ...mixinClasses];
|
---|
| 223 | const newClasses = potentiallyNewClasses.filter(c => !allClasses.has(c));
|
---|
| 224 | for (let newClass of newClasses)
|
---|
| 225 | frontier.add(newClass);
|
---|
| 226 | allClasses.add(clazz);
|
---|
| 227 | frontier.delete(clazz);
|
---|
| 228 | }
|
---|
| 229 | }
|
---|
| 230 | return [...allClasses];
|
---|
| 231 | };
|
---|
| 232 | const deepDecoratorSearch = (...classes) => {
|
---|
| 233 | const decoratorsForClassChain = findAllConstituentClasses(...classes)
|
---|
| 234 | .map(clazz => decorators.get(clazz))
|
---|
| 235 | .filter(decorators => !!decorators);
|
---|
| 236 | if (decoratorsForClassChain.length == 0)
|
---|
| 237 | return {};
|
---|
| 238 | if (decoratorsForClassChain.length == 1)
|
---|
| 239 | return decoratorsForClassChain[0];
|
---|
| 240 | return decoratorsForClassChain.reduce((d1, d2) => mergeDecorators(d1, d2));
|
---|
| 241 | };
|
---|
| 242 | const directDecoratorSearch = (...classes) => {
|
---|
| 243 | const classDecorators = classes.map(clazz => getDecoratorsForClass(clazz));
|
---|
| 244 | if (classDecorators.length === 0)
|
---|
| 245 | return {};
|
---|
| 246 | if (classDecorators.length === 1)
|
---|
| 247 | return classDecorators[0];
|
---|
| 248 | return classDecorators.reduce((d1, d2) => mergeDecorators(d1, d2));
|
---|
| 249 | };
|
---|
| 250 | const getDecoratorsForClass = (clazz) => {
|
---|
| 251 | let decoratorsForClass = decorators.get(clazz);
|
---|
| 252 | if (!decoratorsForClass) {
|
---|
| 253 | decoratorsForClass = {};
|
---|
| 254 | decorators.set(clazz, decoratorsForClass);
|
---|
| 255 | }
|
---|
| 256 | return decoratorsForClass;
|
---|
| 257 | };
|
---|
| 258 | const decorateClass = (decorator) => ((clazz) => {
|
---|
| 259 | const decoratorsForClass = getDecoratorsForClass(clazz);
|
---|
| 260 | let classDecorators = decoratorsForClass.class;
|
---|
| 261 | if (!classDecorators) {
|
---|
| 262 | classDecorators = [];
|
---|
| 263 | decoratorsForClass.class = classDecorators;
|
---|
| 264 | }
|
---|
| 265 | classDecorators.push(decorator);
|
---|
| 266 | return decorator(clazz);
|
---|
| 267 | });
|
---|
| 268 | const decorateMember = (decorator) => ((object, key, ...otherArgs) => {
|
---|
| 269 | var _a, _b, _c;
|
---|
| 270 | const decoratorTargetType = typeof object === 'function' ? 'static' : 'instance';
|
---|
| 271 | const decoratorType = typeof object[key] === 'function' ? 'method' : 'property';
|
---|
| 272 | const clazz = decoratorTargetType === 'static' ? object : object.constructor;
|
---|
| 273 | const decoratorsForClass = getDecoratorsForClass(clazz);
|
---|
| 274 | const decoratorsForTargetType = (_a = decoratorsForClass === null || decoratorsForClass === void 0 ? void 0 : decoratorsForClass[decoratorTargetType]) !== null && _a !== void 0 ? _a : {};
|
---|
| 275 | decoratorsForClass[decoratorTargetType] = decoratorsForTargetType;
|
---|
| 276 | let decoratorsForType = (_b = decoratorsForTargetType === null || decoratorsForTargetType === void 0 ? void 0 : decoratorsForTargetType[decoratorType]) !== null && _b !== void 0 ? _b : {};
|
---|
| 277 | decoratorsForTargetType[decoratorType] = decoratorsForType;
|
---|
| 278 | let decoratorsForKey = (_c = decoratorsForType === null || decoratorsForType === void 0 ? void 0 : decoratorsForType[key]) !== null && _c !== void 0 ? _c : [];
|
---|
| 279 | decoratorsForType[key] = decoratorsForKey;
|
---|
| 280 | // @ts-ignore: array is type `A[] | B[]` and item is type `A | B`, so technically a type error, but it's fine
|
---|
| 281 | decoratorsForKey.push(decorator);
|
---|
| 282 | // @ts-ignore
|
---|
| 283 | return decorator(object, key, ...otherArgs);
|
---|
| 284 | });
|
---|
| 285 | const decorate = (decorator) => ((...args) => {
|
---|
| 286 | if (args.length === 1)
|
---|
| 287 | return decorateClass(decorator)(args[0]);
|
---|
| 288 | return decorateMember(decorator)(...args);
|
---|
| 289 | });
|
---|
| 290 |
|
---|
| 291 | function Mixin(...constructors) {
|
---|
| 292 | var _a, _b, _c;
|
---|
| 293 | const prototypes = constructors.map(constructor => constructor.prototype);
|
---|
| 294 | // Here we gather up the init functions of the ingredient prototypes, combine them into one init function, and
|
---|
| 295 | // attach it to the mixed class prototype. The reason we do this is because we want the init functions to mix
|
---|
| 296 | // similarly to constructors -- not methods, which simply override each other.
|
---|
| 297 | const initFunctionName = settings.initFunction;
|
---|
| 298 | if (initFunctionName !== null) {
|
---|
| 299 | const initFunctions = prototypes
|
---|
| 300 | .map(proto => proto[initFunctionName])
|
---|
| 301 | .filter(func => typeof func === 'function');
|
---|
| 302 | const combinedInitFunction = function (...args) {
|
---|
| 303 | for (let initFunction of initFunctions)
|
---|
| 304 | initFunction.apply(this, args);
|
---|
| 305 | };
|
---|
| 306 | const extraProto = { [initFunctionName]: combinedInitFunction };
|
---|
| 307 | prototypes.push(extraProto);
|
---|
| 308 | }
|
---|
| 309 | function MixedClass(...args) {
|
---|
| 310 | for (const constructor of constructors)
|
---|
| 311 | // @ts-ignore: potentially abstract class
|
---|
| 312 | copyProps(this, new constructor(...args));
|
---|
| 313 | if (initFunctionName !== null && typeof this[initFunctionName] === 'function')
|
---|
| 314 | this[initFunctionName].apply(this, args);
|
---|
| 315 | }
|
---|
| 316 | MixedClass.prototype = settings.prototypeStrategy === 'copy'
|
---|
| 317 | ? hardMixProtos(prototypes, MixedClass)
|
---|
| 318 | : softMixProtos(prototypes, MixedClass);
|
---|
| 319 | Object.setPrototypeOf(MixedClass, settings.staticsStrategy === 'copy'
|
---|
| 320 | ? hardMixProtos(constructors, null, ['prototype'])
|
---|
| 321 | : proxyMix(constructors, Function.prototype));
|
---|
| 322 | let DecoratedMixedClass = MixedClass;
|
---|
| 323 | if (settings.decoratorInheritance !== 'none') {
|
---|
| 324 | const classDecorators = settings.decoratorInheritance === 'deep'
|
---|
| 325 | ? deepDecoratorSearch(...constructors)
|
---|
| 326 | : directDecoratorSearch(...constructors);
|
---|
| 327 | for (let decorator of (_a = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.class) !== null && _a !== void 0 ? _a : []) {
|
---|
| 328 | const result = decorator(DecoratedMixedClass);
|
---|
| 329 | if (result) {
|
---|
| 330 | DecoratedMixedClass = result;
|
---|
| 331 | }
|
---|
| 332 | }
|
---|
| 333 | applyPropAndMethodDecorators((_b = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.static) !== null && _b !== void 0 ? _b : {}, DecoratedMixedClass);
|
---|
| 334 | applyPropAndMethodDecorators((_c = classDecorators === null || classDecorators === void 0 ? void 0 : classDecorators.instance) !== null && _c !== void 0 ? _c : {}, DecoratedMixedClass.prototype);
|
---|
| 335 | }
|
---|
| 336 | registerMixins(DecoratedMixedClass, constructors);
|
---|
| 337 | return DecoratedMixedClass;
|
---|
| 338 | }
|
---|
| 339 | const applyPropAndMethodDecorators = (propAndMethodDecorators, target) => {
|
---|
| 340 | const propDecorators = propAndMethodDecorators.property;
|
---|
| 341 | const methodDecorators = propAndMethodDecorators.method;
|
---|
| 342 | if (propDecorators)
|
---|
| 343 | for (let key in propDecorators)
|
---|
| 344 | for (let decorator of propDecorators[key])
|
---|
| 345 | decorator(target, key);
|
---|
| 346 | if (methodDecorators)
|
---|
| 347 | for (let key in methodDecorators)
|
---|
| 348 | for (let decorator of methodDecorators[key])
|
---|
| 349 | decorator(target, key, Object.getOwnPropertyDescriptor(target, key));
|
---|
| 350 | };
|
---|
| 351 | /**
|
---|
| 352 | * A decorator version of the `Mixin` function. You'll want to use this instead of `Mixin` for mixing generic classes.
|
---|
| 353 | */
|
---|
| 354 | const mix = (...ingredients) => decoratedClass => {
|
---|
| 355 | // @ts-ignore
|
---|
| 356 | const mixedClass = Mixin(...ingredients.concat([decoratedClass]));
|
---|
| 357 | Object.defineProperty(mixedClass, 'name', {
|
---|
| 358 | value: decoratedClass.name,
|
---|
| 359 | writable: false,
|
---|
| 360 | });
|
---|
| 361 | return mixedClass;
|
---|
| 362 | };
|
---|
| 363 |
|
---|
| 364 | export { Mixin, decorate, hasMixin, mix, settings };
|
---|