docs: function buildItem(parentAttrNode, row, context) {
      const item = {};
  
      if (context.primaryKey === null) resolvePrimaryKey(row, context);
  
      Object.keys(parentAttrNode.attributes).forEach(attrName => {
          const attrNode = parentAttrNode.attributes[attrName];
  
          if (context.selectedInternalLevel > 0) {
              if (!attrNode.selectedInternal) return;
              if (!attrNode.selectedInternal[context.selectedInternalLevel - 1]) return;
          } else if (!attrNode.selected) return;
  
          if (attrNode.attributes) {
              const subContext = Object.assign({}, context);
              subContext.attrPath = context.attrPath.concat([attrName]);
  
              if (attrNode.dataSources) {
                  // resource context:
                  subContext.resourceAttrNode = attrNode;
                  subContext.primaryKey = null;
                  subContext.secondaryRows = null;
  
                  let rawResult;
                  let keys;
                  let keyIsNull = true;
                  let parentKeyRow = row;
  
                  if (
                      attrNode.parentDataSource &&
                      attrNode.parentDataSource !== context.resourceAttrNode.primaryDataSource
                  ) {
                      parentKeyRow = context.secondaryRows[attrNode.parentDataSource];
                  }
  
                  if (parentKeyRow) {
                      rawResult = getResult(attrNode, attrNode.joinVia || attrNode.primaryDataSource, subContext);
                      keys = rawResult.parentKey.map(keyAttr => {
                          if (typeof parentKeyRow[keyAttr] === 'undefined') {
                              /*
                              // TODO: Strict mode?
                              throw new DataError(`Sub-resource "${subContext.attrPath.join('.')}" ` +
                                  misses keyattribute "${keyAttr}" in parent result ` +
                                  `(DataSource "${context.resourceAttrNode.primaryDataSource}")`);
                              */
                          } else if (parentKeyRow[keyAttr] !== null) {
                              keyIsNull = false;
                          }
                          return parentKeyRow[keyAttr];
                      });
                  }
  
                  if (keyIsNull) {
                      keys = [];
                  } else if (!rawResult.multiValuedParentKey) {
                      // normal and composite parentKey: "keys" is a string (as array) afterwards:
                      keys = [keys.join(keySeparator)];
                  } else {
                      // dereference parentKey array (key length must be 1 when multiValued)
                      // but "keys" is still an array (multiValued) afterwards:
                      keys = keys[0];
  
                      if (!Array.isArray(keys)) {
                          throw new DataError(
                              `Sub-resource "${subContext.attrPath.join('.')}" ` +
                                  `multiValued key attribute "${rawResult.parentKey[0]}" ` +
                                  'in parent result is not an array ' +
                                  `(DataSource "${context.resourceAttrNode.primaryDataSource}")`
                          );
                      }
                  }
  
                  let secondaryRows = null;
                  if (attrNode.joinVia) {
                      let primaryRawResult = null; // set on demand to avoid errors for empty results
                      const joinViaKeys = [];
                      secondaryRows = {};
  
                      keys.forEach(key => {
                          let joinViaRows = rawResult.indexedData[key];
                          if (!joinViaRows) return;
  
                          if (rawResult.uniqueChildKey) joinViaRows = [joinViaRows];
  
                          joinViaRows.forEach(joinViaRow => {
                              primaryRawResult =
                                  primaryRawResult || getResult(attrNode, attrNode.primaryDataSource, subContext);
  
                              let childKeyIsNull = true;
                              const childKey = primaryRawResult.parentKey
                                  .map(keyAttr => {
                                      if (typeof joinViaRow[keyAttr] === 'undefined') {
                                          throw new DataError(
                                              `Sub-resource "${subContext.attrPath.join('.')}" ` +
                                                  `misses key attribute "${keyAttr}" in joinVia result ` +
                                                  `(DataSource "${attrNode.joinVia}")`
                                          );
                                      } else if (joinViaRow[keyAttr] !== null) {
                                          childKeyIsNull = false;
                                      }
                                      return joinViaRow[keyAttr];
                                  })
                                  .join(keySeparator);
  
                              if (!childKeyIsNull) {
                                  joinViaKeys.push(childKey);
  
                                  secondaryRows[childKey] = {};
                                  secondaryRows[childKey][attrNode.joinVia] = joinViaRow;
                              }
                          });
                      });
  
                      rawResult = primaryRawResult;
                      keys = joinViaKeys;
                  }
  
                  const subItem = [];
                  keys.forEach(key => {
                      const subRow = rawResult.indexedData[key];
                      if (!subRow) {
                          // ignore
                      } else {
                          subContext.secondaryRows = secondaryRows ? secondaryRows[key] : null;
  
                          if (!rawResult.uniqueChildKey) {
                              subRow.forEach(singleSubRow => subItem.push(buildItem(attrNode, singleSubRow, subContext)));
                          } else {
                              subItem.push(buildItem(attrNode, subRow, subContext));
                          }
                      }
                  });
  
                  item[attrName] = attrNode.many ? subItem : subItem[0] || null;
              } else {
                  // nested-attribute context:
                  item[attrName] = buildItem(attrNode, row, subContext);
              }
          } else {
              item[attrName] = buildAttribute(attrNode, row, context);
          }
      });
  
      // Extension: "item"
      if (parentAttrNode.resourceName) {
          const resource = context.api.getResource(parentAttrNode.resourceName);
          if (resource && resource.extensions && resource.extensions.item) {
              resource.extensions.item({
                  item,
                  row,
                  secondaryRows: context.secondaryRows,
                  request: context.request,
                  getAttribute: attribute => getAttribute(attribute || [], parentAttrNode),
                  getResult: (attrNode, dataSourceName) =>
                      getResult(
                          attrNode || parentAttrNode,
                          dataSourceName || (attrNode || parentAttrNode).primaryDataSource,
                          context
                      ),
                  buildItem: attributes => {
                      const maxRecursionLevel = 20;
  
                      function selectInternal(level, isSelected) {
                          attributes.forEach(attribute => {
                              let attrNode = parentAttrNode;
                              attribute.forEach(attrName => {
                                  if (!(attrNode.attributes && attrNode.attributes[attrName])) {
                                      let attrStr = attribute.join('.');
                                      throw new ImplementationError(
                                          `item-extension/buildItem: Unknown attribute "${attrStr}"`
                                      );
                                  }
                                  attrNode = attrNode.attributes[attrName];
  
                                  if (!attrNode.selectedInternal) {
                                      // we use a flag per recursion level to handle recursive buildItem calls independently
                                      attrNode.selectedInternal = new Array(maxRecursionLevel).fill(false);
                                  }
                                  attrNode.selectedInternal[level - 1] = isSelected;
                              });
                          });
                      }
  
                      const subContext = Object.assign({}, context);
                      subContext.selectedInternalLevel++;
  
                      if (subContext.selectedInternalLevel > maxRecursionLevel) {
                          let attrStr = attributes.map(attribute => attribute.join('.')).join(',');
                          throw new ImplementationError(
                              `item-extension/buildItem: Recursion depth for "${attrStr}" too big`
                          );
                      }
  
                      selectInternal(subContext.selectedInternalLevel, true);
                      const internalItem = buildItem(parentAttrNode, row, subContext);
                      selectInternal(subContext.selectedInternalLevel, false);
  
                      return internalItem;
                  }
              });
          }
      }
  
      context.primaryKey = null; // clear primaryKey cache for next row
  
      return item;
  }