skip to main content

Writing Data

Write services allow you to create or update object graphs from a certain class.

The GraphQL Livebase API allows you to write objects to the database using mutation type operations. All CRUD scenarios are supported:

In addition to the actual writing services, which as already stated are of type mutation (see conventions on service nomenclature), there are two types of services, of type query, which cover two “cross-cutting” scenarios, both of which support writing:

  • Validate: services that allow simulating a write by checking its correctness at the Data Validation level, requesting any Issue of this type, but without raising server-side errors. More information is available at the bottom: type ValidationResult.
  • Preview: services that allow you to get a preview of the write result, which is useful for getting information computed by the server, such as derived attributes and associable objects.

Write services return as output the type <ClassName> and type <ClassName>Page structures, described for read services. Unlike the latter, the input structures used in writes are multiple and highly specialized. For this reason, in the following paragraphs, we have grouped input, writing services, and support services according to the scenario they are part of, among those listed above.

Reference engine model #

All examples on this page reference the engine model in the figure:

Employee

The Employee class, center, as modeled in the Tutorial.

The Employee class has a role for each supported relationship type, as well as native, query, math, and platform attributes declared on it.

Create new objects #

The following services and facilities allow you to create new objects or test their creation.

create service #

The Mutation.<ClassName>___create service allows you to create a graph of objects from a certain class.

type Mutation {
  ClassName___create(data: ClassNameCreate!, forceWarnings: ForceWarnings): ClassName
}

The service requests the structure <ClassName>Create as input, and returns the structure <ClassName> containing the newly created object as output. You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

For example, the service to create an Employee object is as follows:

type Mutation {
  Employee___create(data: EmployeeCreate!, forceWarnings: ForceWarnings): Employee
}

previewCreate service #

The Query.<ClassName>___previewCreate service allows you to get a preview of the result of creating a graph of objects, which is useful for getting information computed by the server, such as derived attributes and objects that can be associated with the graph.

type Query {
  ClassName___previewCreate(data: ClassNameDraftCreate!): ClassName
}

The service requests as input the structure <ClassName>DraftCreate, and returns as output the structure <ClassName>, whose computed fields are pre-populated, if possible, from the information sent to the server; associables are always available for all roles.

For example, the service for getting a preview of an Employee object is as follows:

type Query {
  Employee___previewCreate(data: EmployeeDraftCreate!): Employee
}

validateCreate service #

The Query.<ClassName>___validateCreate service mirrors the create service, but with the purpose of checking whether the graph of objects created is correct at the Data Validation level.

type Query {
  ClassName___validateCreate(data: ClassNameDraftCreate!): ValidationResult
}

The service requests the structure <ClassName>DraftCreate (the same as the service previewCreate) as input, and returns the structure ValidationResult as output, which indicates whether the graph is valid or not and reports any Issues at the Data Validation level.

For example, the service to validate the creation of an Employee object is as follows:

type Query {
  Employee___validateCreate(data: EmployeeDraftCreate!): ValidationResult
}

input <ClassName>Create. #

The input type <ClassName>Create allows you to specify the data needed to create an object of class <ClassName>. This input follows the structure type <ClassName>, with some important differences.

Attributes are mapped as follows:

  • only fields related to native (editable) attributes are present, while derived attributes and platform are excluded;
  • fields related to required attributes are mandatory (!).

For example, for the Employee class, we have the following EmployeeCreate structure:

input EmployeeCreate {
# attributes
first_name: String! # is required
last_name: String! # is required
date_of_birth: Date! # is required
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# outgoing roles
# ...
}

The outgoing roles are mapped as follows:

  • to-one associations: ID;
  • to-many associations: [ID];
  • part to-one: <PartClassName>Create (analogous to <ClassName>Create);
  • part to-many: [<PartClassName>Create] (analogous to [<ClassName>Create]).

IDs specified for associations must refer to pre-existing instances for the pointed classes. On the other hand, part roles allow the contextual creation of a part object through its input (this is why we speak of the creation of an object graph).

For Employee, outgoing roles are mapped as follows:

input EmployeeCreate {
  # attributes
  # ...

  # outgoing roles
  team: ID # association to 1
  supervisor: ID # reflexive association to 1
  address: AddressCreate # composition to 1
  qualification_: [ID] # association to N
  assignments: [Project_assignmentCreate] # composition to N
}

