• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

rawmodel/framework: Strongly-typed JavaScript object with support for validation ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

rawmodel/framework

开源软件地址:

https://github.com/rawmodel/framework

开源编程语言:

TypeScript 82.4%

开源软件介绍:

Rawmodel Framework

Build Status codecov

Rawmodel is a strongly-typed JavaScript object with support for validation and error handling. It's a lightweight open source framework for the server and browser (using module bundler), written with TypeScript. It's actively maintained, well tested and already used in production environments. The source code is available on GitHub where you can also find our issue tracker.

Introduction

Rawmodel provides a mechanism for creating strongly-typed data objects with built-in logic for unified data validation and error handling. It has a simple and intuitive API and tends to be a powerful, magic-free, minimalistic and unopinionated framework for writing application data layers where you have a complete control. It could be a perfect fit when writing an Express.js action, GraphQL resolver or similar and it's easily extendable.

Installation

Run the command below to install the package.

$ npm install --save @rawmodel/core
$ npm install --save @rawmodel/handlers // OPTIONAL
$ npm install --save @rawmodel/parsers // OPTIONAL
$ npm install --save @rawmodel/schema // OPTIONAL
$ npm install --save @rawmodel/validators // OPTIONAL

This package uses promises thus you need to use Promise polyfill when promises are not supported.

Example

The code below shows a basic usage example.

import { Model, prop } from '@rawmodel/core';

// defining a basic model
class User extends Model {
  @prop()
  public name: string;
}

// usage example
const model = new User({
  'name': 'John Smith',
});
model.name; // => 'John Smith'

Usage

Below we explain some of the most important features that this framework provides. Please check the API section to see a complete list of features.

Defining Props

Model properties are defined using the prop ES6 decorator. The code below is an example of a basic model class with a name property.

import { Model, prop } from '@rawmodel/core';

class User extends Model {
  @prop()
  public name: string;
}

const user = new User();
user.name = 'John Smith';
user.name; // -> "John Smith"

Type Casting

Each property has a built-in system for type casting, thus we can force a value to be automatically converted to a specific type when setting a value.

import { ParserKind } from '@rawmodel/core';
import { stringParser } from '@rawmodel/parsers';

class User extends Model {
  @prop({
    parser: {
      resolver: stringParser(),
    },
  })
  public name: string;
}

Common types are supported by default. A Model also represents a type and you can create your own parsers when needed. Please see the API section for further details.

Nested Models

As mentioned above, a model class is already a type. This way you can create complex nested structures by nesting models as shown in the example below.

import { Model, ParserKind, prop } from '@rawmodel/core';

class Address extends Model {
  @prop()
  public country: string;
}

class Friend extends Model {
  @prop()
  public name: string;
}

class User extends Model {
  @prop({
    parser: {
      resolver: Address,
    },
  })
  public address: Address;
  @prop({
    parser: {
      array: true,
      resolver: Friend,
    },
  })
  public friends: Friend[];
}

Prop Default Value

We can set a defaultValue for each property which will automatically populate a property on creation.

The defaultValue can also be a method which returns a dynamic value. This function shares the context of the associated model.

@prop({
  defaultValue() { return new Date() },
})
public now: string;

Prop Fake Value

Similar to default values, we can set a fakeValue for each property, to populate a property with fake data when calling the fake() method. This is useful when writting automated tests.

The fakeValue can also be a method which returns a dynamic value. This function shares the context of the associated model.

@prop({
  fakeValue() { return new Date() },
})
public today: string;

Prop Empty Value

By default, all defined properties are set to null. Similar to default and fake values we can set an emptyValue option for each property, to automatically replace null values.

The emptyValue can also be a method which returns a dynamic value. This function shares the context of the associated model.

@prop({
  emptyValue() { return '' },
})
public name: string;

Prop Value Transformation

A property can have a custom getter and a custom setter. This function shares the context of the associated model.

@prop({
  getter(value) { return value },
  setter(value) { return value },
})
public name: string;

Value Assignments

Model's properties are like properties of a Javascript Object. We can easily assign a value to a property through its setter method (e.g. model.name = 'value';). Instead of assigning properties one by one, we can use the populate() method to assign values to multiple enumerable properties.

model.populate({
  'name': 'John Smith',
  'age': 35,
});

We can allow only selected properties to be populated by using population strategies (e.g. useful when populating data received from a form).

class User extends Model {
  @prop({
    populatable: ['internal'], // list population strategy names
  })
  public id: string;
  @prop({
    populatable: ['input', 'internal'], // list population strategy names
  })
  public name: string;
}

const data = {
  'id': 100,
  'name': 'John Smith'
};
const user = new User();
user.populate(data); // -> { "id": 100, "name": "John Smith" }
user.populate(data, 'internal'); // -> { "id": 100, "name": "John Smith" }
user.serialize(data, 'input'); // -> { id: null, "name": "John Smith" }

Model properties also support dynamic data assignments. In translation, this means that we can populate a property using a function that shares the context of the associated model and is realized on property assignment.

user.name = () => 'Join';

It's encouraged to use the populate() method for assigning values unless you know how RawModel works in-depth. Adding items to an array through the native push method, directly assigning model instances and similar data manipulation can lead to strange effects.

Serialization & Filtering

Model provides useful methods for object serialization and filtering. All enumerable properties are serializable by default and are thus included in the result object returned by the serialize() method. We can customize the output and include or exclude properties for different situations by using serialization strategies.

class User extends Model {
  @prop({
    serializable: ['output'], // list serialization strategy names
  })
  public id: string;
  @prop({
    serializable: ['input', 'output'], // list serialization strategy names
  })
  public name: string;
}

