1.6.1 • Published 4 months ago

property-manager v1.6.1

Weekly downloads
2
License
MIT
Repository
github
Last release
4 months ago

property-manager npm

Join the chat at https://gitter.im/snowyu/property-manager.js

Build Status Test Coverage Code Climate downloads license

使得业务对象/类的属性更容易管理.

适用于需要围绕业务对象/类来维护管理数据的场合. 我们首先需要定义该业务对象有哪些数据类型.

能够快速对该业务对象进行赋值,合并,导出(JsonObject)以及校验.

功能:

  • 定义的属性可跟随业务类继承
  • 业务对象/类赋值属性从纯对象(JSON Object).
  • 克隆业务对象.
  • 比较业务对象是否相同(所有属性值相同).
  • 导出属性到纯对象(JSON Object).
  • 定义/声明属性类型及默认值
    • 支持 arrayOf 带类型的数组类型
    • 支持模板属性template(属性值由模板内容确定):
      • template {string | (this) => string}:
        • 字符串(模板), 如, '${author}-${uuid()}'
        • 或模板函数, 如, function() {return this.author + '-' + uuid()}
      • 导入的函数供模板使用 imports: {Object} the optional functions could be used in the template string.
      • 注意: 默认模板属性为只读,不过也可以将它设置为可写. 一旦新值被写入后,模板就不再有用,除非新值是nullundefined

我们经常需要管理一个对象的属性,考虑以下几点:

  • 创建业务对象时将选项设置为对象的属性
    • var myObj = new MyObject({opt1:value1, opt2:value2})
  • 赋值(设置)来自另一个对象的属性
    • myObj.assign({opt1:v1, opt2:v2})
  • 克隆业务对象(属性值完全相同):
    • var newObj = myObj.clone()
  • 通过分配的属性比较两个对象是否相同。
    • myObj.isSame(anotherObj)
  • 将属性导出为普通对象或 JSON,以便将来更轻松地重新创建对象。
    • 有一些内部属性不应该被导出。
    • 不应导出属性的空值或默认值。
    • 应该导出并分配有意义的(非英语)名称。
    • myObj.toObject() and myObj.toJSON()
    • JSON.stringify(myObj)
  1. Problem: 如何为对象属性赋值?
    • replace the standard assignPropertyTo() method.
    • define the attribute's assign(value, dest, src, name) method on the $attributes.
      • the custom attribute's assign the value. return the changed value.
  2. Problem: 如何决定应该为也许对象中的哪些属性赋值或获取属性的默认值?
    1. 在此对象上预先定义所有属性,即使值为 null。
      • 不支持默认值
    2. 定义一个简单的 $attributes 属性来管理属性及默认值:
      • {attrName: {value:'defaultValue'}, ...}
    3. 定义一个复杂的 $attributes(使用 Properties 类)来管理属性

所以我们有了相应的属性管理类:SimplePropertyManagerNormalPropertyManagerAdvancePropertyManager

首先是属性规则:

  • 导出的属性意味着它们是 JSON.stringify(aObj) 属性。
  • 不可枚举的属性不能被导出(export)和赋值(assign)。
  • 无法导出以"$"开头的可枚举属性,但可以被赋值(assign)。
  • undefined 值无法导出。
  • 只读(可写writable为假)属性不能赋值。
  • 属性的赋值顺序就是定义的属性的顺序。

你可以继承它

  • SimplePropertyManager: /lib/simple
    • 直接使用对象的属性描述符。
    • 所以不支持默认值。
    • 不支持对象赋值钩子函数。
    • 不支持有意义的(非英文)名称(别名)。
  • NormalPropertyManager: /lib/normal
    • 使用 $attributes 普通对象来保存声明属性
    • 支持默认值
    • 支持对象赋值钩子函数
    • 支持有意义的(非英文)名称(别名)
  • AdvancePropertyManager: /lib/advance
    • 使用 $attributes 来保存声明属性
    • $attributesProperties 类的实例。
    • 因此您可以自定义继承自的“Properties”类。
    • 支持默认值
    • 支持对象赋值钩子函数
    • 支持有意义的(非英文)名称(别名)
    • 如果可能,支持类型检查

