I built a custom module which creates a custom field type (with formatter and widget), and I would like to make one property of the field incremental (to increase its value by one for each instance of the field.)
What I have so far is:
- web/modules/custom/asciinema/src/Plugin/Field/FieldType/AsciinemaItem.php (the field definition)
 
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    return [
      // columns contains the values that the field will store
      'columns' => [
        // List the values that the field will save.
        'aid' => [
          'type'        => 'serial',
          'size'        => 'normal',
          'description' => 'The id of the asciinema instance.',
          'unsigned'    => TRUE,
          'not null'    => TRUE,
        ],
      ],
      .....
      'unique keys' => [
        'aid' => ['aid'],
      ],
    ];
  // This function ensures that the value is incremented each time a new field instance is saved.
  public function preSave() {
    if ($this->getEntity()->isNew()) {
      // Increase the value of aid when dealing with a new entity.
      $fieldConfig = $this->getFieldDefinition();
      $field_name = $fieldConfig->getName();
      $entity_type = $fieldConfig->getTargetEntityTypeId();
      
      $query = \Drupal::database()->select($entity_type . '__' . $field_name, 'ef')
                ->fields('ef', [$field_name . '_' . 'aid'])
                ->orderBy($field_name . '_' . 'aid', 'DESC');
      $lastIndex = $query->execute()->fetchField();
      
      $result = !empty($lastIndex) ? $lastIndex + 1 : 1;
      $this->values['aid'] = $result;
      $this->setValue($this->values);
    }
  }
- web/modules/custom/asciinema/src/Plugin/Field/FieldWidget/AsciinemaDefaultWidget.php (the field widget).
 
 public function formElement(
    FieldItemListInterface $items,
    $delta,
    array $element,
    array &$form,
    FormStateInterface $form_state
  ) {
    // Current values with fallbacks.
    $aid      = isset($items[$delta]->aid) ? $items[$delta]->aid : 0;
    ....
    $element['asciinema']['id']['aid'] = [
      '#title'          => t('AID'),
      '#description'    => t('Asciinema ID.'),
      '#disabled'       => TRUE,
      '#type'           => 'textfield',
      '#default_value'  => $aid,
    ];
    ....
    return $element;
    ....
  public function massageFormValues(
    array $values,
    array $form,
    FormStateInterface $form_state
  ) {
    foreach ($values as &$value) {
      // ID.
      $value['aid']       = $value['asciinema']['id']['aid'];
    ....
      unset($value['asciinema']);
    }
    return $values;
  }
- web/modules/custom/asciinema/src/Plugin/Field/FieldFormatter/AsciinemaDefaultFormatter.php (The formatter):
 
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $source = [];
    foreach ($items as $delta => $item) {
      // Render output using asciinema_default theme.
      $source['asciinema'] = [
        '#theme'     => 'asciinema_default',
        '#asciinema' => [
          'aid'          => $item->aid,
        ],
      ];
      ...
      $elements[$delta] = [
        '#markup' => \Drupal::service('renderer')->render($source),
      ];
    }
    return $elements;
As it is right now I can save the values entered in my custom field and the aid gets increased for each field instance. However I can not edit any of the past data because that would generate:
NOTICE: PHP message: Uncaught PHP Exception Drupal\Core\Entity\EntityStorageException: "SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "node_revision__asciinema__asciinema_aid__key"
php_1       | DETAIL:  Key (asciinema_aid)=(4) already exists.:
I would love to find out what's missing or to find a better and cleaner way to deal with incremental fields.
EDIT:
I managed to get around the limitations by removing:
'unique keys' => [
        'aid' => ['aid'],
      ],
... from the schema definitions and adding an extra condition in the presave() function.
public function preSave() {
    if ($this->getEntity()->isNew() || empty($this->values['aid'])) {
      ...
    }
However I do not consider this a definitive answer, but more of a work-around. I would like to make use of database constraints like AUTO INCREMENT or UNIQUE KEY as well. To this date I have not found out how to have it working with custom field properties.