input <ClassName>DraftCreate. #

The input type <ClassName>Create allows you to specify some data required to create an object of class <ClassName>; it is the permissive counterpart of <ClassName>Create, where no fields are required (!). This allows more freedom to the services that use it (previewCreate and validateCreate).

For example, for the Employee class, you have the following EmployeeDraftCreate structure:

input EmployeeCreate {
# attributes
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# outgoing roles
team: ID # association to 1
supervisor: ID # reflexive association to 1
address: AddressCreate # composition to 1
qualification_: [ID] # association to N
assignments: [Project_assignmentCreate] # composition to N
}

Update existing objects #

The following services and facilities allow you to update pre-existing objects or test their modification.

update service #

The Mutation.<ClassName>___update service allows you to update one or more objects from a certain class via a modification graph.

type Mutation {
  ClassName___update(data: ClassNameUpdate!, forceWarnings: ForceWarnings): ClassName
}

The service requests as input the structure <ClassName>Update, and returns as output the structure <ClassName> containing the newly updated object. You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

For example, the service to update an Employee object is as follows:

type Mutation {
  Employee___update(data: EmployeeUpdate!, forceWarnings: ForceWarnings): Employee
}

An example mutation with this service is available here.

previewUpdate service #

The Query.<ClassName>___previewUpdate service allows you to get a preview of the result of updating an graph of objects, which is useful for getting information computed by the server, such as derived attributes and objects that can be associated with the graph.

type Query {
  ClassName___previewUpdate(data: ClassNameDraftUpdate!): ClassName
}

The service requests the structure <ClassName>DraftUpdate as input, and returns the structure <ClassName> as output, whose computed fields are pre-populated, if possible, from the information sent to the server; associables are always available for all roles.

For example, the service for getting a preview of an updated Employee object is as follows:

type Query {
  Employee___previewUpdate(data: EmployeeDraftUpdate!): Employee
}

validateUpdate service #

The Query.<ClassName>___validateUpdate service mirrors the update service, but with the purpose of checking whether the graph of changes is correct at the Data Validation level.

type Query {
  ClassName___validateUpdate(data: ClassNameDraftUpdate!): ValidationResult
}

