Skip to content

Commit 8481106

Browse files
authored
Merge pull request #16 from moznion/unmarshaller
Unmarshaller support
2 parents 74764dd + 613583e commit 8481106

27 files changed

+1413
-2475
lines changed

README.md

Lines changed: 141 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,56 @@
11
# ts-dynamodb-attributes-transformer [![.github/workflows/check.yml](https://github.com/moznion/ts-dynamodb-attributes-transformer/actions/workflows/check.yml/badge.svg)](https://github.com/moznion/ts-dynamodb-attributes-transformer/actions/workflows/check.yml) [![npm version](https://badge.fury.io/js/@moznion%2Fts-dynamodb-attributes-transformer.svg)](https://badge.fury.io/js/@moznion%2Fts-dynamodb-attributes-transformer)
22

3-
Code transformer plugin powered by [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) that transforms TypeScript object to Amazon DynamoDB attributes.
3+
Code transformer plugin powered by [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) that transforms TypeScript object from/to Amazon DynamoDB attributes.
44

5-
## How it works
5+
## Description
66

7-
This plugin replaces the TypeScript function invocation of `dynamodbRecord<T>(obj: T)` 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. In short, this plugin generates the DynamoDB attribute code for every property of type `T`.
7+
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`.
8+
9+
10+
### `dynamodbRecord<T>(obj: T): Record<keyof T, AttributeValue>`
11+
12+
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.
813

914
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.
1015

11-
Manual making the translation layer between the object and DynamoDB's Record is no longer needed!
16+
### `fromDynamodbRecord<T>(attrs: Record<string, AttributeValue>): T`
17+
18+
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.
1219

1320
## Motivations
1421

1522
- To do automatic generation of the [DynamoDB attribute data type](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html) code that is recognizable by [aws-sdk-js-v3](https://github.com/aws/aws-sdk-js-v3), with type safety.
23+
- Manual making the translation layer between the object and DynamoDB's Record is no longer needed!
1624
- 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.
1725

1826
### Benchmark
1927

2028
The benchmark result between this project and [kayomarz/dynamodb-data-types](https://github.com/kayomarz/dynamodb-data-types) is the following:
2129

30+
marshalling:
31+
2232
```
2333
node version: v16.17.0
24-
dynamodb-data-types marshalling x 3,475,450 ops/sec ±0.45% (96 runs sampled)
25-
ts-dynamodb-attributes-transformer marshalling x 13,405,409 ops/sec ±0.43% (91 runs sampled)
34+
dynamodb-data-types marshalling x 3,845,247 ops/sec ±0.63% (90 runs sampled)
35+
ts-dynamodb-attributes-transformer marshalling x 13,614,974 ops/sec ±0.24% (100 runs sampled)
2636
Fastest is ts-dynamodb-attributes-transformer marshalling
2737
```
2838

39+
unmarshalling:
40+
41+
```
42+
node version: v16.17.0
43+
dynamodb-data-types unmarshalling x 1,800,718 ops/sec ±0.30% (96 runs sampled)
44+
ts-dynamodb-attributes-transformer unmarshalling x 3,493,272 ops/sec ±0.50% (98 runs sampled)
45+
Fastest is ts-dynamodb-attributes-transformer unmarshalling
46+
```
47+
2948
Please see also [benchmark](./examples/benchmark) project.
3049

3150
## Synopsis
3251

52+
### Marshalling into DynamoDB record from the Typescript Object
53+
3354
```ts
3455
import { AttributeValue } from '@aws-sdk/client-dynamodb';
3556
import { dynamodbRecord } from '@moznion/ts-dynamodb-attributes-transformer';
@@ -63,32 +84,32 @@ const record: Record<keyof User, AttributeValue> = dynamodbRecord<User>({
6384
Then this plugin transforms the above TypeScript code like the following JavaScript code:
6485

6586
```js
66-
const record = function (arg) {
67-
return {
68-
id: {
69-
N: arg.id.toString()
70-
},
71-
name: {
72-
S: arg.name
73-
},
74-
tags: {
75-
M: function () {
76-
var m;
77-
m = {}
78-
for (const kv of arg.tags) {
79-
m[kv[0]] = { S: kv[1] }
80-
}
81-
return m;
82-
}()
87+
const record = (function (arg) {
88+
return {
89+
id: {
90+
N: arg.id.toString()
91+
},
92+
name: {
93+
S: arg.name
94+
},
95+
tags: {
96+
M: (function () {
97+
var m;
98+
m = {}
99+
for (const kv of arg.tags) {
100+
m[kv[0]] = { S: kv[1] }
83101
}
84-
};
85-
}({
86-
id: 12345,
87-
name: 'John Doe',
88-
tags: new Map([
89-
['foo', 'bar'],
90-
['buz', 'qux'],
91-
]),
102+
return m;
103+
})()
104+
}
105+
};
106+
})({
107+
id: 12345,
108+
name: 'John Doe',
109+
tags: new Map([
110+
['foo', 'bar'],
111+
['buz', 'qux'],
112+
]),
92113
});
93114
/*
94115
* This record is equal to the following object:
@@ -106,14 +127,98 @@ const record = function (arg) {
106127
*/
107128
```
108129

130+
### Unmarshalling into TypeScript object from DynamoDB record
131+
132+
```ts
133+
import { fromDynamodbRecord } from '@moznion/ts-dynamodb-attributes-transformer';
134+
135+
interface User {
136+
readonly id?: number;
137+
readonly name?: string;
138+
readonly tags: Map<string, string>;
139+
}
140+
141+
const user: User = fromDynamodbRecord<User>({
142+
id: {
143+
N: '12345',
144+
},
145+
name: {
146+
S: 'John Doe',
147+
},
148+
tags: {
149+
M: {
150+
foo: {
151+
S: 'bar',
152+
},
153+
buz: {
154+
S: 'qux',
155+
},
156+
},
157+
},
158+
});
159+
```
160+
161+
Then this plugin transforms the above TypeScript code like the following JavaScript code:
162+
163+
```js
164+
const record = (function (arg) {
165+
return {
166+
id: (function () {
167+
const numStr = arg.id.N;
168+
return numStr === undefined ? undefined : Number(numStr);
169+
})(),
170+
name: arg.name.S,
171+
tags: (function () {
172+
var m, r;
173+
m = new Map();
174+
r = arg['tags']?.M;
175+
for (const k in r) {
176+
m.set(k, r[k]?.S);
177+
}
178+
return m;
179+
})(),
180+
};
181+
})({
182+
id: {
183+
N: '12345',
184+
},
185+
name: {
186+
S: 'John Doe',
187+
},
188+
tags: {
189+
M: {
190+
foo: {
191+
S: 'bar',
192+
},
193+
buz: {
194+
S: 'qux',
195+
},
196+
},
197+
},
198+
});
199+
200+
/*
201+
* This object is equal to the following:
202+
*
203+
* {
204+
* id: 12345,
205+
* name: "John Doe",
206+
* tags: {
207+
* foo: { S: "bar" },
208+
* buz: { S: "qux" },
209+
* }
210+
* }
211+
*/
212+
```
213+
109214
## How to use this transformer
110215
111-
This plugin exports a function that has the signature `dynamodbRecord<T extends object>(item: T): Record<keyof T, AttributeValue>`.
216+
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`.
112217

113-
This function is a marker to indicate to the transformer to replace this function invocation with the generated DynamoDB record code. Therefore, there are some restrictions:
218+
These functions are the markers to indicate to the transformer to replace the function invocation with the generated code. Therefore, there are some restrictions:
114219

115220
- 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.
116-
- Type `T` must be class or interface.
221+
- Type `T` must be class or interface. If it needs to do unmarshalling, this type `T` must be a derived type of the **interface**.
117222

118223
### Examples
119224

@@ -187,11 +292,11 @@ NOTE: if the TypeScript property has `unknown` type and the value is `null` then
187292
188293
## Options
189294
190-
### `TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK` env var (default: `<empty>`)
295+
### Lenient type checking (default: `false`)
191296
192297
By default, if this plugin encounters unsupported types, it raises the error and halts the transformation.
193298
194-
But if `TS_DYNAMODB_ATTR_TRANSFORMER_LENIENT_TYPE_CHECK` environment variable is not empty, it proceeds the transformation with ignoring the unsupported typed property even if it gets the unsupported types.
299+
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.
195300
196301
## Note
197302

examples/benchmark/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ npm run bench
1313

1414
```
1515
node version: v16.17.0
16-
dynamodb-data-types marshalling x 3,475,450 ops/sec ±0.45% (96 runs sampled)
17-
ts-dynamodb-attributes-transformer marshalling x 13,405,409 ops/sec ±0.43% (91 runs sampled)
16+
dynamodb-data-types marshalling x 3,845,247 ops/sec ±0.63% (90 runs sampled)
17+
ts-dynamodb-attributes-transformer marshalling x 13,614,974 ops/sec ±0.24% (100 runs sampled)
1818
Fastest is ts-dynamodb-attributes-transformer marshalling
19+
dynamodb-data-types unmarshalling x 1,800,718 ops/sec ±0.30% (96 runs sampled)
20+
ts-dynamodb-attributes-transformer unmarshalling x 3,493,272 ops/sec ±0.50% (98 runs sampled)
21+
Fastest is ts-dynamodb-attributes-transformer unmarshalling
1922
```
2023

examples/benchmark/dynamodb_data_types.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,29 @@ function toDynamoDBRecord() {
1515
AttributeValue.wrap(obj);
1616
}
1717

18-
module.exports = toDynamoDBRecord;
18+
function fromDynamoDBRecord() {
19+
AttributeValue.unwrap({
20+
id: {
21+
N: '123455',
22+
},
23+
name: {
24+
S: 'John Doe',
25+
},
26+
tags: {
27+
M: {
28+
foo: {
29+
S: 'bar',
30+
},
31+
buz: {
32+
S: 'qux',
33+
},
34+
},
35+
},
36+
flags: {
37+
SS: ['foo', 'bar'],
38+
},
39+
});
40+
}
41+
42+
module.exports.toDynamoDBRecord = toDynamoDBRecord;
43+
module.exports.fromDynamoDBRecord = fromDynamoDBRecord;

examples/benchmark/index.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const Benchmark = require('benchmark');
2-
const toDynamoDBRecordByDataTypes = require('./dynamodb_data_types');
2+
const toDynamoDBRecordByDataTypes = require('./dynamodb_data_types').toDynamoDBRecord;
3+
const fromDynamoDBRecordByDataTypes = require('./dynamodb_data_types').fromDynamoDBRecord;
34
const toDynamoDBRecordByTransformer = require('./using_transformer').toDynamoDBRecord;
5+
const fromDynamoDBRecordByTransformer = require('./using_transformer').fromDynamoDBRecord;
46

57
console.log(`node version: ${process.version}`);
68
new Benchmark.Suite()
@@ -16,4 +18,19 @@ new Benchmark.Suite()
1618
.on('complete', function () {
1719
console.log('Fastest is ' + this.filter('fastest').map('name'));
1820
})
19-
.run({ async: true });
21+
.run({ async: false });
22+
23+
new Benchmark.Suite()
24+
.add('dynamodb-data-types unmarshalling', function () {
25+
fromDynamoDBRecordByDataTypes();
26+
})
27+
.add('ts-dynamodb-attributes-transformer unmarshalling', function () {
28+
fromDynamoDBRecordByTransformer();
29+
})
30+
.on('cycle', function (event) {
31+
console.log(String(event.target));
32+
})
33+
.on('complete', function () {
34+
console.log('Fastest is ' + this.filter('fastest').map('name'));
35+
})
36+
.run({ async: false });
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare function toDynamoDBRecord(): void;

examples/benchmark/using_transformer.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dynamodbRecord } from '../../index';
1+
import { dynamodbRecord, fromDynamodbRecord } from '../../index';
22

33
interface Obj {
44
readonly id: number;
@@ -22,3 +22,27 @@ const obj: Obj = {
2222
export function toDynamoDBRecord() {
2323
dynamodbRecord<Obj>(obj);
2424
}
25+
26+
export function fromDynamoDBRecord() {
27+
fromDynamodbRecord<Obj>({
28+
id: {
29+
N: '123455',
30+
},
31+
name: {
32+
S: 'John Doe',
33+
},
34+
tags: {
35+
M: {
36+
foo: {
37+
S: 'bar',
38+
},
39+
buz: {
40+
S: 'qux',
41+
},
42+
},
43+
},
44+
flags: {
45+
SS: ['foo', 'bar'],
46+
},
47+
});
48+
}

examples/ttypescript/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dynamodbRecord } from '@moznion/ts-dynamodb-attributes-transformer';
1+
import { dynamodbRecord, fromDynamodbRecord } from '../../index';
22
import { AttributeValue } from '@aws-sdk/client-dynamodb';
33

44
interface User {
@@ -15,5 +15,7 @@ const record: Record<keyof User, AttributeValue> = dynamodbRecord<User>({
1515
['buz', 'qux'],
1616
]),
1717
});
18-
1918
console.log(JSON.stringify(record));
19+
20+
const user: User = fromDynamodbRecord<User>(record);
21+
console.log(user);

0 commit comments

Comments
 (0)