const user = new User({
  'id': 100,
  'name': 'John Smith',
});
user.serialize(); // -> { "id": 100, "name": "John Smith" }
user.serialize('input'); // -> { "name": "John Smith" }
user.serialize('output'); // -> { "id": 100, "name": "John Smith" }

A model can also be serialized into an array by using the flatten() method. We can thus easily scroll through all model values in a loop. The method also supports strategies thus we can customize the output and include or exclude properties for different situations.

user.flatten(); // [{ path, value, prop }, ...]
user.flatten('input');
user.flatten('output');

Commits & Rollbacks

RawModel tracks changes for all properties and provides a mechanism for committing values and rollbacks.

class User extends Model {
  @prop()
  public name: string;
}

const user = new User();
user.name = 'Mandy Taylor'; // changing property's value
user.isChanged(); // -> true
user.commit(); // set `initialValue` of each property to the value of  `value`
user.isChanged(); // -> false
user.name = 'Tina Fey'; // changing property's value
user.rollback(); // -> reset `value` of each property to its `initialValue` (last committed value)

Note that the commit method will memorize a serialized data and the rollback method will apply it back. Assigning functions or instances to properties is discourages.

Validation

RawModel provides a simple mechanism for validating properties. All validators shares the context of the associated model.

class User extends Model {
  @prop({
    validators: [ // property validation setup
      { // validator recipe
        resolver(v) { return !!v }, // [required] validator function
        code: 422, // [optional] error code
      },
    ],
  })
  public name: string;
}

const user = new User();
user.validate().catch((err) => {
  user.collectErrors(); // -> [{ path: ['name'], code: 422 }]
});

Error Handling

RawModel provides a mechanism for handling property-related errors. The logic is aligned with the validation thus the validation and error handling can easily be managed in a unified way. This is great because we always deal with validation errors and can thus directly send these errors back to a user in a unified format. All handlers shares the context of the associated model.

class User extends Model {
  @prop({
    handlers: [ // property error handling setup
      { // handler recipe
        resolver(e) { return e.message === 'foo' }, // [required] error resolve function
        code: 31000, // [optional] error code
      },
    ],
  })
  public name: string;
}

const error = new Error();
const user = new User();
user.handle(error).then(() => {
  user.collectErrors(); // -> [{ path: ['name'], code: 31000 }]
});

This mechanism is especially handful when saving data to a database. MongoDB database, for example, throws a uniqueness error (E11000) if we try to insert a value that already exists in the database. We can catch that error by using the handle() method and then return a unified validation error message to a user.

Raw Schema

JSON Schema is a pretty popular standard for describing JSON objects. It's sufficient for general use cases, but it's not powerful enough to cover all RawModel features. RawModel provides its own schema syntax which allows for the creation of generic models from a JSON definition.

We use createModelClass method to generate a new generic model class from a JSON definition. A model with a single property name could look something like this:

import { createModelClass } from '@rawmodel/schema';

const schema = { // raw model schema
  props: [ // properties definition
    {
      name: 'email', // property name
    },
  ],
};

const Model = createModelClass(schema); // creates model class

We can define static or dynamic default values. Dynamic values must have a resolver under the defaultValues option. If the property's defaultValue matches the resolver name, then the dynamic resolver is applied, otherwise, the static value of the defaultValue is copied. Similar logic applies to getters, setters, fake and empty values.

const schema = {
  defaultValues: {
    currentDate() { return new Date() },
  },
  props: [
    {
      name: 'email',
      defaultValue: 'Noname',
    },
    {
      name: 'date',
      defaultValue: 'currentDate', // referencing currentDate()
    },
  ],
};

Validators and handlers can be defined in a similar way.

import { stringLengthValidator } from '@rawmodel/validators';

const schema = {
  validators: { // schema validators
    stringLength: stringLengthValidator, // validator resolver
  },
  props: [ // schema properties
    { // property definition
      name: 'title', // property name
      validators: [
        {
          resolver: 'stringLength', // validator resolver name
          code: 30001, // validation error code
          options: { min: 5 }, // validator arguments
        },
      ],
    },
  ],
};

Schema supports basically all RawModel features. Check the API section for all the details.

GraphQL

RawModel can be a perfect framework for writing GraphQL resolvers. An instance of a root model, in our case the App class, can represent GraphQL's rootValue.

import { Model } from '@rawmodel/core';
import { graphql, buildSchema } from 'graphql';

class App extends Model { // root resolver
  public hello() { // `hello` property resolver
    return 'Hello World!';
  }
}

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const root = new App(); // root resolver

graphql(schema, '{ hello }', root).then((response) => {
  console.log(response);
});

API

@rawmodel/core

createModelClass(config)

Create the Model class from a list of property definitions.

Option Type Required Default Description
config.$.name String Yes - Property name.
config.$.prop.setter Function No - Custom setter.
config.$.prop.getter Function No - Custom getter.
config.$.prop.parser Parser No - Data type parser (see supported types).
config.$.prop.defaultValue Any No - Prop default value.
config.$.prop.fakeValue Any No - Prop fake value.
config.$.prop.emptyValue Any No - Prop empty value.
config.$.prop.validators Array No - List of validator recipes.
config.$.prop.handlers Array No - List of error handler recipes.
config.$.prop.populatable String[] No - List of strategies for populating the property value.
config.$.prop.serializable String[] No - List of strategies for serializing the property value.
config.$.prop.enumerable Boolean No true Indicates that the property is enumerable.
const Model = createModelClass([
  {
    name: 'name',
    prop: {
      defaultValue: 'John Smith',
    },
  },
]);

Model(data, config)

Abstract class which represents a strongly-typed JavaScript object.

Option Type Required Default Description
data Any No - Data for populating model properties.
config.context Any No - Arbitrary co

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap