@moznion/ts-dynamodb-attributes-transformer
    TypeScript icon, indicating that this package has built-in type declarations

    0.2.0 • Public • Published

    ts-dynamodb-attributes-transformer .github/workflows/check.yml npm version

    Code transformer plugin powered by TypeScript Compiler API that transforms TypeScript object from/to Amazon DynamoDB attributes.

    Description

    This plugin replaces the TypeScript function invocation with the generated object code. In short, this plugin generates the code for every property of type T.

    dynamodbRecord<T>(obj: T): Record<keyof T, AttributeValue>

    This plugin replaces dynamodbRecord<T>(obj: T) invocation with Record<keyof T, AttributeValue> value that is defined in aws-sdk-js-v3 according to the type T and the contents of the object.

    This plugin powers the users can do drop-in replacements for the existing Record<keyof T, AttributeValue> value and/or the generator with dynamodbRecord<T>(obj: T) function.

    fromDynamodbRecord<T>(attrs: Record<string, AttributeValue>): T

    This replaces fromDynamodbRecord<T>(attrs: Record<string, AttributeValue>) invocation with the object which has type T. This method is responsible to translate the DynamoDB attributes to the actual TypeScript object, i.e. unmarshalling.

    Motivations

    • To do automatic generation of the DynamoDB attribute data type code that is recognizable by aws-sdk-js-v3, with type safety.
      • Manual making the translation layer between the object and DynamoDB's Record is no longer needed!
    • Performance. This uses TypeScript Compiler API, so it generates/determine the DynamoDB attribute code at the compiling timing. This means the logic doesn't have to do a reflection on the fly so this contributes to a good performance.

    Benchmark

    The benchmark result between this project and kayomarz/dynamodb-data-types is the following:

    marshalling:

    node version: v16.17.0
    dynamodb-data-types marshalling x 3,845,247 ops/sec ±0.63% (90 runs sampled)
    ts-dynamodb-attributes-transformer marshalling x 13,614,974 ops/sec ±0.24% (100 runs sampled)
    Fastest is ts-dynamodb-attributes-transformer marshalling
    

    unmarshalling:

    node version: v16.17.0
    dynamodb-data-types unmarshalling x 1,800,718 ops/sec ±0.30% (96 runs sampled)
    ts-dynamodb-attributes-transformer unmarshalling x 3,493,272 ops/sec ±0.50% (98 runs sampled)
    Fastest is ts-dynamodb-attributes-transformer unmarshalling
    

    Please see also benchmark project.

    Synopsis

    Marshalling into DynamoDB record from the Typescript Object

    import { AttributeValue } from '@aws-sdk/client-dynamodb';
    import { dynamodbRecord } from '@moznion/ts-dynamodb-attributes-transformer';
    
    interface User {
      readonly id: number;
      readonly name: string;
      readonly tags: Map<string, string>;
    }
    
    const record: Record<keyof User, AttributeValue> = dynamodbRecord<User>({
      id: 12345,
      name: 'John Doe',
      tags: new Map<string, string>([
        ['foo', 'bar'],
        ['buz', 'qux'],
      ]),
    });
    
    /*
     * Then you can use this record value on the aws-sdk-js-v3's DynamoDB client; for example,
     *
     *   const dyn = new DynamoDBClient(...);
     *   await dyn.send(new PutItemCommand({
     *     TableName: "...",
     *     Item: record, // <= HERE!
     *   }));
     */

    Then this plugin transforms the above TypeScript code like the following JavaScript code:

    const record = (function (arg) {
      return {
        id: {
          N: arg.id.toString()
        },
        name: {
          S: arg.name
        },
        tags: {
          M: (function () {
            var m;
            m = {}
            for (const kv of arg.tags) {
              m[kv[0]] = { S: kv[1] }
            }
            return m;
          })()
        }
      };
    })({
      id: 12345,
      name: 'John Doe',
      tags: new Map([
        ['foo', 'bar'],
        ['buz', 'qux'],
      ]),
    });
    /*
     * This record is equal to the following object:
     *
     *   {
     *     id: { N: "12345" },
     *     name: { S: "John Doe" },
     *     tags: {
     *       M: {
     *         foo: { S: "bar" },
     *         buz: { S: "qux" }
     *       }
     *     }
     *   }
     */

    Unmarshalling into TypeScript object from DynamoDB record

    import { fromDynamodbRecord } from '@moznion/ts-dynamodb-attributes-transformer';
    
    interface User {
      readonly id?: number;
      readonly name?: string;
      readonly tags: Map<string, string>;
    }
    
    const user: User = fromDynamodbRecord<User>({
      id: {
        N: '12345',
      },
      name: {
        S: 'John Doe',
      },
      tags: {
        M: {
          foo: {
            S: 'bar',
          },
          buz: {
            S: 'qux',
          },
        },
      },
    });

    Then this plugin transforms the above TypeScript code like the following JavaScript code:

    const record = (function (arg) {
      return {
        id: (function () {
          const numStr = arg.id.N;
          return numStr === undefined ? undefined : Number(numStr);
        })(),
        name: arg.name.S,
        tags: (function () {
          var m, r;
          m = new Map();
          r = arg['tags']?.M;
          for (const k in r) {
            m.set(k, r[k]?.S);
          }
          return m;
        })(),
      };
    })({
      id: {
        N: '12345',
      },
      name: {
        S: 'John Doe',
      },
      tags: {
        M: {
          foo: {
            S: 'bar',
          },
          buz: {
            S: 'qux',
          },
        },
      },
    });
    
    /*
     * This object is equal to the following:
     *
     *   {
     *     id: 12345,
     *     name: "John Doe",
     *     tags: {
     *       foo: { S: "bar" },
     *       buz: { S: "qux" },
     *     }
     *   }
     */

    How to use this transformer

    This plugin exports the functions that have the signature function dynamodbRecord<T extends object>(item: T, shouldLenientTypeCheck?: boolean): Record<keyof T, AttributeValue> and function fromDynamodbRecord<T extends object>(attrs: Record<string, AttributeValue>, shouldLenientTypeCheck?: boolean): T.

    These functions are the markers to indicate to the transformer to replace the function invocation with the generated code. Therefore, there are some restrictions:

    • Type parameter T is mandatory parameter (i.e. this mustn't be omitted). A transformer analyzes the type of the given T to collect the property information.
    • Type T must be class or interface. If it needs to do unmarshalling, this type T must be a derived type of the interface.

    Examples

    ttypescript

    ttypescript is a custom TypeScript compiler that triggers the specified transformers in the tsconfig.json.

    Please refer to the examples/ttypescript project directory and ttypescript official README for more details.

    Anyway, the important thing is specifying compilerOptions.plugins in tsconfig.json like the following:

    {
      "compilerOptions": {
        // ...
        "plugins": [
          { "transform": "@moznion/ts-dynamodb-attributes-transformer/transformer" }
        ]
      },
      // ...
    }

    ts-jest

    If you use ts-jest with this transformer, one of the easiest ways is using that with ttypescript toghether.

    It needs ttypescript configuration and additionally the jest configuration in jest.config.js like the below:

    module.exports = {
      // ...
      transform: {
        '^.+\\.tsx?$': [
          'ts-jest',
          {
            compiler: 'ttypescript',
          },
        ],
      },
      // ...
    };

    TypeScript types to DynamoDB types

    Please see also Supported data types and naming rules in Amazon DynamoDB for more details about the DynamoDB types.

    Scalar Types

    TypeScript DynamoDB
    number, BigInt N
    string S
    Uint8Array B
    boolean BOOL
    unknown NULL

    NOTE: if the TypeScript property has unknown type and the value is null then DynamoDB attribute becomes { NULL: true }. Else, that attribute value is { NULL: false }.

    Document Types

    TypeScript DynamoDB
    Set<string> SS
    Set<number>, Set<BigInt> NS
    Set<Uint8Array> BS
    List<$SCALAR_TYPE> L
    Map<string, $SCALAR_TYPE>, { [key: string]: $SCALAR_TYPE } M

    Options

    Lenient type checking (default: false)

    By default, if this plugin encounters unsupported types, it raises the error and halts the transformation.

    But if true value is given through the second argument of the function, it proceeds the transformation with ignoring the unsupported typed property even if it gets the unsupported types.

    Note

    This transformer plugin referred to the various things from kimamula/ts-transformer-keys

    Authors

    moznion (moznion@mail.moznion.net)

    Install

    npm i @moznion/ts-dynamodb-attributes-transformer

    DownloadsWeekly Downloads

    6

    Version

    0.2.0

    License

    MIT

    Unpacked Size

    90.5 kB

    Total Files

    25

    Last publish

    Collaborators

    • moznion