sam-goodwin / typesafe-dynamodb

TypeSafe type definitions for the AWS DynamoDB API
Apache License 2.0
205 stars 11 forks source link

feat: add type-safe mappings for marshall and unmarshall from util-dynamodb #19

Closed sam-goodwin closed 2 years ago

sam-goodwin commented 2 years ago

Fixes #18

Marshall a JS Object to an AttributeMap

A better type definition @aws-sdk/util-dynamodb's marshall function is provided which maintains the typing information of the value passed to it and also adapts the output type based on the input marshallOptions

Given an object literal:

const myObject = {
  key: "key",
  sort: 123,
  binary: new Uint16Array([1]),
  buffer: Buffer.from("buffer", "utf8"),
  optional: 456,
  list: ["hello", "world"],
  record: {
    key: "nested key",
    sort: 789,
  },
} as const;

Call the marshall function to convert it to an AttributeMap which maintains the exact structure in the type system:

import { marshall } from "typesafe-dynamodb/lib/marshall";
// marshall the above JS object to its corresponding AttributeMap
const marshalled = marshall(myObject)

// typing information is carried across exactly, including literal types
const marshalled: {
  readonly key: S<"key">;
  readonly sort: N<123>;
  readonly binary: B;
  readonly buffer: B;
  readonly optional: N<456>;
  readonly list: L<readonly [S<"hello">, S<"world">]>;
  readonly record: M<...>;
}

Unmarshall an AttributeMap back to a JS Object

A better type definition @aws-sdk/util-dynamodb's unmarshall function is provided which maintains the typing information of the value passed to it and also adapts the output type based on the input unmarshallOptions.

import { unmarshall } from "typesafe-dynamodb/lib/marshall";

// unmarshall the AttributeMap back into the original object
const unmarshalled = unmarshall(marshalled);

// it maintains the literal typing information (as much as possible)
const unmarshalled: {
  readonly key: "key";
  readonly sort: 123;
  readonly binary: NativeBinaryAttribute;
  readonly buffer: NativeBinaryAttribute;
  readonly optional: 456;
  readonly list: readonly [...];
  readonly record: Unmarshall<...>;
}

If you specify {wrapNumbers: true}, then all number types will be wrapped as { value: string }:

const unmarshalled = unmarshall(marshalled, {
  wrapNumbers: true,
});

// numbers are wrapped in { value: string } because of `wrappedNumbers: true`
unmarshalled.sort.value; // string

// it maintains the literal typing information (as much as possible)
const unmarshalled: {
  readonly key: "key";
  // notice how the number is wrapped in the `NumberValue` type?
  // this is because `wrapNumbers: true`
  readonly sort: NumberValue<123>;
  readonly binary: NativeBinaryAttribute;
  readonly buffer: NativeBinaryAttribute;
  readonly optional: NumberValue<...>;
  readonly list: readonly [...];
  readonly record: Unmarshall<...>;
};