The service requests as input the structure <ClassName>DraftUpdate (the same as the service previewUpdate and returns as output the structure ValidationResult, which indicates whether the graph is valid or not and reports any Issues at the Data Validation level.

For example, the service to validate the update of a pre-existing Employee object is as follows:

type Query {
  Employee___validateUpdate(data: EmployeeDraftUpdate!): ValidationResult
}

input <ClassName>Update. #

The input type <ClassName>Update allows you to specify the data required to modify a pre-existing object of class <ClassName>. This input follows the type <ClassName> structure, with some important differences.

Attributes are mapped as follows:

  • only fields related to native (editable) attributes are present, while derived attributes and platform are excluded, except for the _id: ID! field;
  • unlike <ClassName>Create, fields related to required attributes are not mandatory;
  • the only mandatory field is _id, necessary to specify which class instance you want to modify.

For example, for the class Employee, you have the following structure EmployeeUpdate:

input EmployeeUpdate {
_id: ID!

# attributes
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# outgoing roles
# ...
}

The outgoing roles are mapped as follows:

For Employee, the outgoing roles are mapped as follows:

input EmployeeUpdate {
  # attributes
  # ...

  # outgoing roles
  team: TeamRoleRef # association to 1
  supervisor: EmployeeRoleRef # reflexive association to 1
  address: AddressRoleObject # composition to 1
  qualification_: QualificationRoleRefs # association to N
  assignments: Project_assignmentRoleObjects # composition to N
}

input <ClassName>RoleRef(s) #

The input type <ClassName>RoleRef and <ClassName>RoleRefs are intended to handle the modification of the association towards the class <ClassName> in the context of the update service. They are generated for all classes that have at least one incoming managed association_; as the name suggests, <ClassName>RoleRef is generated for to-one associations, while <ClassName>RoleRefs is generated for to-many associations.

input ClassNameRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

Contextually with the modification of an object of the Source class, it is possible to specify, for fields related to outgoing association roles, which objects of the corresponding Target classes you want to associate or remove:

  • <ClassName>RoleRef allows you to associate with an existing object (by specifying its ID using set), or to remove the pre-existing association (by setting the value remove: true);
  • <ClassName>RoleRefs allows you to specify new associations (specifying a list of IDs using add) and the removal of pre-existing associations (specifying a list of IDs using remove) at the same time, or allows you to remove all pre-existing associations (setting removeAll: true).

As shown above, for the Team and Qualification classes, the TeamRoleRef and QualificationRoleRefs inputs are generated, respectively; these appear in the EmployeeUpdate input, in the team (to one), and qualification_ (to many) role fields, respectively, as part of the Mutation.Employee___update service.

Similarly, looking at the reference engine model, we can infer that the inputs ProjectRoleRef and EmployeeRoleRef (also the latter used by EmployeeUpdate) appear in the generated GraphQL schema.

input <ClassName>RoleObject(s) #

The input type <ClassName>RoleObject and <ClassName>RoleObjects are intended to handle the creation/modification of Part objects of class <ClassName>, in the context of the update service. They are generated for all classes that have at least one incoming Part role managed; as the name suggests, <ClassName>RoleObject is generated for one compositions, while <ClassName>RoleRefs is generated for many compositions.

input ClassNameRoleObject {
  create: ClassNameCreate
  update: ClassNameUpdate
  delete: Boolean
}

input ClassNameRoleObjects {
  create: [ClassNameCreate].
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

While editing an object of class Main, it is possible to specify, for fields related to outgoing composition roles, which objects of the corresponding Part classes you want to create, modify or destroy:

  • <ClassName>RoleObject allows you to create a new part object (by populating the create field, of type <ClassName>Create), update the pre-existing part object (by populating the update field, of type <ClassName>Update), or delete the pre-existing part object (by valorizing delete: true) ;
  • <ClassName>RoleObjects allows you to simultaneously specify the contextual creation of new part objects (by specifying a list of <ClassName>Create via add), the contextual modification of existing part objects (specifying a list of <ClassName>Update via update), the contextual deletion of pre-existing part objects (specifying a list of IDs via delete), or allows the deletion of all pre-existing part objects (by setting deleteAll: true).

As shown above, for the Address and Project_assignment classes, the AddressRoleObject and Project_assignmentRoleObjects inputs are generated, respectively; these appear in the EmployeeUpdate input, in the address (to one) and assignments (to many) role fields, respectively, as part of the Mutation.Employee___update service.

input <ClassName>DraftUpdate #

The input type <ClassName>DraftUpdate is the variant of <ClassName>Update used by the services previewUpdate and validateUpdate. It has no substantial differences with its counterpart, except for the structures used as the value of fields related to exiting part roles.

For example, for the Employee class, you have the following structure EmployeeDraftUpdate:

input EmployeeDraftUpdate {
  _id: ID! # same as EmployeeUpdate
  # attributes (same as EmployeeUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # outgoing roles
  #...
}

The outgoing roles are instead mapped as follows:

For Employee, the outgoing roles are mapped as follows:

input EmployeeDraftUpdate {
  # attributes (same as EmployeeUpdate).
  # ...

  # outgoing roles
  team: TeamDraftUpdateRoleRef # association to 1
  supervisor: EmployeeDraftUpdateRoleRef # reflexive 1 association
  address: AddressDraftUpdateRoleObject # 1-way composition
  qualification_: QualificationDraftUpdateRoleRefs # association to N
  assignments: Project_assignmentDraftUpdateRoleObjects # composition in N
}

input <ClassName>DraftUpdateRoleRef(s) #

The input type <ClassName>DraftUpdateRoleRef and <ClassName>DraftUpdateRoleRefs are the variants of <ClassName>RoleRef and [<ClassName>RoleRefs](#input- classnamerolerefs) nested to the <ClassName>DraftUpdate input used by the services previewUpdate and validateUpdate. Net of the different name, these structures coincide with their counterparts, as we can see from the following comparison:

input ClassNameDraftUpdateRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameDraftUpdateRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}
input ClassNameRoleRef {
  set: ID
  remove: Boolean
}

input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

input <ClassName>DraftUpdateRoleObject(s). #

The input type <ClassName>DraftUpdateRoleObject and <ClassName>DraftUpdateRoleObjects are the variants of <ClassName>RoleObject and [<ClassName>RoleObjects](#input- classnameroleobjects) nested to the <ClassName>DraftUpdate input used by services previewUpdate and validateUpdate.

Compared to the association role structures, these also differ semantically from their counterparts. Since part object inputs allow contextual creation, DraftUpdate and Update inputs use <ClassName>DraftCreate (which has no required fields) <ClassName>Create (which can have required fields), respectively.

Below we see the comparison with <ClassName>RoleObject/<ClassName>RoleObjects:

input ClassNameDraftUpdateRoleObject {
create: ClassNameDraftCreate
update: ClassNameDraftUpdateUpdate
delete: Boolean
}

input ClassNameDraftUpdateRoleObjects {
create: [ClassNameDraftCreate]
update: [ClassNameDraftUpdateUpdate]
delete: [ID]
deleteAll: Boolean
}
input ClassNameRoleObject {
  create: ClassNameCreate
  update: ClassNameUpdate
  delete: Boolean
}

input ClassNameRoleObjects {
  create: [ClassNameCreate]
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

This difference in the nested structures for the parts actually justifies having a dedicated DraftUpdate structure for the previewUpdate and validateUpdate services to provide the same freedom as the previewCreate and validateCreate services.

updateBulk service #

The Mutation.<ClassName>___updateBulk service, the many counterpart of the [update](#update service) service, allows one or more objects to be updated from a certain class via a modification graph that is applied in a bulk fashion to each selected object.

type Mutation {
  ClassName___updateBulk(
    options: ClassNamePageOptions!
    data: ClassNameUpdateBulk!
    forceWarnings: ForceWarnings
  ): ClassNameBulkResult
}

The service requires the structure <ClassName>PageOptions as input, properly set to identify the objects you want to modify. Also required is an input <ClassName>UpdateBulk, which describes the bulk changes that will be applied to each selected object.

You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

The service returns a <ClassName>BulkResult object as output, which contains, in the items field, the list of objects modified by the mutation.

type ClassNameBulkResult {
  items: [ClassName!]!
  totalCount: Int!
}

You can specify which fields you want to retrieve for each record in the list, similar to the <ClassName>Page structure. The totalCount field shows the count of records

For example, the service for modifying several Employee objects is as follows:

type Mutation {
  Employee___updateBulk(
    options: EmployeePageOptions!
    data: EmployeeUpdateBulk!
    forceWarnings: ForceWarnings
  ): EmployeeBulkResult
}

validateUpdateBulk service #

The Query.<ClassName>___validateUpdateBulk service mirrors the updateBulk service, but with the purpose of checking whether bulk modification of objects does not violate constraints at the Data Validation level.

type Query {
  ClassName___validateUpdateBulk(options: ClassNamePageOptions!): ValidationResult
}

The service requests as input the structure <ClassName>DraftUpdateBulk and returns as output the structure ValidationResult, which indicates whether the bulk change is valid or not and reports any Issue at the Data Validation level.

For example, the service to validate the bulk modification of several Employee objects is as follows:

type Query {
  Employee___validateUpdateBulk(options: ClassNamePageOptions!): ValidationResult
}

input <ClassName>UpdateBulk. #

The input type <ClassName>UpdateBulk allows you to specify the data required for the bulk modification of pre-existing objects of class <ClassName>. This input traces the input <ClassName>Update, with one difference: the _id field is absent.

In fact, the change graph described by this structure does not refer to a pre-existing object of the class but rather represents the changes that will be applied in bulk fashion to all objects selected by the service updateBulk.

For example, for the Employee class, you have the following structure EmployeeUpdateBulk:

input EmployeeUpdateBulk {
  # _id is absent

  # attributes (same as EmployeeUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # outgoing roles (same as EmployeeUpdate)
  team: TeamRoleRef # association to 1
  supervisor: EmployeeRoleRef # reflexive association to 1
  address: AddressRoleObject # composition to 1
  qualification_: QualificationRoleRefs # association to N
  assignments: Project_assignmentRoleObjects # composition in N
}

input <ClassName>DraftUpdateBulk. #

The input type <ClassName>DraftUpdateBulk is the variant of <ClassName>UpdateBulk used by the validateUpdateBulk service. It does not have substantial differences with its counterpart, except for the structures used to value the fields related to outgoing part roles. In fact, the same considerations made for the input <ClassName>DraftUpdate apply.

For example, for class Employee, you have the following structure EmployeeDraftUpdateBulk:

input EmployeeDraftUpdateBulk {
  # _id is absent (same as EmployeeUpdateBulk)

  # attributes (same as EmployeeDraftUpdate)
  first_name: String
  last_name: String
  date_of_birth: Date
  email_address: String
  date_joined: Date
  hourly_cost: Real
  username: String

  # outgoing roles (same as EmployeeDraftUpdate)
  team: TeamDraftUpdateRoleRef # association to 1
  supervisor: EmployeeDraftUpdateRoleRef # reflexive association to 1
  address: AddressDraftUpdateRoleObject # 1-way composition
  qualification_: QualificationDraftUpdateRoleRefs # association to N
  assignments: Project_assignmentDraftUpdateRoleObjects # composition in N
}

Make a generic write #

As seen so far, the services and facilities offered by the schema to cover the Create and Update scenarios are essentially very similar to each other; this approach is in line with the predictability principle of the GraphQL schema.

When designing applications that use the GraphQL API intensively (such as a web client), however, it may not be necessary to distinguish between a Create and an Update write; in such cases, the rigid typing structures seen so far may be a limitation, since the developer is forced to handle these two scenarios separately.

For this reason, starting with Livebase version 5.8, the schema offers generic write services (and their supporting Preview and Validate services) that bring these differences together, which use flexible inputs that can take on the meaning of a Create or Update depending on the fields being valued.

save service #

The Mutation.<ClassName>___create service allows you to create a graph of objects of a certain class, or to update one or more objects of the same class, via a graph of changes. It groups the create and update services.

type Mutation {
  ClassName___save(data: ClassNameDraft!, forceWarnings: ForceWarnings): ClassName
}

The service requests the structure <ClassName>Draft as input, and returns the structure <ClassName> containing the modified object as output. You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

For example, the service to update an Employee object is as follows:

type Mutation {
  Employee___save(data: EmployeeDraft!, forceWarnings: ForceWarnings): Employee
}

preview service #

The Query.<ClassName>___previewCreate service allows you to get a preview of the result of creating or updating an graph of objects, useful for getting server-computed information such as derived attributes and objects that can be associated with the graph; It groups the previewCreate and previewUpdate services.

type Query {
  ClassName___preview(data: ClassNameDraft!): ClassName
}

The service requests as input the structure <ClassName>Draft, and returns as output the structure <ClassName>, whose computed fields are pre-populated, if possible, from the information sent to the server; associables are always available for all roles.

For example, the service for getting a preview of a created or updated Employee object is as follows:

type Query {
  Employee___preview(data: EmployeeDraft!): Employee
}

validate service #

The Query.<ClassName>___validate service mirrors the save service, but to verify whether the graph of objects created or modified is correct at the Data Validation level. It groups the validateCreate and validateUpdate services.

type Query {
  ClassName___validate(data: ClassNameDraft!): ValidationResult
}

The service requests the structure <ClassName>Draft as input, and returns the structure ValidationResult as output, which indicates whether the graph is valid or not and reports any Issues at the Data Validation level.

For example, the service to validate the creation or update of a pre-existing Employee object is as follows:

type Query {
  Employee___validate(data: EmployeeDraft!): ValidationResult
}

input <ClassName>Draft. #

The input type <ClassName>Draft is the most flexible of the available input structures, and is used indiscriminately by the save, preview and validate services.

As stated, here, this flexible input is able to take on the meaning of a Create or Update depending on the valued fields.

Attributes are mapped as follows:

  • only fields related to native (editable) attributes are present, while derived attributes and platform are excluded, except for the _id: ID! field;
  • unlike <ClassName>Create, fields related to required attributes are not required;
  • unlike <ClassName>Update, the _id field is also not required. The presence or absence of a value for _id distinguishes whether the operation is logically a Create or an Update;

For example, for the Employee class, you have the following EmployeeDraft structure:

input EmployeeUpdate {
_id: ID # is not mandatory
# attributes
first_name: String
last_name: String
date_of_birth: Date
email_address: String
date_joined: Date
hourly_cost: Real
username: String

# outgoing roles
# ...
}

The outgoing roles are mapped as follows:

  • to-one associations: ID. In the context of an Update, you can remove the existing association by enhancing this field to null;
  • associations to many: <TargetClassName>DraftRoleRefs (analogous to <ClassName>DraftRoleRefs;
  • part to one: <PartClassName>Draft (analogous to <ClassName>Create). In the context of an Update, you can update an existing part object by including the _id in the nested structure; you can also delete the existing part by setting this field to null;
  • part to-many: <PartClassName>DraftRoleObjects (analogous to <ClassName>DraftRoleObjects).

For Employee, the outgoing roles are mapped as follows:

input EmployeeCreate {
  # attributes
  # ...

  # outgoing roles
  team: ID # association to 1
  supervisor: ID # reflexive association to 1
  address: AddressDraft # composition to 1
  qualification_: QualificationDraftRoleRefs # association to N
  assignments: Project_assignmentDraftRoleObjects # composition to N
}

input <ClassName>DraftRoleRefs. #

The input type <ClassName>DraftRoleRefs is intended to handle the modification of the association towards the class <ClassName> in the context of the service save. Like its counterpart <ClassName>RoleRefs, it is generated for all classes that have at least one many_ incoming managed association. Net of the different name, this structure coincides with its counterpart, as we can see from the following comparison:

input ClassNameDraftRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}
input ClassNameRoleRefs {
  add: [ID]
  remove: [ID]
  removeAll: Boolean
}

input <ClassName>DraftRoleObjects. #

The input type <ClassName>RoleObjects is intended to handle the creation/modification of Part objects of class <ClassName>, in the context of the save service. Like its counterpart <ClassName>RoleObjects, it is generated for all classes that have at least one many_ incoming managed Part role.

Unlike its counterpart, this structure offers a single save field (containing a list of <ClassName>Draft) that merges the create and update fields and allows the specification of both the creation of new part objects and the modification of existing part objects. The semantics of each operation is determined by the presence of a value in the nested _id field of each part object.

Below we see the comparison with <ClassName>RoleObjects:

input ClassNameDraftRoleObjects {
save: [ClassNameDraft]
delete: [ID]
deleteAll: Boolean
}
input ClassNameRoleObjects {
  create: [ClassNameCreate]
  update: [ClassNameUpdate]
  delete: [ID]
  deleteAll: Boolean
}

Delete objects #

The following services and facilities allow you to delete objects or test their deletion.

delete service #

The Mutation.<ClassName>___delete service allows you to delete an object of a certain class.

type Mutation {
  ClassName___delete(_id: ID!, forceWarnings: ForceWarnings): DeleteResult
}

The service requests as input the ID of the object of <ClassName> that you want to delete, and returns as output an object <DeleteResult>; the latter simply contains the field deleted: Boolean, which is worth true if the object was deleted or false otherwise (for example, if it was not found).

type DeleteResult {
  deleted: Boolean
}

You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

For example, the service to delete an Employee object is as follows:

type Mutation {
  Employee___delete(_id: ID!, forceWarnings: ForceWarnings): DeleteResult
}

Which can be used as follows:

mutation {
  Employee___delete(_id: "10101") {
    deleted
  }
}
{
  "deleted": true
}

validateDelete service #

The Query.<ClassName>___validateDelete service mirrors the delete service, but with the purpose of checking whether deleting the object does not violate constraints at the Data Validation level.

type Query {
  ClassName___validateDelete(_id: ID!): ValidationResult
}

The service requests the ID of the object of <ClassName> that you want to delete as input and returns the ValidationResult structure as output, which indicates whether the deletion is valid or not and reports any Issue at the Data Validation level.

For example, the service to validate the deletion of an Employee object is as follows:

type Query {
  Employee___validateDelete(_id: ID!): ValidationResult
}

deleteBulk service #

The Mutation.<ClassName>___deleteBulk service, the to-many counterpart of the delete service, allows you to delete one or more objects of a certain class.

type Mutation {
  ClassName___deleteBulk(
    options: ClassNamePageOptions!
    forceWarnings: ForceWarnings
  ): DeleteBulkResult
}

The service requires as input the structure <ClassName>PageOptions, properly valorized to identify the set of objects you want to delete, and returns in output an object <DeleteBulkResult>; the latter simply contains the field deleted: Int, to indicate the number of deleted objects.

type DeleteBulkResult {
  deleted: Boolean
}

You can work around any warnings returned by the server caused by non-blocking Class Warnings by including the forceWarnings argument, of type ForceWarnings.

For example, the service to delete several Employee objects is as follows:

type Mutation {
  Employee___deleteBulk(
    options: EmployeePageOptions!
    forceWarnings: ForceWarnings
  ): DeleteBulkResult
}

validateDeleteBulk service #

The Query.<ClassName>___validateDeleteBulk service mirrors the deleteBulk service, but with the purpose of checking whether deleting objects does not violate constraints at the Data Validation level.

type Query {
  ClassName___validateDeleteBulk(options: ClassNamePageOptions!): ValidationResult
}

The service requests the ID of the object of <ClassName> that you want to delete as input and returns the ValidationResult structure as output, which indicates whether the deletion is valid or not and reports any Issue at the Data Validation level.

For example, the service to validate the deletion of several Employee objects is as follows:

type Query {
  Employee___validateDeleteBulk(options: ClassNamePageOptions!): ValidationResult
}

type ValidationResult #

All services of type Validate mentioned so far (such as Query.<ClassName>___validate) return the following common ValidationResult structure:

type ValidationResult {
  isValid: Boolean
  issues: [Issue]
}

The isValid field is worth true if, in checking the correctness of the call at the Data Validation level, the server raised Issues of this type. The same Issues are reported in the issues field.

It is important to note that services of type Validate are the only ones able to get Issues from the GraphQL server in the data field and not nested in the errors field; this is because, in communicating the Issues, the server does not go into exception. However, these services can only “detect” Issues at the Data Validation level. More information is available here: Reference: Issue types.

input ForceWarnings. #

As stated in The GraphQL schema: Server-side errors, by default, the server returns a GraphQL error whether there are (blocking) errors or (non-blocking) warnings in the application, as is the case when a Class warning is set to not prevent the user from performing an action.

In GraphQL, some services allow you to “force” warnings not to block execution.

The mutation type write services listed are forceable via the forceWarnings argument:

The input type ForceWarnings consists of two options:

input ForceWarnings {
  actionVeto: Boolean = false
  dataValidation: Boolean = false
}

These allow you to force warnings raised by the server in response to the events for which you can set a Class Warning.

Class warning editor

Basically, actionVeto refers to the following events:

  • Edit: occurs when a GraphQL service that modifies existing data (update, save, delete, …) is invoked;
  • Create: occurs when a GraphQL service that creates new data (create, save) is invoked.

On the other hand, dataValidation refers to the Database events in the figure.

By properly setting one of these two fields to true, it is possible to bypass any non-blocking Class Warning defined on our class of interest.

Let’s look at an example. Suppose we have defined the following non-blocking Class Warning on Employee:

Example class warning

The following call fails:

mutation {
  Employee___save(
    data: {
      first_name: "Reiner"
      last_name: "Galliard"
      date_of_birth: "15/08/1990"
      date_joined: "10/10/2018"
      hourly_cost: "12.0"
    }
  ) {
    full_name
    age
  }
}
{
  "errors": [
    {
      "message": "Hourly cost is too low concerning the employee's experience.",
      "extensions": {
        "sourceRequestReference": "Employee___save",
        "userMessage": "Hourly cost is too low with respect to the employee's experience.",
        "issueLevel": "WARNING",
        "issueReferenceType": "ENTITY",
        "issueType": "ENTITY_DOMAIN",
        "entityName": "Employee",
        "entityID": "117010",
        "_clientId": "",
        "attributeNames": [],
        "roleNames": [],
        "applicationName": "Administration",
        "profileName": "Administrator",
        "traceId": "fd2eab81a5ea1ec2c6c7687ad5d3bf080e14c130",
        "classification": "DataFetchingException"
      }
    }
  ],
  "data": {
    "Employee___save": null
  }
}

The server returned a single Issue of level WARNING. Instead of changing the value of the hourly_cost field, we modify the call so that it forces the Class warning and verify that the next call is successful:

mutation {
Employee___save(
data: {
first_name: "Reiner"
last_name: "Galliard"
date_of_birth: "15/08/1990"
date_joined: "10/10/2018"
hourly_cost: "12"
}
forceWarnings: {dataValidation: true}
) {
full_name
age
}
}
{
  "data": {
    "Employee___save": {
      "full_name": "Reiner Leonhart",
      "age": 30
    }
  }
}