# Hooks

Hooks made it possible to tune how models respond to how queries are built for CRUD operations and conditions for model lifecycles.

# CRUD Operation Hooks

CRUD operation hooks override the operation definition of the client adapter. For example, the Default Adapter interprets the create operation with something that looks like the below

const operation = "createOne" + upperFirst(model.entity);
query = query
  .where("data", {
    type: upperFirst(model.entity) + "CreateInput",
    required: true,
    value: data,
  })
  .mutation()
  .operation(operation)
  .parseWith((response) => response.data[operation]);

This means every create operation of models using this adapter will build and execute a createOne[ModelName] mutation with a required argument named data of type [ModelName]CreateInput.

class Post extends Model {
  static entity = 'Post';
  ...

  static hooks() {
    return {
      create(data: any, query: Query, model: ModelType): Query | void{
        const operation = "create" + upperFirst(model.entity);
        query.where('data', {
          type: upperFirst(model.entity) + "CreateInput",
          required: false,
          value: data,
        })
        .mutation()
        .operation(operation)
        .parseWith((response) => response.data[operation]);
      }

      createMany(data: any, query: Query, model: ModelType): Query | void{
        const operation = "create" + plural(upperFirst(model.entity));

		    // create and return a completely new query
        return model.newQuery()
          .select('*')
          .where("data", {
            type: "[" + upperFirst(model.entity) + "CreateInput]",
            required: false,
            value: data,
          })
          .mutation()
            .operation(operation)
            .parseWith((response) => response.data[operation]);
          }
    };
  }
}

The above slightly changes the behaviour of the create and createMany operations only for the Post model.

Currently all adapter methods are overridable.

Note that hooks completely override adapter methods.

# Lifecycle Hooks

Lifecycle hooks are called before or after a create, update or delete operation. This gives you the opportunity to further modify mutation data or completely abort the operation.

Here is an example of a Location model that has an Address model. Changes might have been made to an instantiated location's address directly.

const location = await Location.with("address").findUnique({ id: 1 });

location.address.is_public = false;
location.$save();

In this case,

location.$isDirty(); // returns false
location.$isDeepDirty(); // return true

Now, to save the location and the changes made to its address, supply an $updating hook to make changes to the update data.

class Location extends Model {
  static entity = 'Location';
  static primaryKey = 'handle'
  ...

  static hooks() {
    return {
      $updating(model: Location, data: Attributes) {
        if (model.address && model.address.$isDirty()) {
          data.address = {
            update: model.address.$getAttributeChanges(),
          }
        }
      },
    }
}

# Available lifecycle hooks

class Location extends Model {
  static entity = 'Location';
  ...

  static hooks() {
    return {
      $creating(model: Model, data: Attributes): void | Attributes | false{
        //
      },

      $created(model: Model): void{
        //
      },

      $updating(model: Model, data: Attributes): void | Attributes | false{
        //
      },

      $updated(model: Model): void{
        //
      },

      $deleting(model: Model): void | false{
        //
      },

      $deleted(model: Model): void{
        //
      },
    }
}

When false is returned at $creating, $updating and $deleting, the operation is aborted.

Note that $creating and $created hooks are not triggered when using createMany.