docs: function createField(def, fieldName, fieldProps) {
    if (!lutil.isValidName(fieldName)) throw new InvalidDefinitionError(def.name, 'invalid field name %s', fieldName);
    if (!fieldProps) throw new InvalidDefinitionError(def.name, 'incomplete field %s', fieldName);
    if (!lutil.isPlainObject(fieldProps)) {
      fieldProps = { column: lutil.dbName(fieldName), category: 'entity', type: fieldProps };
    } else {
      fieldProps = Object.assign({}, fieldProps);
      if (typeof fieldProps.deps === 'string') {
        fieldProps.deps = fieldProps.deps.trim().split(/\s*,\s*/);
      } else if (!fieldProps.deps) fieldProps.deps = [];
  
      // determine and check the field's category
      if (fieldProps.virtual) {
        // if(fieldProps.get && typeof fieldProps.get!=='function'){
        //   throw new InvalidDefinitionError(def.name, "get property of virtual field %s should be function", fieldName);
        // }
        // if(fieldProps.set && typeof fieldProps.set!=='function'){
        //   throw new InvalidDefinitionError(def.name, "set property of virtual field %s should be function", fieldName);
        // }
        fieldProps.category = 'virtual';
      } else if (fieldProps.ref) {
        if (!fieldProps.column) {
          throw new InvalidDefinitionError(def.name, 'column property of ref field %s required', fieldName);
        }
        if (!def.refs[fieldProps.ref]) {
          throw new InvalidDefinitionError(def.name, 'ref %s not found', fieldProps.ref);
        }
        fieldProps.category = 'ref';
      } else if (fieldProps.join) {
        if (!fieldProps.column) {
          throw new InvalidDefinitionError(def.name, 'column property of join field %s required', fieldName);
        }
        let join;
        if (fieldProps.join instanceof Model) {
          for (const jj in def.joins) {
            if (def.joins[jj].model === fieldProps.join) {
              join = def.joins[jj];
              fieldProps.join = jj;
              break;
            }
          }
          if (!join) throw new InvalidDefinitionError(def.name, 'join %s not found', fieldProps.join);
        } else {
          join = def.joins[fieldProps.join];
          if (!join) throw new InvalidDefinitionError(def.name, 'join %s not found', fieldProps.join);
        }
  
        fieldProps.deps = lutil.uniqueConcat(fieldProps.deps, join.deps);
        fieldProps.category = 'join';
      } else {
        if (!fieldProps.column) fieldProps.column = lutil.dbName(fieldName);
        fieldProps.category = 'entity';
      }
    }
  
    const TypeClass = getTypeClass(fieldProps.type);
    if (!TypeClass) throw new InvalidDefinitionError(def.name, 'unsupportted type %s of field %s', fieldProps.type, fieldName);
    fieldProps.type = TypeClass;
    return new TypeClass(def.model, fieldName, fieldProps);
  }