shiftcode / dynamo-easy

DynamoDB client for NodeJS and browser with a fluent api to build requests. We take care of the type mapping between JS and DynamoDB, customizable trough typescript decorators.
https://shiftcode.github.io/dynamo-easy/
MIT License
204 stars 27 forks source link

get item by partition key returns error only inside lambda #412

Open orr-levinger opened 7 months ago

orr-levinger commented 7 months ago

Describe the bug This bug happens to me only when running in a lambda. When running locally this doesnt happen. here are my libs versions:

dynamo-easy version: 8.0.0-next.3 aws v3 dynamo version: 3.445.0 Node.js Version: v18.18.0

I have a model that has a string field called status:

import {
  Model,
  PartitionKey,
  SortKey,
  LSISortKey,
  GSIPartitionKey,
  GSISortKey,
  Property,
  fromDb as fromDBB,
  PropertyMetadata,
  metadataForModel,
  Attributes,
  ModelConstructor,
} from '@shiftcoders/dynamo-easy';
import { BaseDynamoModel } from '@models/base-dynamo-model';
import { MATCH_TABLE } from '@static/consts';
import { MatchGraphQLType, Quality, RejectReason } from '@type/Match';
import type { MatchStatus } from '@type/Match';
import { Avatar, Gender } from '@type/User';
import type { MapperForType, StringAttribute } from '@shiftcoders/dynamo-easy';

@Model({ tableName: MATCH_TABLE })
export class MatchModel extends BaseDynamoModel implements MatchGraphQLType {
  static readonly byUniqueId = 'byUniqueId';
  static readonly byMatchIdUserId = 'byMatchId-userId';
  static readonly byUserIdStatus = 'byUserId-status';
  static readonly byUserIdActive = 'byUserId-active';
  static readonly byConversationId = 'byConversationId';
  @GSIPartitionKey(MatchModel.byUniqueId)
  id: string;
  @PartitionKey()
  @GSISortKey(MatchModel.byMatchIdUserId)
  userId: string;
  @SortKey()
  @GSIPartitionKey(MatchModel.byMatchIdUserId)
  matchId: string;

  @LSISortKey(MatchModel.byUserIdStatus)
  @Property({ mapper: statusMapper })
  **status: MatchStatus;**

  @LSISortKey(MatchModel.byUserIdActive)
  active: 'true' | 'false';
}

export type MatchStatus =
  | 'pending'
  | 'invited'
  | 'accepted'
  | 'failure'
  | 'success';

here is the DDB table definition:

  MatchesTable:
    DeletionPolicy: Delete
    Type: "AWS::DynamoDB::Table"
    Properties:
      Tags:
        - Key: env
          Value: ${sls:stage}
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
        - AttributeName: status
          AttributeType: S
        - AttributeName: userId
          AttributeType: S
        - AttributeName: matchId
          AttributeType: S
        - AttributeName: active
          AttributeType: S
      KeySchema:
        - AttributeName: userId
          KeyType: HASH
        - AttributeName: matchId
          KeyType: RANGE
      LocalSecondaryIndexes:
        - IndexName: byUserId-status
          KeySchema:
            - AttributeName: userId
              KeyType: HASH
            - AttributeName: status
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
        - IndexName: byUserId-active
          KeySchema:
            - AttributeName: userId
              KeyType: HASH
            - AttributeName: active
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      GlobalSecondaryIndexes:
        - IndexName: byUniqueId
          KeySchema:
            - AttributeName: id
              KeyType: HASH
          Projection:
            ProjectionType: ALL
        - IndexName: byMatchId-userId
          KeySchema:
            - AttributeName: matchId
              KeyType: HASH
            - AttributeName: userId
              KeyType: RANGE
          Projection:
            ProjectionType: ALL
      BillingMode: PAY_PER_REQUEST
      TableName: ${self:custom.base}-matches
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

here is my table item instance:

  "userId": {
    "S": "f43276be-3a2c-4888-b2b9-9ae23471094c"
  },
  "matchId": {
    "S": "4913f5ad-198e-4fcd-b929-2f3e996ce6ce"
  },
  "active": {
    "S": "true"
  },
  "id": {
    "S": "f43276be-3a2c-4888-b2b9-9ae23471094c_4913f5ad-198e-4fcd-b929-2f3e996ce6ce"
  },
  "status": {
    "S": "invited"
  }
}

this is the query i run:

  async getMatchById(id: string) {
    return this.query().index(MatchModel.byUniqueId).wherePartitionKey(id).execSingle();
  }

only when this code runs inside lambda i get the error: { "errorType": "Error", "errorMessage": "could not resolve the dynamo db type for attribute value invited", "stack": [ "Error: could not resolve the dynamo db type for attribute value invited", " at typeOfFromDb (/opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/util.ts:231:9)", " at fromDbOne (/opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/mapper.ts:291:64)", " at /opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/mapper.ts:274:20", " at Array.forEach ()", " at fromDb (/opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/mapper.ts:244:44)", " at Object.fromDb (/var/task/src/functions/events/webpack:/blind-chat-backend/src/models/match-model.ts:75:14)", " at /opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/mapper.ts:268:50", " at Array.forEach ()", " at fromDb (/opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/mapper/mapper.ts:244:44)", " at /opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/dynamo/request/read-many.request.ts:222:63", " at Array.map ()", " at ReadManyRequest.mapFromDb (/opt/nodejs/node_modules/@shiftcoders/dynamo-easy/src/dynamo/request/read-many.request.ts:222:43)", " at processTicksAndRejections (node:internal/process/task_queues:95:5)", " at Runtime.v [as handler] (/var/task/src/functions/events/webpack:/blind-chat-backend/src/functions/events/get-match.ts:16:3)" ] }

when creating a property mapper:

export const statusMapper: MapperForType<string, StringAttribute> = {
  fromDb: (attributeValue: any): any => {
    console.log('fromDb orr', attributeValue);
    return attributeValue.S;
  },
  toDb: (modelValue: any): any => {
    console.log('toDb orr', modelValue);
    try {
      return modelValue;
    } catch (e) {
      console.log('error', e);
      return modelValue;
    }
  },
};

that does nothing special just what dynamo-easy was supposed to do it works.. and prints: fromDb orr { S: 'invited' }

also noticed the wrong mapper is chosen: INFO dynamo.mapper.mapper (Object): map toDb {"item":"Conscientiousness"}

image