$attributes 包含对象的所有属性(如属性描述符),其中key 是属性名称。 而value值是属性描述符对象.

属性描述符对象:

  • name (String): 要导出的非英文名称,默认为 key 名称。
  • value: 属性的默认值(如果存在)。 默认为 undefined
  • type (String): 属性的类型名称。 默认为 undefined
  • enumerable (Boolean): defaults to true
    • 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
  • configurable (Boolean): defaults to true
    • 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
  • writable (Boolean): defaults to true
    • 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。
  • assign (Function(value, dest, src, name)): defaults to undefined.
    • 自定义属性赋值函数。只需“返回”更改后的值。如果返回 undefined 就不要赋值
    • 注意: 它只用于从另一个对象赋值时。
    • 如果单独对属性赋值,则无效。 请使用属性描述符 set 来做到这一点。
    • 将其封装为智能赋值功能(仅支持 Advance Property Manager ):
      • 自动添加带前缀的隐藏内部属性
      • 自动添加描述符 get 函数以读取属性
      • 自动添加描述符 set 函数来写入属性(调用 assign 描述符)
  • 仅适用于 Normal 和 Advance Property Manager:
    • assigned (Boolean): 是否属性可被赋值. defaults: undefined
      • 如果 undefined 那么是否可被赋值由此决定: enumerable isn't false and (writable isn't false or isFunction(set))
    • 智能赋值支持(Smart Assign Support) 仅适用于 AdvancePropertyManager
      • assigned (Boolean|String): 当“assigned”值为字符串时启用智能赋值支持
      • 如果字符串值为空(""),那么它使用 nonExported1stChar+[name] 作为内部属性名称
      • 当字符串值非空,那么该assigned值作为内部属性名
      • 根据属性的可写性,自动创建对应的get/set描述符.
    • exported (Boolean): 是否该属性可被导出. defaults: undefined
      • 如果 undefined 那么是否可被导出由此决定: enumerable isn't false and the first char isn't "$"
    • alias (String|ArrayOf String): 该属性的别名. 用于从其它纯对象选项中赋值时
    • clone (Boolean): 初始化时如果值为对象,是否克隆默认属性值。defaults to true.
'$attributes': {
  'attrName': {
    name: 'exportedName',
    value: 123,
    enumerable: false,
    type: 'String',
    configurable: true,
    writable: true,
    assign: function(value, dest, src, name)
    get: ...,
    set: ....
  }
}

或者作为能力注入

当作为能力注入时,以下方法将被添加(或替换):

  • initialize(options) : overwrite this for assign options from constructor?
    • apply the initialized value of the properties to the object if possible.
    • then call assign method.
  • assign(options) : assign the options' attributes to this object.
    • how to decide which attribute should be assign?
    • I need an attributes manage class? or just a simple attributes list?
    • or define all attributes even the value is null when initialize
    • this must be an optional feature.
  • assignPropertyTo(dest, options, attributeName, value): assign an attribute to dest.
    • you can override it to determine howto assign an object value.
  • assignProperty(options, attributeName, value): assign an atrribute. called by assign
  • assignTo(dest) : assign the attributes to this dest object.
  • mergeTo(dest): merge the attributes itself to dest object.
    • do not overwrite the already exist attributes of the dest.
  • isSame(obj): compare the obj's attributes whether is the same value with itself.
  • clone(options): create a new object with the same attributes' value.
  • toObject(options): convert it as plain object.
    • do not export the non-enumerable attributes or beginning with '$'
    • do not export the attribute's value is null
    • do not export the attribute's value is default value.
      • where to get the default value?
  • toJSON(): this will call the toObject() to return.

Note: you should specify the position of the argument if the first argument is not the options

Usage

Make your class manage the properties

