Skip to main content
Version: 2.0.0

Getting Started

ivo for TypeScript lets you define a schema with a fluent field builder, then derive a model with create, update, and delete methods.

Installation

npm i ivo

Defining a schema

A schema is created with new Schema((b) => ...) where b is a FieldBuilder. Fields are declared with b.required(...), b.lax(...), b.constant(...), b.dependent(...), or b.virtual(...), then passed to b.field(...).

Loading playground…

Model methods

The model returned by schema.getModel() exposes async methods:

MethodDescription
createCreates a new instance from a partial input.
updateApplies a partial update to an existing instance.
deleteTriggers all registered onDelete listeners on the provided entity.

Creating an entity

Unknown properties and output-only properties (constant, dependent, timestamps) are ignored automatically.

const { data, error } = await UserModel.create({
email: "john.doe@mail.com",
id: 5, // ignored because 'id' is constant
name: "John Doe", // ignored because it is not on the schema
username: "john_doe",
updatedAt: new Date(), // ignored because it is a timestamp
usernameLastUpdatedAt: new Date(), // ignored because it is dependent
});

if (error) return handleError(error);

console.log(data);
// {
// id: '...',
// createdAt: Date,
// email: 'john.doe@mail.com',
// phoneNumber: null,
// updatedAt: null,
// username: 'john_doe',
// usernameLastUpdatedAt: null
// }

Updating an entity

const user = await usersDb.findByID(id);
if (!user) return handleError({ message: "User not found" });

const { data, error } = await UserModel.update(user, {
usernameLastUpdatedAt: new Date(), // dependent -> ignored
id: 75, // constant -> ignored
age: 34, // not on schema -> ignored
username: "johndoe",
});

if (error) return handleError(error);

console.log(data);
// {
// updatedAt: Date,
// username: 'johndoe',
// usernameLastUpdatedAt: Date
// }

Field categories

Other topics

Schema options

The second argument to new Schema accepts options:

new Schema((b) => ..., {
equalityDepth: 1,
sanitizeError: (payload, ctxOptions) => payload,
onDelete: [listener],
onSuccess: [listener],
postValidate: { fields: ['email', 'phoneNumber'], validator: ... },
ignore: { fields: ['secret'], handler: () => true },
ignoreUpdate: { fields: ['email'], handler: () => true },
required: { fields: ['email', 'phoneNumber'], handler: ... },
timestamps: true,
});
OptionDescription
equalityDepthNesting depth used to compare values during updates (default: 1).
sanitizeErrorTransform the error payload before it is returned.
onDeleteGlobal listener(s) invoked by model.delete.
onSuccessGlobal listener(s) invoked after a successful create/update.
postValidateCross-field validation configuration (fields + validator).
ignoreIgnore input fields when the handler returns true.
ignoreUpdateIgnore update values for the listed fields when the handler returns true.
requiredCross-field required constraint (fields + handler).
timestampsEnable createdAt/updatedAt (boolean or { createdAt?, updatedAt? }).

See Life cycles and Validators for more.

Extending a schema

Use .extend() to create a new schema that inherits fields and options from the parent:

const AdminSchema = userSchema.extend<AdminInput, AdminOutput>(
(b) => b.field(b.required("role").validate(validateRole)),
{ useParentOptions: true },
);

Set useParentOptions: false to drop parent options and start from the provided options only. Fields can be removed with the remove option.