Escritura condicional en DynamoDB en Node.js (AWS SDK v3)

Lo interesante de una escritura condicional en el AWS SDK v3 no es la ConditionExpression, que funciona igual en todas partes y está cubierta en expresiones de condición de DynamoDB. Es el camino del fallo: v3 te entrega dentro del error lanzado el Item de la carrera que perdiste, si lo pediste, y no te da nada si no lo hiciste.

Código

import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
  TableName: 'Music',
  Key: {
    Artist: {S: 'Arturo Sandoval'},
    SongTitle: {S: 'Cubano Chant'}
  },
  UpdateExpression: 'SET #upd0 = :updValue0, #version = :newVersion',
  ConditionExpression: 'attribute_exists(#cond0) AND #version = :expectedVersion',
  ExpressionAttributeNames: {
    '#upd0': 'Genre',
    '#version': 'Version',
    '#cond0': 'Artist'
  },
  ExpressionAttributeValues: {
    ':updValue0': {S: 'Latin Jazz'},
    ':expectedVersion': {N: '7'},
    ':newVersion': {N: '8'}
  },
  ReturnValuesOnConditionCheckFailure: 'ALL_OLD'
});

try {
  await client.send(command);
  console.log('Updated to version 8');
} catch (err) {
  if (err.name === 'ConditionalCheckFailedException') {
    // With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
    // rides back on the exception — no extra read to see what beat you.
    console.log('Lost the race — item is now:', err.Item);
  } else {
    throw err;
  }
}

Explicación

  • La comprobación fallida es un error lanzado, no un campo de estado. v3 rechaza la promesa, así que el camino de la escritura y el de la carrera perdida son ramas distintas. err.name === 'ConditionalCheckFailedException' es el discriminador; cualquier otra cosa hay que relanzarla, y para eso está el else del bloque. Si te tragas el catch entero, has convertido en silencio un throttling en una operación sin efecto.
  • ReturnValuesOnConditionCheckFailure es la única forma de ver quién te ganó. Sin él, el error lleva el mensaje y nada más, y vuelves a un GetItem que no necesitabas. La referencia de la API fija sus valores válidos en ALL_OLD | NONE y confirma que no consume capacidad de lectura.
  • err.Item es un mapa AttributeValue en crudo, con la misma forma que la Key que enviaste, no JavaScript plano. Pásalo por unmarshall de @aws-sdk/util-dynamodb antes de comparar Version con un número, o estarás comparando contra {N: '9'}.
  • La escritura fallida también se factura. La Developer Guide es explícita: una condición que se evalúa como falsa consume capacidad de escritura igualmente, dimensionada según el mayor entre el Item antiguo y el nuevo. Un bucle de reintento sobre una clave caliente es una línea real en la factura, así que limita los intentos.
  • Todos los nombres del bloque están aliaseados (#versionVersion, #cond0Artist) porque el Expression Builder que lo generó aliasea sin condiciones. Aquí es más pesado de lo necesario y nunca es incorrecto: ese es el intercambio que hace.

Sacar de la excepción la copia del que perdió

Pon el Version almacenado a 9 y ejecuta el bloque, que espera 7. DynamoDB Local 3.3.0 lanza el error, y el error capturado lleva:

err.name     ConditionalCheckFailedException
err.message  The conditional request failed
err.$metadata.httpStatusCode  400
err.Item     {
               Artist:     { S: 'Arturo Sandoval' },
               Year:       { N: '1994' },
               Version:    { N: '9' },
               SongTitle:  { S: 'Cubano Chant' },
               AlbumTitle: { S: 'Danzon' }
             }

Ese Version: 9 es la clave de todo. El reintento puede volver directo a la actualización con :expectedVersion puesto a 9, sin lectura extra y sin ninguna ventana por la que un tercer escritor se cuele entre tu GetItem y tu reintento.

Borra ReturnValuesOnConditionCheckFailure de ese mismo comando y vuelve a ejecutarlo. El mismo name, el mismo message, el mismo 400, y err.Item es undefined. Nada te avisa: el parámetro es opcional, su ausencia no es un error, y el código que lee err.Item simplemente empieza a registrar undefined en producción.

Fíjate también en que un 400 aquí no significa una petición mal formada. ValidationException y ConditionalCheckFailedException comparten el código de estado, y solo uno de los dos es un bug: por eso la rama va sobre err.name y nunca sobre el estado.

Para ver una condición tener éxito y fallar contra tus propios datos, con la expresión escrita por ti en vez de tecleada, descarga DynoTable.

Ejemplos relacionados

Referencias

Verificado por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.

Trabaja con DynamoDB sin la Consola

Un cliente de escritorio rápido para DynamoDB que ejecuta el SQL real que DynamoDB no puede — JOINs, GROUP BY, agregaciones — con edición visual y un agente de IA con tus propias claves de Bedrock.

Prueba gratuita de 30 días, sin tarjeta — después, el plan Free sin límite de tiempo.