Fields

Fields are synonymous to table columns. To add fields to a model, assign an object to Model.fields:

class User extends Model {}
User.fields = { fieldName: fieldConfig };

You can also use a getter function to return the model's fields, but first add the fields to the model's config to ensure they are linked to the model and that field inheritance is maintained:

class User extends Model {
static get fields {
this.config = { fields: { fieldName: fieldConfig } };
return this.config.fields;
}
}
note

Field names should be unique. The Model.fields setter will throw if a field name is already a Model.prototype property or is already added as a virtual.

Knorm uses field names as column names when running queries. To transform field names to different column names (e.g. snake-casing), use a fieldToColumn mapping function (ref. Knorm options) or specify a different column name per field with the field config.

Field config

Field config can be a string or an object. If it's a string, it indicates the field's type:

class User extends Model {}
User.fields = { firstName: 'string' };

For object configs, these options are supported:

OptionTypeDefaultDescription
typestring (required)noneSee field types.
defaultany / functionnoneA default value to use during insert operations if the field has no value. If configured as a function, it will be called the model instance as a parameter.
columnstringthe field nameThe column name to use for this field during database operations. NOTE: this takes precedence over the value returned by the fieldToColumn mapping function.
primarybooleanfalseWhether or not the field is the primary field. NOTE: a model can only have one primary field, any subsequent primary fields will overwrite the existing one (also in child models).
uniquebooleanfalseWhether or not the field is a unique field. Primary and unique fields are used for instance operations i.e. fetch, update, delete etc
methodsbooleanfalseIf true, this adds fetchByField, updateByField and deleteByField static methods to the model. See generated methods
updatedbooleantrueWhether or not this field should be included in the data to be updated during update operations. Intended for use with unique and primary fields.
referencesFieldnoneA field that this field references (indicating that this field is a foreign field). Used for relations.
castobjectnoneAn object with forSave or forFetch (or both) functions that are called with the field value to cast it to something else before save (insert and update) and after fetch operations.
Validators:
typestringnoneThe field type is also used as a validator.
requiredbooleanfalseValidates that the field's value is neither undefined nor null.
minLengthintegernoneValidates that the field-value's length is at least as long as this value. Supported only for string, text and array (for JSON validation) field types.
maxLengthintegernoneValidates that the field-value's length is not longer than this value. Supported only for string, text and array (for JSON validation) field types.
oneOfarraynoneValidates that the field's value is one of the values in this array. Uses strict equality and case-sensitive matching for strings.
equalsmixednoneValidates that the field's value is equal to this value. Uses strict equality and case-sensitive matching for strings.
regexRegExp / objectnoneValidates that the field's value either matches or does not match a regular expression, or both. See regex validation
validatefunctionnoneValidates the field's value against a custom validation function. See custom validation
shapestring / objectnoneValidates the structure of json (and jsonb) feilds. See JSON validation

Field types

These field types are supported:

TypeDescriptionValidator
textKnex's text schema typetypeof value === 'string'
uuidKnex's uuid schema typevalidator.isUUID
binaryKnex's binary schema typevalue instanceof Buffer
decimalKnex's decimal schema typevalidator.isDecimal
stringKnex's string schema typetypeof value === 'string'
booleanKnex's boolean schema typetypeof value === 'boolean'
integerKnex's integer schema typeNumber.isInteger(value)
dateTimeKnex's dateTime schema typevalue instanceof Date
dateKnex's date schema typevalue instanceof Date
jsonKnex's json schema typeJSON validation
jsonbKnex's jsonb schema typeJSON validation
Custom types:
uuid4Similar to the uuid type but must be a valid V4 UUIDvalidator.isUUID
emailSimilar to the string type but must be a valid email addressvalidator.isEmail
numberAll numbers, including integers and decimal valuestypeof value === 'number'
anyAny valuenone
objectObjects i.e. {}JSON validation
arrayArrays i.e. []JSON validation

Field inheritance

In knorm, a model's fields (and virtuals) are cloned and inherited when the model is inherited. This:

  • allows adding more fields to the child model without modifying the parent's fields
  • allows overwriting fields defined in the parent
  • eliminates the need to re-configure common fields for every model.

To overwrite a field in a child model, add a field with the same name as the field you wish to overwrite.

class User extends Model {}
User.fields = {
id: { type: 'integer', primary: true },
names: { type: 'string' }
};
class Employee extends User {}
Employee.fields = { id: { type: 'uuid', primary: false } };
console.log(User.fields); // => { id, names }
console.log(User.fields.id.type); // 'integer'
console.log(User.primary); // 'id'
console.log(Employee.fields); // { id, names }
console.log(Employee.fields.id.type); // 'uuid'
console.log(Employee.primary); // throws 'Employee: no primary field configured'

Primary and unique fields

Knorm uses primary and unique fields to find a row in the database for fetch, update and delete operations. Every model must have at least one primary field but can have multiple unique fields.

note

Composite/multiple primary fields are not currently supported. If two fields are configured as primary, only the latter will be configured as a primary field.

If a field is configured as primary or unique, you may also want to prevent it from being updated (with the updated option). Note that this options only works for fields that are either primary or unique:

class User extends Model {}
User.fields = {
id: { type: 'integer', primary: true, updated: false },
names: 'string'
};
const user = await new User.insert({ id: 1, names: 'Foo' });
user.names = 'Bar';
await user.update(); // `id` will not be sent in the update query

Generated methods

You can configure static methods for fetching, updating and deleting rows by a field to be added to the model with the methods option:

class User extends Model {}
User.fields = {
id: { type: 'integer', methods: true },
firstName: { type: 'string', methods: true }
};
// User will now have these static methods:
User.fetchById(id, options);
User.updateById(id, data, options);
User.deleteById(id, options);
User.fetchByFirstName(firstName, options);
User.updateByFirstName(firstName, data, options);
User.deleteByFirstName(firstName, options);

options are optional for all the methods

The method names are resolved by upper-casing the first letter of the field name and taking the rest of the field name as is.

info

Since these methods are intended to work with a single row, they will automatically throw a NoRowsFetchedError, NoRowsUpdatedError or NoRowsDeletedError error if the row is not found in the database.

Value casting

You can configure forSave and forFetch cast functions for every field. These are handy for type-casting or some other functionality:

class User extends Model {}
User.fields = {
data: {
type: 'email',
cast: {
forSave(value, model) {
if (value) {
return value.toLowerCase();
}
},
forFetch(value, model) {
if (value) {
return value.replace('@', '[at]');
}
}
}
}
};
info

forSave cast functions are called whenever data is being sent to the database e.g. for insert and update operations.

forFetch cast functions are called whenever data is fetched from the database. Thos could be from a fetch operation or when data is returned after an insert, update or delete operation.

info

forSave cast functions are called after validation. This allows specifying some field-type in the field's configuration while the database stores the values with a different type, if needed.

info

Cast functions are not called if the field's value is undefined, but are called if it's null.