electrodb icon indicating copy to clipboard operation
electrodb copied to clipboard

Virtual attributes - should they invoke the getters of fields they depend on?

Open JoshBarr opened this issue 1 year ago • 1 comments

Describe the bug Firstly, thanks for this library. It makes working with DDB really straightforward.

The issue I'm having is that watched attribute values passed into the get method of a virtual attribute do not appear to invoke their own get methods. This can lead to errors when attempting to read existing items that may not yet have the new attribute added.

Here's an example in the playground:

ElectroDB Version 2.13.1

ElectroDB Playground Link Playground

Entity/Service Definitions Include your entity model (or a model that sufficiently recreates your issue) to help troubleshoot.

const tasks = new Entity(
  {
    model: {
      entity: "tasks",
      version: "1",
      service: "taskapp"
    },
    attributes: {
      id: {
        type: "string",
      },
      replayedAt: {
        type: 'list',
        items: {
          type: 'number',
          required: true,
        },
        required: true,
        default: [],
        // Add a getter for accessing existing items that may not have a `replayedAt` attribute
        get: (value: unknown) => {
          if (Array.isArray(value)) {
            return value
          }

          return []
        },
      },
      isReplay: {
        type: 'boolean',
        required: true,
        watch: ['replayedAt'],
        get: (_, { replayedAt }) => {
            // Note: the sibling attribute values passed into the `get` method
            // do not invoke their own get methods, so we must check
            // if the attribute is an array before we can check it has a length > 0.
            // The below will throw an error of "Cannot read properties of undefined (reading 'length')"
            return replayedAt.length > 0
        },
        set: () => undefined,
      },
    },
    indexes: {
      id: {
        pk: {
          field: "pk",
          composite: ["id"]
        },
      },
    }
  },
  { table }
);

Expected behavior In the example above, I'd expect replayedAt to be an array, even if the attribute does not exist on the Item. In the current behaviour, I need to essentially re-implement the replayedAt.get method inside isReplay.

Errors

Cannot read properties of undefined (reading 'length')" - For more detail on this error reference: https://electrodb.dev/en/reference/errors/#aws-error
    at Entity4.formatResponse (/node_modules/electrodb/src/entity.js:955:20)
    at Entity4.executeQuery (/node_modules/electrodb/src/entity.js:746:23)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at Entity4.go (/node_modules/electrodb/src/entity.js:508:18)

Additional context I fully accept that I might be using ElectroDB incorrectly!

JoshBarr avatar Oct 08 '24 03:10 JoshBarr

I just tried this locally, with the latest version (2.15.0) like this, and everything worked as expected: https://gist.github.com/tywalch/fa43890fa0c7beb84804811b7f95bf46

You have the concept of "virtual columns" correct, and your implementation looks excellent; can you try the gist above? If you are still experience the error, can you upgrade to see if it might be a bug in a previous version?

tywalch avatar Oct 19 '24 18:10 tywalch

I am going to close this ticket, but feel free to reopen it if you need to pick this question back up 👍

tywalch avatar Nov 02 '24 19:11 tywalch

I'm running into a similar issue, where I want to add an attribute to an existing ElectroDB entity where there are already items in DynamoDB without that new attribute. I want to query for an existing item, and since the new attribute isn't in the existing item, I want ElectroDB to call the get method of that attribute (defaulting that attribute to some value if it doesn't exist in the item)

In @JoshBarr 's case, I think he may be adding the replayedAt attribute to an existing ElectroDB entity (see "even if the attribute does not exist on the Item" in his original question), and expecting to query for existing items and have replayedAt return an empty array (and isReplay return false), since that attribute doesn't exist on the existing items

For example:

  • Create items with this entity
  • Add new attributes onto the entity and query for existing items (like this)

ides15 avatar Nov 06 '24 19:11 ides15