@ikonintegration/ikapi 5.0.0-alpha1 → 5.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +89 -99
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/package-lock.json +838 -823
  6. package/dist/package.json +30 -29
  7. package/dist/src/BaseEvent/DynamoTransaction.d.ts +50 -0
  8. package/dist/src/BaseEvent/DynamoTransaction.js +23 -0
  9. package/dist/src/BaseEvent/DynamoTransaction.js.map +1 -1
  10. package/dist/src/BaseEvent/Launchable/ContainerExec.d.ts +42 -0
  11. package/dist/src/BaseEvent/Launchable/ContainerExec.js +102 -0
  12. package/dist/src/BaseEvent/Launchable/ContainerExec.js.map +1 -0
  13. package/dist/src/BaseEvent/Launchable/index.d.ts +34 -0
  14. package/dist/src/BaseEvent/Launchable/index.js +43 -0
  15. package/dist/src/BaseEvent/Launchable/index.js.map +1 -0
  16. package/dist/src/BaseEvent/Launchable/types.d.ts +10 -0
  17. package/dist/src/BaseEvent/Launchable/types.js +2 -0
  18. package/dist/src/BaseEvent/Launchable/types.js.map +1 -0
  19. package/dist/src/Config/EnvironmentVar.js.map +1 -1
  20. package/dist/src/Logger/Logger.js +4 -2
  21. package/dist/src/Logger/Logger.js.map +1 -1
  22. package/dist/src/Server/lib/Server.js +1 -1
  23. package/dist/src/Server/lib/Server.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -0
  25. package/index.ts +2 -0
  26. package/package.json +30 -29
  27. package/src/BaseEvent/DynamoTransaction.ts +50 -0
  28. package/src/BaseEvent/Launchable/ContainerExec.ts +96 -0
  29. package/src/BaseEvent/Launchable/index.ts +53 -0
  30. package/src/BaseEvent/Launchable/types.ts +15 -0
  31. package/src/Config/EnvironmentVar.ts +1 -6
  32. package/src/Logger/Logger.ts +5 -2
  33. package/src/Server/lib/Server.ts +1 -1
  34. package/taskfile.yml +65 -0
  35. package/tests/Utils/Utils.test.ts +2 -3
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
2
  "name": "@ikonintegration/ikapi",
3
- "version": "5.0.0-alpha1",
3
+ "version": "5.0.1-alpha.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
8
8
  "scripts": {
9
- "build": "tsc --build",
10
- "clean": "tsc --build --clean",
11
- "lint-fix": "eslint . --fix",
12
- "lint": "eslint .",
13
- "docs": "npx typedoc --tsconfig tsconfig.json index.ts src/**/* --includeVersion --entryPointStrategy expand",
14
- "test": "npm run test-ts && npm run test-js",
15
- "test-ts": "jest --coverage --silent --runInBand --logHeapUsage && cat coverage/coverage.txt",
16
- "test-js": "rm -rf ./smoke-tests; tsc -p tsconfig.smoke.json && cd smoke-tests && npm i && NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --config=./jest.smoke.config.js --coverage --silent --runInBand --logHeapUsage && cat coverage/coverage.txt",
17
- "test-dev": "jest --coverage --runInBand && cat coverage/coverage.txt"
9
+ "build": "task build",
10
+ "clean": "task clean",
11
+ "lint-fix": "task lint-fix",
12
+ "lint": "task lint",
13
+ "docs": "task docs",
14
+ "test": "task test",
15
+ "test-ts": "task test-ts",
16
+ "test-js": "task test-js",
17
+ "test-dev": "task test-dev"
18
18
  },
