From 9922d60e5b8e2888ca4d7ddda035fa36f733553e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 13 Apr 2021 13:01:24 +0200 Subject: [PATCH] feat(@xen-orchestra/mixin): 0.1.0 --- @xen-orchestra/mixin/index.js | 139 ++++++-------------------- @xen-orchestra/mixin/legacy.js | 120 ++++++++++++++++++++++ @xen-orchestra/mixin/package.json | 5 +- @xen-orchestra/proxy/package.json | 1 + @xen-orchestra/proxy/src/app/index.js | 38 +------ packages/xo-server/package.json | 2 +- packages/xo-server/src/xapi/index.js | 2 +- packages/xo-server/src/xo.js | 2 +- 8 files changed, 158 insertions(+), 151 deletions(-) create mode 100644 @xen-orchestra/mixin/legacy.js diff --git a/@xen-orchestra/mixin/index.js b/@xen-orchestra/mixin/index.js index c2424412f..6ec78054a 100644 --- a/@xen-orchestra/mixin/index.js +++ b/@xen-orchestra/mixin/index.js @@ -1,120 +1,39 @@ -const { getBoundPropertyDescriptor } = require('bind-property-descriptor') +const camelCase = require('lodash/camelCase') -// =================================================================== +const { defineProperties, defineProperty, keys } = Object +const noop = Function.prototype -const { defineProperties, getOwnPropertyDescriptor } = Object - -const isIgnoredProperty = name => name[0] === '_' || name === 'constructor' - -const IGNORED_STATIC_PROPERTIES = { - __proto__: null, - - arguments: true, - caller: true, - length: true, - name: true, - prototype: true, +const MIXIN_CYCLIC_DESCRIPTOR = { + configurable: true, + get() { + throw new Error('cyclic dependency') + }, } -const isIgnoredStaticProperty = name => name in IGNORED_STATIC_PROPERTIES -const ownKeys = - (typeof Reflect !== 'undefined' && Reflect.ownKeys) || - (({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) => - symbols !== undefined ? obj => names(obj).concat(symbols(obj)) : names)(Object) +module.exports = function mixin(object, mixins, args) { + // add lazy property for each of the mixin, this allows mixins to depend on + // one another without any special ordering + const descriptors = {} + keys(mixins).forEach(name => { + const Mixin = mixins[name] + name = camelCase(name) -// ------------------------------------------------------------------- - -const mixin = Mixins => Class => { - if (!Array.isArray(Mixins)) { - throw new TypeError('Mixins should be an array') - } - - const { name } = Class - - // Copy properties of plain object mix-ins to the prototype. - { - const allMixins = Mixins - Mixins = [] - const { prototype } = Class - const descriptors = { __proto__: null } - allMixins.forEach(Mixin => { - if (typeof Mixin === 'function') { - Mixins.push(Mixin) - return - } - - for (const prop of ownKeys(Mixin)) { - if (prop in prototype) { - throw new Error(`${name}#${prop} is already defined`) - } - - ;(descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop)).enumerable = false // Object methods are enumerable but class methods are not. - } - }) - defineProperties(prototype, descriptors) - } - - const n = Mixins.length - - function DecoratedClass(...args) { - const instance = new Class(...args) - - for (let i = 0; i < n; ++i) { - const Mixin = Mixins[i] - const { prototype } = Mixin - const mixinInstance = new Mixin(instance, ...args) - const descriptors = { __proto__: null } - const props = ownKeys(prototype) - for (let j = 0, m = props.length; j < m; ++j) { - const prop = props[j] - - if (isIgnoredProperty(prop)) { - continue - } - - if (prop in instance) { - throw new Error(`${name}#${prop} is already defined`) - } - - descriptors[prop] = getBoundPropertyDescriptor(prototype, prop, mixinInstance) - } - defineProperties(instance, descriptors) - } - - return instance - } - - // Copy original and mixed-in static properties on Decorator class. - const descriptors = { __proto__: null } - ownKeys(Class).forEach(prop => { - let descriptor - if ( - !( - isIgnoredStaticProperty(prop) && - // if they already exist... - (descriptor = getOwnPropertyDescriptor(DecoratedClass, prop)) !== undefined && - // and are not configurable. - !descriptor.configurable - ) - ) { - descriptors[prop] = getOwnPropertyDescriptor(Class, prop) + descriptors[name] = { + configurable: true, + get: () => { + defineProperty(object, name, MIXIN_CYCLIC_DESCRIPTOR) + const instance = new Mixin(object, ...args) + defineProperty(object, name, { + value: instance, + }) + return instance + }, } }) - Mixins.forEach(Mixin => { - ownKeys(Mixin).forEach(prop => { - if (isIgnoredStaticProperty(prop)) { - return - } + defineProperties(object, descriptors) - if (prop in descriptors) { - throw new Error(`${name}.${prop} is already defined`) - } - - descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop) - }) + // access all mixin properties to trigger their creation + keys(descriptors).forEach(name => { + noop(object[name]) }) - defineProperties(DecoratedClass, descriptors) - - return DecoratedClass } -module.exports = mixin diff --git a/@xen-orchestra/mixin/legacy.js b/@xen-orchestra/mixin/legacy.js new file mode 100644 index 000000000..c2424412f --- /dev/null +++ b/@xen-orchestra/mixin/legacy.js @@ -0,0 +1,120 @@ +const { getBoundPropertyDescriptor } = require('bind-property-descriptor') + +// =================================================================== + +const { defineProperties, getOwnPropertyDescriptor } = Object + +const isIgnoredProperty = name => name[0] === '_' || name === 'constructor' + +const IGNORED_STATIC_PROPERTIES = { + __proto__: null, + + arguments: true, + caller: true, + length: true, + name: true, + prototype: true, +} +const isIgnoredStaticProperty = name => name in IGNORED_STATIC_PROPERTIES + +const ownKeys = + (typeof Reflect !== 'undefined' && Reflect.ownKeys) || + (({ getOwnPropertyNames: names, getOwnPropertySymbols: symbols }) => + symbols !== undefined ? obj => names(obj).concat(symbols(obj)) : names)(Object) + +// ------------------------------------------------------------------- + +const mixin = Mixins => Class => { + if (!Array.isArray(Mixins)) { + throw new TypeError('Mixins should be an array') + } + + const { name } = Class + + // Copy properties of plain object mix-ins to the prototype. + { + const allMixins = Mixins + Mixins = [] + const { prototype } = Class + const descriptors = { __proto__: null } + allMixins.forEach(Mixin => { + if (typeof Mixin === 'function') { + Mixins.push(Mixin) + return + } + + for (const prop of ownKeys(Mixin)) { + if (prop in prototype) { + throw new Error(`${name}#${prop} is already defined`) + } + + ;(descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop)).enumerable = false // Object methods are enumerable but class methods are not. + } + }) + defineProperties(prototype, descriptors) + } + + const n = Mixins.length + + function DecoratedClass(...args) { + const instance = new Class(...args) + + for (let i = 0; i < n; ++i) { + const Mixin = Mixins[i] + const { prototype } = Mixin + const mixinInstance = new Mixin(instance, ...args) + const descriptors = { __proto__: null } + const props = ownKeys(prototype) + for (let j = 0, m = props.length; j < m; ++j) { + const prop = props[j] + + if (isIgnoredProperty(prop)) { + continue + } + + if (prop in instance) { + throw new Error(`${name}#${prop} is already defined`) + } + + descriptors[prop] = getBoundPropertyDescriptor(prototype, prop, mixinInstance) + } + defineProperties(instance, descriptors) + } + + return instance + } + + // Copy original and mixed-in static properties on Decorator class. + const descriptors = { __proto__: null } + ownKeys(Class).forEach(prop => { + let descriptor + if ( + !( + isIgnoredStaticProperty(prop) && + // if they already exist... + (descriptor = getOwnPropertyDescriptor(DecoratedClass, prop)) !== undefined && + // and are not configurable. + !descriptor.configurable + ) + ) { + descriptors[prop] = getOwnPropertyDescriptor(Class, prop) + } + }) + Mixins.forEach(Mixin => { + ownKeys(Mixin).forEach(prop => { + if (isIgnoredStaticProperty(prop)) { + return + } + + if (prop in descriptors) { + throw new Error(`${name}.${prop} is already defined`) + } + + descriptors[prop] = getOwnPropertyDescriptor(Mixin, prop) + }) + }) + defineProperties(DecoratedClass, descriptors) + + return DecoratedClass +} +module.exports = mixin diff --git a/@xen-orchestra/mixin/package.json b/@xen-orchestra/mixin/package.json index d1a3a7a7c..b4ba7df99 100644 --- a/@xen-orchestra/mixin/package.json +++ b/@xen-orchestra/mixin/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@xen-orchestra/mixin", - "version": "0.0.0", + "version": "0.1.0", "license": "ISC", "homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/mixin", "bugs": "https://github.com/vatesfr/xen-orchestra/issues", @@ -19,7 +19,8 @@ "node": ">=6" }, "dependencies": { - "bind-property-descriptor": "^1.0.0" + "bind-property-descriptor": "^1.0.0", + "lodash": "^4.17.21" }, "scripts": { "postversion": "npm publish" diff --git a/@xen-orchestra/proxy/package.json b/@xen-orchestra/proxy/package.json index 7a3ce43e4..36b4ced9a 100644 --- a/@xen-orchestra/proxy/package.json +++ b/@xen-orchestra/proxy/package.json @@ -41,6 +41,7 @@ "@xen-orchestra/emit-async": "^0.0.0", "@xen-orchestra/fs": "^0.14.0", "@xen-orchestra/log": "^0.2.0", + "@xen-orchestra/mixin": "^0.1.0", "@xen-orchestra/self-signed": "^0.1.0", "@xen-orchestra/xapi": "^0.6.1", "ajv": "^8.0.3", diff --git a/@xen-orchestra/proxy/src/app/index.js b/@xen-orchestra/proxy/src/app/index.js index 7ed8c9d0a..69a6eee6a 100644 --- a/@xen-orchestra/proxy/src/app/index.js +++ b/@xen-orchestra/proxy/src/app/index.js @@ -1,45 +1,11 @@ -import camelCase from 'lodash/camelCase' +import mixin from '@xen-orchestra/mixin' import { createDebounceResource } from '@vates/disposable/debounceResource' import mixins from './mixins' -const { defineProperties, defineProperty, keys } = Object -const noop = Function.prototype - -const MIXIN_CYCLIC_DESCRIPTOR = { - configurable: true, - get() { - throw new Error('cyclic dependency') - }, -} - export default class App { constructor(opts) { - // add lazy property for each of the mixin, this allows mixins to depend on - // one another without any special ordering - const descriptors = {} - keys(mixins).forEach(name => { - const Mixin = mixins[name] - name = camelCase(name) - - descriptors[name] = { - configurable: true, - get: () => { - defineProperty(this, name, MIXIN_CYCLIC_DESCRIPTOR) - const instance = new Mixin(this, opts) - defineProperty(this, name, { - value: instance, - }) - return instance - }, - } - }) - defineProperties(this, descriptors) - - // access all mixin properties to trigger their creation - keys(descriptors).forEach(name => { - noop(this[name]) - }) + mixin(this, mixins, [opts]) const debounceResource = createDebounceResource() this.config.watchDuration('resourceCacheDelay', delay => { diff --git a/packages/xo-server/package.json b/packages/xo-server/package.json index 89fd7ad21..04aa9f49b 100644 --- a/packages/xo-server/package.json +++ b/packages/xo-server/package.json @@ -46,7 +46,7 @@ "@xen-orchestra/emit-async": "^0.0.0", "@xen-orchestra/fs": "^0.14.0", "@xen-orchestra/log": "^0.2.0", - "@xen-orchestra/mixin": "^0.0.0", + "@xen-orchestra/mixin": "^0.1.0", "@xen-orchestra/self-signed": "^0.1.0", "@xen-orchestra/template": "^0.1.0", "@xen-orchestra/xapi": "^0.6.1", diff --git a/packages/xo-server/src/xapi/index.js b/packages/xo-server/src/xapi/index.js index 4a5fd7255..5b6964a05 100644 --- a/packages/xo-server/src/xapi/index.js +++ b/packages/xo-server/src/xapi/index.js @@ -5,7 +5,7 @@ import concurrency from 'limit-concurrency-decorator' import deferrable from 'golike-defer' import fatfs from 'fatfs' import mapToArray from 'lodash/map' -import mixin from '@xen-orchestra/mixin' +import mixin from '@xen-orchestra/mixin/legacy' import ms from 'ms' import synchronized from 'decorator-synchronized' import tarStream from 'tar-stream' diff --git a/packages/xo-server/src/xo.js b/packages/xo-server/src/xo.js index 508f1ff8b..2271cdb58 100644 --- a/packages/xo-server/src/xo.js +++ b/packages/xo-server/src/xo.js @@ -1,6 +1,6 @@ import XoCollection from 'xo-collection' import XoUniqueIndex from 'xo-collection/unique-index' -import mixin from '@xen-orchestra/mixin' +import mixin from '@xen-orchestra/mixin/legacy' import { createClient as createRedisClient } from 'redis' import { createDebounceResource } from '@vates/disposable/debounceResource' import { createLogger } from '@xen-orchestra/log'