DEV Community

Cover image for New DynamoDB-Toolbox v1 beta: Features and breaking changes
Thomas Aribart for Serverless By Theodo

Posted on

New DynamoDB-Toolbox v1 beta: Features and breaking changes

☝️ NOTE: This article details how to migrate from the beta.0 to the beta.1 of the new DynamoDB-Toolbox major.

👉 If you need the full documentation of the beta.1 release, you may be looking for this article.

👉 If you need the full documentation of the beta.0 release, you may be looking for its previous version.

A new v1 beta of DynamoDB-Toolbox is out 🙌

Simply named beta.1, the aim of this article is to succinctly describe its new features and breaking changes from the beta.0.

New features

UpdateItemCommand

The main update from this release is that the UpdateItemCommand is now available 🥳

It's clearly one of of the hardest feature I had to develop in my life (thank you, DynamoDB reference for being very unclear on soooooo many edge cases 😳). I'm glad it is now shipped!

See the updated documentation article for an exhaustive documentation, but here's a sneak peek:

import { UpdateItemCommand, $add, $get } from 'dynamodb-toolbox';

const { Item } = await pokemonEntity
  .build(UpdateItemCommand)
  .item({
    pokemonId,
    ...
    name: newName,
    // Save current 'level' value to 'previousLevel'
    previousLevel: $get('level'),
    // Add 1 to the current 'level'
    level: $add(1),
  })
  .options({
    // Only if level is < 99
    condition: { attr: 'level', lt: 99 },
    // Will type the response `Attributes`
    returnValues: 'ALL_NEW',
    ...
  })
  .send();
Enter fullscreen mode Exit fullscreen mode

MockEntity

Also, a new mockEntity util is now available to help you write unit tests and make assertions:

import { mockEntity } from 'dynamodb-toolbox';

const mockedPokemonEntity = mockEntity(pokemonEntity);

mockedPokemonEntity.on(GetItemCommand).resolve({
  // 🙌 Type-safe!
  Item: {
    pokemonId: 'pikachu1',
    name: 'Pikachu',
    level: 42,
    ...
  },
});

await pokemonEntity
  .build(GetItemCommand)
  .key({ pokemonId: 'pikachu1' })
  .options({ consistent: true })
  .send();
// => Will return mocked values!

mockedPokemonEntity.received(GetItemCommand).args(0);
// => [{ pokemonId: 'pikachu1' }, { consistent: true }]
Enter fullscreen mode Exit fullscreen mode

Providing getters for table names

Last small improvement: Table names can now be provided with getters. This can be useful in some contexts where you may want to use the Table class without actually running any command (e.g. tests or deployments):

const myTable = new TableV2({
  ...
  // 👇 Only executed at command execution
  name: () => process.env.TABLE_NAME,
});
Enter fullscreen mode Exit fullscreen mode

Breaking changes

default options

With the appearance of the UpdateItemCommand, the default option had to be reworked.

I disliked it for being ambiguous and impractical in some cases (like the internal created timestamp attribute). Well, it is now split into three options:

  • defaults.key option (keyDefault method): Fills undefined key attributes during key computing. Used in all commands for now, we'll see about Queries and Scans.
  • defaults.put option (putDefault method): Used for non-key attributes in PutItemCommands
  • defaults.update option (updateDefault method): Used for non-key attributes in UpdateItemCommands
// 👇 Regular attribute
const lastUpdateAttribute = string({
  defaults: {
    key: undefined,
    put: () => new Date().toISOString(),
    update: () => new Date().toISOString(),
  },
});
// ...or
const lastUpdateAttribute = string()
  .putDefault(() => new Date().toISOString())
  .updateDefault(() => new Date().toISOString());

// 👇 Key attribute
const keyAttribute = string({
  key: true,
  defaults: {
    key: 'my-awesome-partition-key',
    // put & update defaults are not useful in `key` attributes
    put: undefined,
    update: undefined,
  },
});
// ...or
const keyAttribute = string().key().keyDefault('my-awesome-partition-key');
Enter fullscreen mode Exit fullscreen mode

Note that the default method is still there. It acts similarly as putDefault, except if the attribute has been tagged as a key attribute, in which case it acts as keyDefault:

const metadata = any().default({ any: 'value' });
// 👇 Similar to
const metadata = any().putDefault({ any: 'value' });
// 👇 ...or
const metadata = any({
  defaults: {
    key: undefined,
    put: { any: 'value' },
    update: undefined,
  },
});

const keyPart = any().key().default('my-awesome-partition-key');
// 👇 Similar to
const metadata = any().key().keyDefault('my-awesome-partition-key');
// 👇 ...or
const metadata = any({
  key: true,
  defaults: {
    key: 'my-awesome-partition-key',
    // put & update defaults are not useful in `key` attributes
    put: undefined,
    update: undefined,
  },
});
Enter fullscreen mode Exit fullscreen mode

Also, the const shorthand still acts enum(MY_CONST).default(MY_CONST) and does NOT provide update defaults. Let me know if it's something you'd need.

computeDefaults

The same split had to be applied to computed defaults: In the same spirit, the computeDefaults property has been renamed putDefaults, and a new updateDefaults property has appeared (with computeKey still being available to compute the primary key):

import { schema, EntityV2, ComputedDefault } from 'dynamodb-toolbox';

const pokemonSchema = schema({
  ...
  level: number(),
  levelPlusOne: number().default(ComputedDefault),
  previousLevel: number().updateDefault(ComputedDefault),
});

const pokemonEntity = new EntityV2({
  ...
  schema: pokemonSchema,
  putDefaults: {
    // 🙌 Correctly typed!
    levelPlusOne: ({ level }) => level + 1,
  },
  updateDefaults: {
    // 🙌 Correctly typed!
    previousLevel: ({ level }) =>
      // Update 'previousLevel' only if 'level' is updated
      level !== undefined ? $get('level') : undefined,
  },
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

That's it 🙌 I hope you like this new version... and YES, Queries and Scans are next!

See you in a few months for the official release! (before the end of the year I hope 🤞)

Top comments (0)