19
19
  "author": "",
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
- "@aws-sdk/client-dynamodb": "^3.574.0",
23
- "@aws-sdk/client-kms": "^3.556.0",
24
- "@aws-sdk/client-secrets-manager": "^3.576.0",
25
- "@aws-sdk/client-ses": "^3.576.0",
26
- "@aws-sdk/client-sns": "^3.576.0",
27
- "@aws-sdk/client-ssm": "^3.576.0",
28
- "@aws-sdk/credential-provider-node": "^3.576.0",
29
- "@aws-sdk/util-dynamodb": "^3.619.0",
30
- "@smithy/node-http-handler": "^3.0.0",
22
+ "@aws-sdk/client-dynamodb": "^3.650.0",
23
+ "@aws-sdk/client-kms": "^3.650.0",
24
+ "@aws-sdk/client-secrets-manager": "^3.650.0",
25
+ "@aws-sdk/client-ses": "^3.650.0",
26
+ "@aws-sdk/client-sns": "^3.650.0",
27
+ "@aws-sdk/client-ssm": "^3.650.0",
28
+ "@aws-sdk/credential-provider-node": "^3.650.0",
29
+ "@aws-sdk/util-dynamodb": "^3.650.0",
30
+ "@smithy/node-http-handler": "^3.2.0",
31
31
  "@types/email-templates": "^10.0.1",
32
32
  "@types/nodemailer": "^6.4.10",
33
33
  "@types/object-hash": "^3.0.4",
@@ -36,29 +36,30 @@
36
36
  "cuid": "^3.0.0",
37
37
  "dotenv": "^16.4.1",
38
38
  "email-templates": "^12.0.1",
39
- "express": "^4.18.2",
39
+ "express": "^4.21.0",
40
40
  "json-stringify-safe": "^5.0.1",
41
41
  "kysely": "^0.27.2",
42
42
  "node-cache": "^5.1.2",
43
43
  "object-hash": "^3.0.0",
44
44
  "parse-duration": "^1.1.0",
45
- "path-to-regexp": "^7.1.0",
45
+ "path-to-regexp": "^8.1.0",
46
46
  "pg": "^8.11.3",
47
- "redis": "^4.6.13",
47
+ "redis": "^4.7.0",
48
48
  "sha1": "^1.1.1",
49
49
  "stack-trace": "0.0.10",
50
50
  "zod": "^3.23.4"
51
51
  },
52
52
  "devDependencies": {
53
+ "@go-task/cli": "^3.39.0",
53
54
  "@jest/globals": "^29.7.0",
54
- "@types/aws-lambda": "^8.10.137",
55
- "@types/chai": "^4.3.9",
55
+ "@types/aws-lambda": "^8.10.145",
56
+ "@types/chai": "^4.3.19",
56
57
  "@types/cors": "^2.8.15",
57
58
  "@types/express": "^4.17.19",
58
- "@types/jest": "^29.5.12",
59
+ "@types/jest": "^29.5.13",
59
60
  "@types/node": "^20.12.7",
60
61
  "@types/path-to-regexp": "^1.7.0",
61
- "@types/pg": "^8.11.5",
62
+ "@types/pg": "^8.11.9",
62
63
  "@types/supertest": "^6.0.2",
63
64
  "@typescript-eslint/eslint-plugin": "^7.7.1",
64
65
  "@typescript-eslint/parser": "^7.7.1",
@@ -67,15 +68,15 @@
67
68
  "chai": "^4.4.1",
68
69
  "eslint": "^8.48.0",
69
70
  "eslint-config-prettier": "^9.0.0",
70
- "eslint-plugin-import": "^2.28.1",
71
+ "eslint-plugin-import": "^2.30.0",
71
72
  "eslint-plugin-prettier": "^5.0.0",
72
73
  "eslint-plugin-require-extensions": "^0.1.3",
73
74
  "jest": "^29.7.0",
74
75
  "jest-junit": "^16.0.0",
75
76
  "prettier": "^3.2.5",
76
77
  "supertest": "^7.0.0",
77
- "ts-jest": "^29.1.1",
78
+ "ts-jest": "^29.2.5",
78
79
  "ts-node": "^10.9.1",
79
- "typescript": "^5.4.5"
80
+ "typescript": "^5.6.2"
80
81
  }
81
82
  }
@@ -10,6 +10,13 @@ import Response, { ResponseErrorType } from '../API/Response.js'
10
10
  import Globals from '../Globals.js'
11
11
  import Utils from '../Util/Utils.js'
12
12
 