there are two ways to make your class manage the attributes.

  • Class inherits from
    • inherits from PropertyManager directly.
  • Ability to hook on any class
    • You need confirm these method names are not be used. The $attributes is used via normal and advance PropertyManager. the nonExported1stChar is used to change the first char of non-exported property, defaults to '$'. It must be exist. The first four methods must be exist. others are optional. But be care of their dependencies.
      1. $attributes (unless use simple PropertyManager)
      2. nonExported1stChar
      3. assign
      4. assignPropertyTo
      5. getProperties
      6. defineProperties
      7. clone (optional)
        • mergeTo
      8. initialize (optional)
        • getProperties
        • assign
      9. assignProperty (optional)
        • assignPropertyTo
      10. mergeTo (optional)
        • getProperties
        • assignPropertyTo
      11. exportTo (optional)
        • mergeTo
      12. assignTo (optional)
        • getProperties
        • assignPropertyTo
      13. toObject (optional)
        • exportTo
      14. toJSON (optional)
        • toObject
      15. isSame (optional)
        • mergeTo

Class Inherits

there are three PropertyManager class to use, the default is NormalPropertyManager.

inherits = require 'inherits-ex/lib/inherits'
PropertyManager = require 'property-manager' # the default is normal
PropertyManager = require 'property-manager/lib/normal'
SimplePropertyManager = require 'property-manager/lib/simple'
AdvancePropertyManager = require 'property-manager/lib/advance'

# Only for Normal or Advance PropertyManager
defineProperties = ProperManager.defineProperties

class MyClass
  inherits MyClass, PropertyManager
  # if you use normal or advance property manager
  # you can define your properties here to:
  defineProperties MyClass, {
    'attr1': {value:123}
    'hidden': {value:1, enumerable: false}
    '$dontExport': {value:3, enumerable: true}
    'custom':
      value:{}
      assign:(value, dest, src, name)->
        value?={}
        value.exta = 123
        return value
  }
  constructor: (@name, options)->
    # if you use the SimplePropertyManager
    # you should define your properties here:
    #@defineProperties {
    #  'attr1': {value:123}
    #  'hidden': {value:1, enumerable: false}
    #  '$dontExport': {value:3, enumerable: true}
    #}
    super options

class MyClassEx
  inherits MyClassEx, MyClass
  defineProperties MyClassEx, {'extra': {value: 'extra'}}
  constructor: ->super

the following is javascript:

var inherits = require('inherits-ex/lib/inherits');
var PropertyManager = require('property-manager');
var PropertyManager = require('property-manager/lib/normal');
var SimplePropertyManager = require('property-manager/lib/simple');
var AdvancePropertyManager = require('property-manager/lib/advance');

//# Only for Normal or Advance PropertyManager
var defineProperties = ProperManager.defineProperties

function MyClass(name, options) {
  this.name = name;
  // if you use the SimplePropertyManager
  // you should define your properties here:
  //this.defineProperties({
  //  'attr1': {value:123}
  //  'hidden': {value:1, enumerable: false},
  //  '$dontExport': {value:3, enumerable: true}
  //})
  PropertyManager.call(this, options);
}

inherits(MyClass, PropertyManager);

//only for normal, advance property manager
defineProperties(MyClass, {
    'attr1': {value:123},
    'hidden': {value:1, enumerable: false},
    '$dontExport': {value:3, enumerable: true},
    'custom': {
      value: {},
      assign: function(value, dest, src, name) {
        if (value == null) {
          value = {};
        }
        value.exta = 123;
        return value;
      }
    }
});

function MyClassEx() {
  MyClassEx.__super__.constructor.apply(this, arguments)
}
inherits(MyClassEx, MyClass);
defineProperties(MyClassEx, {'extra': {value: 'extra'}});

Ability to hook on any class

propertyManager = require 'property-manager/ability'

