feat(@xen-orchestra/mixin): 0.1.0
This commit is contained in:
@@ -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
|
||||
|
||||
120
@xen-orchestra/mixin/legacy.js
Normal file
120
@xen-orchestra/mixin/legacy.js
Normal file
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user