13
+ /**
14
+ * Interface representing a DynamoDB record with marshalled data.
15
+ * Extends the DynamoDBRecord interface.
16
+ * @property {object} Keys - The keys of the record.
17
+ * @property {object} OldImage - The old image of the record.
18
+ * @property {object} NewImage - The new image of the record.
19
+ */
13
20
  export interface DynamoDBMarshalledRecord extends DynamoDBRecord {
14
21
  marshalled: {
15
22
  Keys?: object
@@ -18,6 +25,12 @@ export interface DynamoDBMarshalledRecord extends DynamoDBRecord {
18
25
  }
19
26
  }
20
27
 
28
+ /**
29
+ * Defines a type for executing a transaction on DynamoDB.
30
+ * @param {Transaction<null, ResponseInnerType | ResponseErrorType, DynamoDBBatchResponse | null>} transaction - The transaction to execute.
31
+ * @param {DynamoDBMarshalledRecord} recordContent - The content of the DynamoDB record.
32
+ * @returns A promise that resolves to a response or a DynamoDB batch response.
33
+ */
21
34
  export type DynamoTransactionExecution<ResponseInnerType> = (
22
35
  transaction: Transaction<
23
36
  null,
@@ -27,15 +40,41 @@ export type DynamoTransactionExecution<ResponseInnerType> = (
27
40
  recordContent: DynamoDBMarshalledRecord
28
41
  ) => Promise<Response<ResponseInnerType | ResponseErrorType> | DynamoDBBatchResponse | null>
29
42
 
43
+ /**
44
+ * Represents a DynamoDB transaction handler that processes events from a DynamoDB stream.
45
+ * @template ResponseInnerType - The inner type of the response.
46
+ */
30
47
  export default class DynamoTransaction<ResponseInnerType> {
48
+ /**
49
+ * A boolean flag indicating whether failures are allowed.
50
+ */
31
51
  private readonly allowFailure: boolean
32
52
 
53
+ /**
54
+ * Readonly property that holds the transaction configuration.
55
+ */
33
56
  private readonly config: TransactionConfig
34
57
 
58
+ /**
59
+ * The context object that provides information about the current execution context.
60
+ * @type {Context}
61
+ */
35
62
  private readonly context: Context
36
63
 
64
+ /**
65
+ * Represents an event from a DynamoDB stream.
66
+ * @type {DynamoDBStreamEvent}
67
+ */
37
68
  private readonly event: DynamoDBStreamEvent
38
69
 
70
+ /**
71
+ * Constructor for a TransactionHandler object.
72
+ * @param {DynamoDBStreamEvent} event - The DynamoDB stream event that triggered the transaction.
73
+ * @param {Context} context - The AWS Lambda context object.
74
+ * @param {TransactionConfig} [config] - Optional configuration for the transaction.
75
+ * @param {boolean} [allowFailure] - Flag to indicate whether to allow transaction failure.
76
+ * @returns None
77
+ */
39
78
  constructor(
40
79
  event: DynamoDBStreamEvent,
41
80
  context: Context,
@@ -48,6 +87,12 @@ export default class DynamoTransaction<ResponseInnerType> {
48
87
  this.allowFailure = !!allowFailure
49
88
  }
50
89
 
90
+ /**
91
+ * Processes the event execution and returns a response based on the outcome.
92
+ * @param {DynamoTransactionExecution<ResponseInnerType>} execution - The execution object to process.
93
+ * @returns {Promise<Response<ResponseErrorType | null> | null | DynamoDBBatchResponse>} A promise that resolves to a response object or null.
94
+ * @throws {Error} If the response code is not within the success range and failure is not allowed.
95
+ */
51
96
  public async processEvent(
52
97
  execution: DynamoTransactionExecution<ResponseInnerType>
53
98
  ): Promise<Response<ResponseErrorType | null> | null | DynamoDBBatchResponse> {
@@ -63,6 +108,11 @@ export default class DynamoTransaction<ResponseInnerType> {
63
108
  return null
64
109
  }
65
110
 
111
+ /**
112
+ * Processes a raw event by executing a transaction on each record in the event.
113
+ * @param {DynamoTransactionExecution<ResponseInnerType>} execution - The transaction execution function.
114
+ * @returns {Promise<Response<ResponseErrorType | any> | null | DynamoDBBatchResponse>} A promise that resolves to a response object, null, or a DynamoDB batch response.
115
+ */
66
116
  private async processRawEvent(
67
117
  execution: DynamoTransactionExecution<ResponseInnerType>
68
118
  ): Promise<Response<ResponseErrorType | any> | null | DynamoDBBatchResponse> {
@@ -0,0 +1,96 @@
1
+ import { Context } from 'aws-lambda'
2
+
3
+ import { LaunchableHandlerType } from './types.js'
4
+ import Response from '../../API/Response.js'
5
+
6
+ /**
7
+ * Represents a ContainerExecution class that handles the execution of a launchable handler.
8
+ * @template Input - The type of input expected by the launchable handler.
9
+ */
10
+ export default class ContainerExecution<Input> {
11
+ /**
12
+ * A private readonly property that holds a handler of LaunchableHandlerType with a given Input type.
13
+ */
14
+ private readonly handler: LaunchableHandlerType<Input>
15
+
16
+ /**
17
+ * Constructor for creating an instance of a class with a LaunchableHandlerType input.
18
+ * @param {LaunchableHandlerType<Input>} handler - The handler for the launchable type.
19
+ * @returns None
20
+ */
21
+ constructor(handler: LaunchableHandlerType<Input>) {
22
+ this.handler = handler
23
+ }
24
+
25
+ /**
26
+ * Executes the function asynchronously, invoking the specified inputs and handling the response accordingly.
27
+ * If the response code is 200, the process exits with code 0; otherwise, it exits with code 1.
28
+ * @returns None
29
+ */
30
+ public async execute() {
31
+ try {
32
+ // Invoke
33
+ const resp = await this.invoke(this.getInputs())
34
+ // Return
35
+ console.debug(resp)
36
+ if (!resp.err && resp.data && resp.data.getCode() == 200) process.exit(0)
37
+ else process.exit(1)
38
+ } catch (e) {
39
+ console.error(e)
40
+ process.exit(1)
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Retrieves inputs from the process arguments and parses them as JSON.
46
+ * @returns {any} The parsed input object from process arguments.
47
+ */
48
+ private getInputs() {
49
+ let input: any = {}
50
+ console.debug(process.argv)
51
+ try {
52
+ input = JSON.parse(process.argv[2])
53
+ } catch (e) {
54
+ console.log('[IKApi Launchable Runtime] - Error while decoding process arguments!', e)
55
+ }
56
+ return input
57
+ }
58
+
59
+ /**
60
+ * Builds a context object for AWS Lambda function.
61
+ * @param {CallableFunction} callback - The callback function to be executed.
62
+ * @returns {Context} A context object with AWS Lambda properties.
63
+ */
64
+ private buildContext(callback: CallableFunction): Context {
65
+ return {
66
+ awsRequestId: `${Date.now()}-${Date.now()}`,
67
+ callbackWaitsForEmptyEventLoop: true,
68
+ getRemainingTimeInMillis: () => 0,
69
+ done: (err, data) => callback(err, data),
70
+ fail: err => callback(err),
71
+ succeed: res => callback(null, res),
72
+ } as any as Context
73
+ }
74
+
75
+ /**
76
+ * Asynchronously invokes a function with the given input and returns a promise that resolves
77
+ * with an object containing an error message or data response.
78
+ * @param {any} input - The input to be passed to the function.
79
+ * @returns {Promise<{ err?: string; data?: Response<any> }>} A promise that resolves with an object
80
+ * containing an error message or data response.
81
+ */
82
+ private async invoke(input: any) {
83
+ // eslint-disable-next-line no-async-promise-executor
84
+ return new Promise<{ err?: string; data?: Response<any> }>(async (resolve, reject) => {
85
+ try {
86
+ const context = this.buildContext((err, data) => {
87
+ resolve({ err, data })
88
+ })
89
+ const resp = await this.handler(input, context)
90
+ if (resp) resolve({ data: resp })
91
+ } catch (e) {
92
+ reject(e)
93
+ }
94
+ })
95
+ }
96
+ }
@@ -0,0 +1,53 @@
1
+ import ContainerExecution from './ContainerExec.js'
2
+ import { LaunchableHandlerType } from './types.js'
3
+ import Utils from '../../Util/Utils.js'
4
+
5
+ /**
6
+ * Represents a Launchable class that can be used to handle launchable items.
7
+ * @template Input - The type of input expected by the LaunchableHandlerType.
8
+ */
9
+ export default class Launchable<Input = any> {
10
+ /**
11
+ * A private readonly property that holds a handler for launching with a specific input type.
12
+ * @type {LaunchableHandlerType<Input>}
13
+ */
14
+ private readonly handler: LaunchableHandlerType<Input>
15
+
16
+ /**
17
+ * Constructor for creating an instance of a class with a LaunchableHandlerType input.
18
+ * @param {LaunchableHandlerType<Input>} handler - The handler for the launchable type.
19
+ * @returns None
20
+ */
21
+ constructor(handler: LaunchableHandlerType<Input>) {
22
+ this.handler = handler
23
+ }
24
+
25
+ /**
26
+ * Returns the export function based on whether the object is a container or not.
27
+ * If it is a container, it returns the container export function, otherwise it returns the handler function.
28
+ * @returns {CallableFunction} The export function to be used.
29
+ */
30
+ public getExport(): CallableFunction {
31
+ return this.isContainer() ? this.getContainerExport() : this.handler
32
+ }
33
+
34
+ /**
35
+ * Checks if the current element is a container by calling the isHybridlessContainer method from the Utils class.
36
+ * @returns {boolean} - true if the element is a container, false otherwise.
37
+ */
38
+ private isContainer(): boolean {
39
+ return Utils.isHybridlessContainer()
40
+ }
41
+
42
+ /**
43
+ * Returns a callable function that executes a ContainerExecution instance and returns a promise.
44
+ * @returns {CallableFunction} A function that when called, executes a ContainerExecution instance.
45
+ */
46
+ private getContainerExport(): CallableFunction {
47
+ // emulate lambda invocation
48
+ const ce = new ContainerExecution(this.handler)
49
+ ce.execute().then(() => {})
50
+ // return empty function, we are all done
51
+ return () => {}
52
+ }
53
+ }
@@ -0,0 +1,15 @@
1
+ import { Context } from 'aws-lambda'
2
+
3
+ import Response from '../../API/Response.js'
4
+
5
+ /**
6
+ * Defines a type for a launchable handler that takes an input event and context,
7
+ * and returns a promise of a response.
8
+ * @param {Input} event - The input event for the handler.
9
+ * @param {Context} context - The context for the handler.
10
+ * @returns {Promise<Response<any>>} A promise that resolves to a response.
11
+ */
12
+ export type LaunchableHandlerType<Input> = (
13
+ event: Input,
14
+ context: Context
15
+ ) => Promise<Response<any>>
@@ -56,12 +56,7 @@ export default class EnvironmentVar<T> {
56
56
  * @param {boolean} [optional] - Indicates whether the environment variable is optional.
57
57
  * @param {string} [paramPrefix] - The prefix to be added to the environment variable name.
58
58
  */
59
- constructor(
60
- paramName: string,
61
- type: EnvironmentType,
62
- optional = true,
63
- paramPrefix: string
64
- ) {
59
+ constructor(paramName: string, type: EnvironmentType, optional = true, paramPrefix: string) {
65
60
  this.paramName = paramName
66
61
  this.type = type
67
62
  this.optional = !!optional
@@ -38,7 +38,10 @@ const DEFAULT_LOG_FUNCTION = PURE_CONSOLE.log.bind(PURE_CONSOLE)
38
38
  * @type {string[]} blacklist - An array of strings to be converted to lowercase and used as a blacklist.
39
39
  * @returns {string[]} - An array of lowercase strings representing the blacklist.
40
40
  */
41
- const blacklist = ['password', 'token', 'accounts'].map(s => s.toLowerCase())
41
+ const blacklist = ['password', 'token', 'accounts', 'rawBody'].map(s => s.toLowerCase())
42
+
43
+ /* Used to have a clean log */
44
+ const defaultSuppress = ['rawBody'].map(s => s.toLowerCase())
42
45
 
43
46
  /**
44
47
  * Configuration options for the logger.
@@ -107,7 +110,7 @@ export default class Logger {
107
110
  ? Array.isArray(this.config.sensitiveFilteringKeywords)
108
111
  ? this.config.sensitiveFilteringKeywords
109
112
  : blacklist
110
- : []
113
+ : defaultSuppress
111
114
  //
112
115
  this.setupBindings()
113
116
  //
@@ -103,7 +103,7 @@ export default class Server {
103
103
  private parsePathParams(req: Request<any, any, any>, routePath: string) {
104
104
  const path = req.getPath()
105
105
  const regex = pathToRegexp(routePath)
106
- const result = regex.exec(path)
106
+ const result = regex.regexp.exec(path)
107
107
  if (result) req.setFixedPathParams(regex.keys, result)
108
108
  }
109
109
  }
package/taskfile.yml ADDED
@@ -0,0 +1,65 @@
1
+ version: '3'
2
+
3
+ vars:
4
+ test_node_options: '--experimental-vm-modules'
5
+ coverage_file: 'coverage/coverage.txt'
6
+
7
+ tasks:
8
+ build:
9
+ desc: Build the project
10
+ cmds:
11
+ - tsc --build
12
+
13
+ clean:
14
+ desc: Clean the build artifacts
15
+ cmds:
16
+ - tsc --build --clean
17
+
18
+ lint:
19
+ desc: Run ESLint
20
+ cmds:
21
+ - eslint .
22
+
23
+ lint-fix:
24
+ desc: Run ESLint and fix issues
25
+ cmds:
26
+ - eslint . --fix
27
+
28
+ docs:
29
+ desc: Generate documentation
30
+ cmds:
31
+ - npx typedoc --tsconfig tsconfig.json index.ts src/**/* --includeVersion --entryPointStrategy expand
32
+
33
+ test:
34
+ desc: Run all tests
35
+ cmds:
36
+ - task test-ts
37
+ - task test-js
38
+
39
+ test-ts:
40
+ desc: Run TypeScript tests
41
+ cmds:
42
+ - jest --workerIdleMemoryLimit=1024 --coverage --silent --runInBand --logHeapUsage
43
+ - cat {{.coverage_file}}
44
+
45
+ test-js:
46
+ desc: Run JavaScript smoke tests
47
+ cmds:
48
+ - rm -rf ./smoke-tests
49
+ - tsc -p tsconfig.smoke.json
50
+ - task test-js-inner
51
+
52
+ test-js-inner:
53
+ desc: Run smoke tests in the smoke-tests directory
54
+ dir: smoke-tests
55
+ cmds:
56
+ - cp -R ../node_modules node_modules
57
+ - npm i
58
+ - NODE_OPTIONS="$NODE_OPTIONS {{.test_node_options}}" jest --workerIdleMemoryLimit=1024 --config=./jest.smoke.config.js --coverage --silent --runInBand --logHeapUsage
59
+ - cat ../{{.coverage_file}}
60
+
61
+ test-dev:
62
+ desc: Run tests in development mode
63
+ cmds:
64
+ - jest --coverage --runInBand
65
+ - cat {{.coverage_file}}
@@ -164,7 +164,6 @@ describe('caseInsensitiveObjectForKey', () => {
164
164
  })
165
165
  })
166
166
 
167
-
168
167
  describe('ddbMarshall', () => {
169
168
  test('Marshalling of a simple object', () => {
170
169
  const input = { key: 'value' }
@@ -205,7 +204,7 @@ describe('ddbUnmarshall', () => {
205
204
  })
206
205
 
207
206
  test('Unmarshalling of a DynamoDB array', () => {
208
- const input = { array: { L: [{ N: '1' }, { N: '2' }, { N: '3' }] }}
207
+ const input = { array: { L: [{ N: '1' }, { N: '2' }, { N: '3' }] } }
209
208
  const output = Utils.ddbUnmarshall(input)
210
209
  expect(output.array).toEqual([1, 2, 3])
211
210
  })
@@ -223,7 +222,7 @@ describe('ddbUnmarshall', () => {
223
222
  })
224
223
 
225
224
  test('Unmarshalling of a primitive value', () => {
226
- const input = { key: { N: '42' }}
225
+ const input = { key: { N: '42' } }
227
226
  const output = Utils.ddbUnmarshall(input)
228
227
  expect(output.key).toEqual(42)
229
228
  })