@commodo/fields
Version:
Enables defining rich data models by decorating function instances with specified model fields. Additionally, it adds `populate` and `validate` methods, for populating model instances with data, and then validating it, respectively.
203 lines (152 loc) • 6.09 kB
Markdown
# @commodo/fields [](https://www.npmjs.com/package/@commodo/fields)
Enables defining rich data models by decorating function instances with specified model fields. Additionally, it adds `populate` and `validate` methods, for populating model instances with data, and then validating it, respectively.
## Usage
```js
import { withFields, string, number, boolean, fields } from "@commodo/fields";
import { compose } from "ramda";
// User function (data model).
const User = compose(
withFields({
// A field which accepts string values.
email: string(),
// Set "list" to true in order to store a list of string values.
previousEmails: string({ list: true }),
// A field which accepts boolean values.
verified: boolean(),
// A field that consists of nested fields. It can accept an instance of Company data model,
// or a plain object, from which a new Company instance will be created upon value assignment.
company: fields({ instanceOf: Company }),
// A field which accepts number values. Additionally, with the passed "validation" callback,
// we are ensuring that the assigned value is greater than or equal to 30.
age: number({
validation: value => {
if (value < 30) {
throw Error("User too young.")
}
}
})
})
)();
// Company function (data model).
const Company = compose(
withFields({
name: string()
})
)();
// Let's create an instance of the User data model, and populate it with some data.
const user = new User();
user.populate({
email: "user3@email.com",
previousEmails: ["user2@email.com", "user1@email.com"],
age: 25,
verified: true,
company: {
name: "Awesome Company"
}
});
// Using the "validate" method, we can check if the assigned values are valid.
// This will throws an error with the "User too young" message.
async user.validate();
```
## Available fields
Out of the box, there are four types of fields you can utilize:
1. `string` - accepts string values
2. `number` - accepts number values
3. `boolean` - accepts boolean values
4. `fields` - accepts a plain object or an instance of another `withFields` function
In the following examples, all types of fields are utilized:
```js
// Company function (data model).
const Company = compose(
withFields({
name: string()
})
)();
const User = compose(
withFields({
email: string(),
age: number(),
verified: boolean(),
company: fields({ instanceOf: Company })
})
)();
```
## Data validation
### Data-type validation
When a value is assigned to a field of a model instance, it is immediately validated on a data-type level, meaning you cannot pass a string value to a field that doesn't accept strings.
Consider the following example:
```js
import { withFields, string, number } from "@commodo/fields";
const User = withFields({
name: string(),
age: number(),
})();
const user = new User();
// Will throw data type error, because we cannot populate the "age" field with a string
// value. Since the field accepts only numbers, the age must be an integer or a float.
user.age = "7";
// The same will happen here.
user.populate({ name: "Rex", age: "7", drools: false });
```
Data-type validation is always executed upon value assignment, synchronously.
### Custom validation
Additionally, you can also add your own custom, business logic related, validation. Unlike the data-type validation, which happens immediately upon assigning the value to a field, the custom validation is triggered by calling the `validate` method. Note that this method validates the whole model instance.
The following snippet shows how we can add your own custom validation and trigger it:
```js
import { withFields, string, number } from "@commodo/fields";
const User = withFields({
name: string({
validate: value => {
if (!value) {
throw new Error("Name is required.");
}
}
}),
age: number({
validate: value => {
if (value && value < 2) {
throw new Error("Your dog is to young.");
}
}
})
})();
const user = new User();
// Will throw an error, since the dog is too young.
user.populate({ name: "Rex", age: 1 });
await user.validate();
// The age is now correct, but now the name is missing.
user.populate({ age: 2 });
await user.validate();
```
Unlike the data-type validation, custom validation can perform asynchronous operations.
## Field options
Each field can accept a few options:
#### `list: boolean`
If set to `true`, field will accept an `null` or an array of values. When setting field value, if a single item in the passed array is of incorrect data type, an error will be thrown.
#### `validation: Function`
A function for validating the assigned value. Not for data-type validation (since it's already done upon assigning a value), but for checking if the value complies with custom logic, for example if the assigned value is greater than 20.
#### `value: any`
## Additional higher order functions
Except options, fields can also be enhanced with a couple of provided higher order functions:
#### `onGet: Function => Function`
#### `onSet: Function => Function`
#### `skipOnPopulate: Function => Function`
#### `setOnce: Function => Function`
## Reference
#### `withFields(fields : { [string] : FieldFactory }): WithFieldsFunction`
Creates a new function, whose instances contain defined fields and are decorated with a couple of useful methods.
### FieldFactory
### `WithFieldsFunction`
Except fields, instances of `WithFieldsFunction` are decorated with a couple of useful methods.
#### `populate(data: Object): void`
Populates fields with given data.
#### `validate(): Promise<void>`
Validates all fields.
#### `getFields(): Array<Field>`
Returns all fields.
#### `getField(name: string): ?Field`
Returns a single field.
#### `clean(): void`
Sets instance as clean.
#### `isDirty(): boolean`
Checks if instance is dirty.