class MyClass
  # add the property manager ability to MyClass
  # the default is normal property manager
  #   propertyManager MyClass
  # you can specified the property manager 'simple', 'advance', 'normal':
  #   propertyManager MyClass, 'simple'
  #   propertyManager MyClass, name: 'simple'
  # and you can specified the options position in the arguments
  propertyManager MyClass, optionsPosition: 1
  # you can exclude some non-core methods
  # propertyManager MyClass, optionsPosition: 1, exclude: ['assignTo', ...]
  # if you use normal or advance property manager
  # you can define your properties here to:
  defineProperties = MyClass.defineProperties
  defineProperties MyClass, {
    'attr1': {value:123}
    'hidden': {value:1, enumerable: false}
    '$dontExport': {value:3, enumerable: true}
    'date':
      assign: (value, dest, src, name, {isExported}) ->
        if isExported
          value.toISOString()
        else if !(value instanceof Date)
          new Date(value)
        else
          value
    'custom':
      value:{}
      assign:(value, dest, src, name, opts)->
        value?={}
        value.exta = 123
        return value
  }
  constructor: (@name, options)->
    # if you use the SimplePropertyManager
    # you should define your properties here:
    #@defineProperties {
    #  'attr1': {value:123, enumerable: true}
    # 'hidden': {value:1, enumerable: false}
    # '$dontExport': {value:3, enumerable: false}
    #}
    @initialize.apply @, arguments

class MyClassEx
  inherits MyClassEx, MyClass
  defineProperties MyClassEx, {'extra': {value: 'extra'}}
  constructor: ->super

the following is javascript:

var propertyManager = require('property-manager/ability');

function MyClass(name, options) {
  // if you use the SimplePropertyManager
  // you should define your properties here:
  //this.defineProperties({
  //  'attr1': {value:123}
  //  'hidden': {value:1, enumerable: false},
  //  '$dontExport': {value:3, enumerable: false}
  //})
  this.name = name;
  this.initialize.apply(this, arguments);
}

// you can exclude some non-core methods:
//propertyManager(MyClass, {optionsPosition:1, exclude: ['assignTo', ...]})
propertyManager(MyClass, {optionsPosition: 1});
var defineProperties = MyClass.defineProperties;

//only for normal, advance property manager
defineProperties(MyClass, {
  'attr1': {value: 123},
  'hidden': {value: 1, enumerable: false},
  '$dontExport': {value: 3, enumerable: true},
  'date': {
    assign(value, dest, src, name, {isExported}) {
      let result;
      if (isExported) {
        result = value.toISOString()
      } else if (!(value instanceof Date)) {
        result = new Date(value)
      }
      return result;
    }
  }
  'custom': {
    value: {},
    assign: function(value, dest, src, name, opts) {
      if (value == null) {
        value = {};
      }
      value.exta = 123;
      return value;
    }
  }
});

function MyClassEx() {
  MyClassEx.__super__.constructor.apply(this, arguments)
}
inherits(MyClassEx, MyClass);
defineProperties(MyClassEx, {'extra': {value: 'extra'}});

Use the Property Manager

Now the MyClass class should have four attributes

  • attr1: can be exported and assigned
  • hidden: can not be exported and assigned
  • $dontExport: can be assigned, can not be exported.
  • custom: can be exported and assigned, the value be changed by assign function in the property descriptor.

the MyClassEx inherits from MyClass (NOTE: only for normal or advance property manager)

  • extra: can be exported and assigned.
  • others inherit from MyClass
assert = require 'assert'
my = new MyClass 'aName', attr1: 3, hidden:11222, $dontExport: 1, custom:{b:12}
assert.deepEqual my.mergeTo(), attr1:3, $dontExport:1, custom:{b:12, exta: 123}
assert.equal my.hidden, 1 # the `hidden` can not be assigned and exported
assert.deepEqual my.toObject(), attr1:3 # the `$dontExport` can not be exported
assert.equal JSON.stringify(my), '{"attr1":3,"custom":{"b":12,"exta":123}}'

obj = my.clone()
assert.ok obj.isSame(my) # compare each assigned properties.
assert.deepEqual obj.mergeTo(), attr1:3, $dontExport:1, custom:{b:12, exta: 123}

myEx = new MyClassEx 'theClassEx', attr1: 3, hidden:11222, $dontExport: 1, custom:{b:12}
assert.deepEqual myEx.mergeTo(), extra:'extra', attr1:3, $dontExport:1, custom:{b:12, exta: 123}

the following is javascript:

var assert = require('assert');

var my = new MyClass('aName', {
  attr1: 3,
  hidden: 11222,
  $dontExport: 1,
  custom: {
    b: 12
  }
});

assert.deepEqual(my.mergeTo(), {
  attr1: 3,
  $dontExport: 1,
  custom: {
    b: 12,
    exta: 123
  }
});

assert.equal(my.hidden, 1);

assert.deepEqual(my.toObject(), {
  attr1: 3
});

assert.equal(JSON.stringify(my), '{"attr1":3,"custom":{"b":12,"exta":123}}');

var obj = my.clone();

assert.ok(obj.isSame(my));

assert.deepEqual(obj.mergeTo(), {
  attr1: 3,
  $dontExport: 1,
  custom: {
    b: 12,
    exta: 123
  }
});

API

Changes

More recent changes see: CHANGELOG.md

v1.4.0

  • feat: add the readonly to smart assigned property
@Properties
class Phone extends AdvancePropertyManager {
  @Prop({
    writable: false,
    exported: true,
    assigned: '',
  }) id!: string;
}
  • BROKEN CHANGE: DO NOT EXPORT the readonly property by default unless exported is true.
@Properties
class Phone extends AdvancePropertyManager {
  @Prop({
    writable: false,
    exported: true,
  }) id!: string;
}

v1.0.0

  • add the array with type supports.
import { arrayOf } from 'property-manager/lib/array';
import AdvancePropertyManager from 'property-manager/lib/advance';
import { PropertyManager as Properties, Property as Prop } from 'property-manager-decorator';

@Properties
class Phone extends AdvancePropertyManager {
  @Prop() value!: string;
  @Prop() codeNum!: string;
  @Prop() kind!: string;
  constructor(initValue?) {
    super(initValue);
  }
}

@Properties
class Contact extends AdvancePropertyManager {
  @Prop({type: String}) name!: string;
  @Prop({type: arrayOf(Phone)}) phones!: Phone[];
  constructor(initValue?) {
    super(initValue);
  }
}
  • BROKEN change toObject method params to (options?: IMergeOptions)
  • BROKEN change assign method params to (src, options?: IMergeOptions)
  • BROKEN change assignTo method params to (dest, options?: IMergeOptions)
  • BROKEN change assignPropertyTo and assignProperty method params to (dest, src, name: string, value, attrs?, options?: IMergeOptions)
  • BROKEN change exportTo method params to (dest, options?: IExportOptions)
  • BROKEN change mergeTo method params to (dest, options?: IMergeOptions)
    • add skipNull and skipUndefined option to IExportOptions and IMergeOptions
  • add the extends(attrs: Object, nonExported1stChar) method to the Properties
    • return a new Properties instance to extends properties from current instance.
  • add the inherited properties supports for AdvancePropertyManager.defineProperties
  • change the recreate argument default value of defineProperties to false for AdvancePropertyManager and NormalPropertyManager
  • set all methods and non-properties of Properties to be non-enumerable.

v0.13.0

  • add typed property for AdvancePropertyManager and NormalPropertyManager
function CustomType(value) {
  if (!(this instanceof CustomType)) return new CustomType(value)
  try {
    value = JSON.parse(value)
  } catch(err) {
    this.value = value
  }
}

const attrs = {
  prop1: {type: CustomType, value: 111}
}

class TypedPM extends AdvancePropertyManager {
  constructor(opts) {
    super(opts)
  }
}
TypedPM.defineProperties(attrs)

const obj = new TypedPM()
console.log(obj.prop1 instanceof CustomType)

v0.11.0

  • add the skipExists option to the Properties.assignTo and Properties.assignPropertyTo
  • the options to the Properties.assignTo(dest, src, options)
    • exclude(String|Array)
    • skipDefault(Boolean)
    • skipExists(Boolean)
    • skipReadOnly(Boolean)
    • exported(Boolean)

v0.10.0

  • add the alias property descriptor(Normal&Advance):
    • You can define one or more aliases to assign from other object(options)
    • alias (String|ArrayOf String)
  • Smart assignment property supports(AdvancePropertyManager):
    • broken: SMART_ASSIGN constant deprecated.
    • assigned descriptor (Boolean|String):
      • String means SMART_ASSIGN.
      • it's the internal property name of the smart assignment if it's string
      • the internal property name is the property name with prefix(nonExported1stChar) if it's an empty string
  • broken: remove attrsName property(fixed to '$attributes')
  • add the helper function: properties/define-properties.

v0.9.0

  • clone default property value if the value is an object when initializing
    • the object instances will share the same one of property value if the default value of property is an object.
      • howto create a new object instance when initializing default value.
      • Solution 1: the value descriptor could be a function to create new object instance:
        • Problem1: it will be only available for normal and advance property manager
          • value: function (){return Object.create()}
        • Problem2: the value can not be a function now.
      • Solution 2: check the value whether is object. if so, clone it when initializing.
        • use this solution. but if someone wish all instance share the same value.
        • add a descriptor to control whethe enable this. but simple can not support the custom descriptor.
          • clone (Boolean): defaults to true.
  • Smart assignment property supports:
    • assign property descriptor (Function(value, dest, src, name)):
      • It only used to assign the options from another object.
      • It's no effect if the assign the property individually. should use the property descriptor set to do so.
      • maybe I should wrap it:
        • add a hidden internal property with prefix(nonExported1stChar)
        • add descriptor get function to read the property
        • add descriptor set function to assign the property(call the assign descriptor).
      • need a descriptor to control whethe enable this.
        • assigned: AdvancePropertyManager::SMART_ASSIGN = 2
        • enabled: !get and !set and assigned is AdvancePropertyManager::SMART_ASSIGN
      • only available for advance property manager.
      • note: only value argument is passed into assign descriptor when assignment the property individually.

v0.8.0

  • add the property writable check: do not assign the readonly property.
  • Normal, Advance
    • add the assigned, exported (Boolean) to property descriptor directly.
      • assigned: enumerable isnt false and (writable isnt false or isFunction(set)).
      • exported: enumerable isnt false and the first char isnt "$"
  • PropertyManager::nonExported1stChar (Char), defaults to '$'
    • note: the exported descriptor is higher prior than nonExported1stChar.
  • nonExported1stChar option to the property manager ability.

v0.7.0

  • broken the arguments order of assign function in property descriptor are changed:
    • attr.assign(value, dest, src, name, opts) instead of assign(dest, src, value, name)

License

MIT

2.0.0-alpha.5

4 months ago

2.0.0-alpha.4

11 months ago

2.0.0-alpha.3

1 year ago

2.0.0-alpha.0

1 year ago

2.0.0-alpha.1

1 year ago

2.0.0-alpha.2

1 year ago

1.6.1

3 years ago

1.6.0

3 years ago

1.5.3

3 years ago

1.5.2

3 years ago

1.5.1

3 years ago

1.5.0

3 years ago

1.4.1

4 years ago

1.4.0

4 years ago

1.3.1

4 years ago

1.3.0

4 years ago

1.2.0

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago

0.12.7

5 years ago

0.12.6

5 years ago

0.12.5

5 years ago

0.12.4

5 years ago

0.12.3

5 years ago

0.12.2

5 years ago

0.12.1

6 years ago

0.12.0

6 years ago

0.10.14

7 years ago

0.10.13

7 years ago

0.10.12

8 years ago

0.10.11

9 years ago

0.10.10

9 years ago

0.10.9

9 years ago

0.10.8

9 years ago

0.10.7

9 years ago

0.10.6

9 years ago

0.10.5

9 years ago

0.10.4

9 years ago

0.10.3

9 years ago

0.10.2

9 years ago

0.10.1

9 years ago

0.10.0

9 years ago

0.9.10

9 years ago

0.9.9

9 years ago

0.9.8

9 years ago

0.9.7

9 years ago

0.9.6

9 years ago

0.9.5

9 years ago

0.9.4

9 years ago

0.9.2

9 years ago

0.9.1

9 years ago

0.9.0

9 years ago

0.8.2

9 years ago

0.8.1

9 years ago

0.8.0

9 years ago

0.7.0

9 years ago

0.6.1

9 years ago

0.6.0

9 years ago

0.1.0

9 years ago

0.0.4

9 years ago

0.0.3

9 years ago

0.0.2

9 years ago

0.0.1

9 years ago

0.0.0

9 years ago