Posted at

AWS SDK (Node.js) + TypeScriptでAmazon PersonalizeのPutEventが実行できないバグとそのワークアラウンド

SDK側にレポートしてるんですが音沙汰ないのでワークアラウンド含めてここに記録。

https://github.com/aws/aws-sdk-js/pull/2821


要約


  • AWS SDK(Node.js)のAmazon Personalize PutEventで型定義が間違っている


  • EventPropertiesJSONの型をstringから{[key: string]: any}に変更すれば解決する

  • Pull Requestを出してレポートしているが反応がない

  • マージされるまではanyasで回避推奨


環境

$ node -v

v10.16.0

$ npm -v
6.9.0

$ npm view aws-sdk version
2.546.0

$ npm view typescript version
3.6.4

$ cat tsconfig.json
$ cat tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"declaration": true,
"outDir": "./dist/",
"rootDir": "./libs/",
"lib": [ "esnext", "dom"],
"strict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitThis": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
"strictNullChecks": true,
"pretty": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noEmitHelpers": true,
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"libs/"
],
"exclude": [
"__tests__",
"docs",
"node_modules",
"./dist",
"./index.js",
"examples/"
]


現象

AWS SDK(Node.js)を使ってTypeScriptでAmazon PersonalizeにputEventしようとすると、TypeScriptのエラーが発生する。


Code Sample1: 型定義に従って記述する

import {

PersonalizeEvents,
} from 'aws-sdk'
import { v4 } from 'uuid'
import moment from 'moment'

const trackEvent = async () => {
const client = new PersonalizeEvents({
region: 'ap-northeast-1'
})
const params: PersonalizeEvents.Types.PutEventsRequest = {
trackingId: 'xxx-xxx-xxx-xxx',
userId: '1',
sessionId: v4(),
eventList: [{
eventId: v4(),
sentAt: moment().toDate(),
eventType: 'rating',
properties: JSON.stringify({
itemId: '1',
eventValue: 1.0
})
}]
}
console.log(params)
const result = await client.putEvents(params).promise()
console.log(result)
return
}
trackEvent()


Code Sample1はAPIバリデーションエラーが発生する

$  ./node_modules/.bin/ts-node libs/index.ts 

{ trackingId: '52c88f57-7d7f-4941-8396-4c6a7434ec0d',
userId: '1',
sessionId: '4d58f851-6c8e-4ae8-8038-56f86f5f40ee',
eventList:
[ { eventId: 'd6ed382a-999e-4728-9895-98612a959506',
sentAt: 2019-08-25T14:57:30.307Z,
eventType: 'rating',
properties: '{"itemId":"1","eventValue":1}' } ] }

{ InvalidInputException: Error parsing properties json
at Object.extractError (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/protocol/json.js:51:27)
at Request.extractError (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/protocol/rest_json.js:55:8)
at Request.callListeners (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/request.js:683:14)
at Request.transition (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/Users/develop/sandbox/aws-playground/node_modules/aws-sdk/lib/request.js:685:12)
message: 'Error parsing properties json',
code: 'InvalidInputException',
time: 2019-08-25T14:57:30.537Z,
requestId: 'd6766b7e-69b0-4889-83cf-18171e509b4a',
statusCode: 400,
retryable: false,
retryDelay: 65.34243647640349 }


Code Sample2: プロパティをObjectで渡す

import {

PersonalizeEvents,
} from 'aws-sdk'
import { v4 } from 'uuid'
import moment from 'moment'

const trackEvent = async () => {
const client = new PersonalizeEvents({
region: 'ap-northeast-1'
})
const params: PersonalizeEvents.Types.PutEventsRequest = {
trackingId: 'xxx-xxx-xxx-xxx',
userId: '1',
sessionId: v4(),
eventList: [{
eventId: v4(),
sentAt: moment().toDate(),
eventType: 'rating',
properties: {
itemId: '1',
eventValue: 1.0
}
}]
}
console.log(params)
const result = await client.putEvents(params).promise()
console.log(result)
return
}
trackEvent()


Code Sample2はTypeScriptの型エラーが発生する

$  ./node_modules/.bin/ts-node libs/index.ts 

/Users/develop/sandbox//node_modules/ts-node/src/index.ts:245
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
libs/index.ts:34:7 - error TS2322: Type '{ itemId: string; eventValue: number; }' is not assignable to type 'string'.

34 properties: {
~~~~~~~~~~

node_modules/aws-sdk/clients/personalizeevents.d.ts:37:5
37 properties: EventPropertiesJSON;
~~~~~~~~~~
The expected type comes from property 'properties' which is declared here on type 'Event'


Code Sample3: Code Sample2をanyで強行突破

import {

PersonalizeEvents,
} from 'aws-sdk'
import { v4 } from 'uuid'
import moment from 'moment'

const trackEvent = async () => {
const client = new PersonalizeEvents({
region: 'ap-northeast-1'
})
const params: PersonalizeEvents.Types.PutEventsRequest = {
trackingId: 'xxx-xxx-xxx-xxx',
userId: '1',
sessionId: v4(),
eventList: [{
eventId: v4(),
sentAt: moment().toDate(),
eventType: 'rating',
properties: {
itemId: '1',
eventValue: 1.0
} as any as PersonalizeEvents.Types.EventPropertiesJSON
}]
}
console.log(params)
const result = await client.putEvents(params).promise()
console.log(result)
return
}
trackEvent()


Code Sample3はAPIバリデーションを通過する

$  ./node_modules/.bin/ts-node libs/index.ts 

{ trackingId: '52c88f57-7d7f-4941-8396-4c6a7434ec0d',
userId: '1',
sessionId: 'dcaf9abd-db3b-4793-a43e-6bb9ce6a1f9a',
eventList:
[ { eventId: 'b938051a-bf9c-4e3c-8c9a-b0453bee46e5',
sentAt: 2019-08-25T14:59:26.639Z,
eventType: 'rating',
properties: [Object] } ] }
{}


問題

eventList[].propertiesの型がTypeScriptの定義とAPI側の想定とで不一致。

APIはObjectを想定しているが、TypeScriptの定義ファイルではstringJSON.stringifyしたものを想定している。


ワークアラウンド

https://github.com/aws/aws-sdk-js/pull/2821/files

のように型ファイルを書き換えれば一応動きます。

ただしnode_modules配下を直接触るのでnpm installやyarn addで消える可能性が非常に高いです。

現状以下のようにasを使ってTypeScriptのチェックを誤魔化す方法が無難そうです。

const properties = {

itemId: '1',
eventValue: 1.0
} as any as PersonalizeEvents.Types.EventPropertiesJSON


AWS Amplify SDKへの懸念

テストコードをみると、putEventsメソッドをモックしていることから、AWS Amplify SDKのAmazon Personalizeインテグレーションも内部で同じメソッドを使用している様子です。

https://github.com/aws-amplify/amplify-js/blob/d96eece301bf28a59ae320a83a18b8fc8f091e10/packages/analytics/__tests__/Providers/AmazonPersonalizeProvider-unit-test.ts

jest.mock('aws-sdk/clients/personalizeevents', () => {

const PersonalizeEvents = () => {
var personalizeEvents = null;
return personalizeEvents;
}

PersonalizeEvents.prototype.putEvents = (params, callback) => {
callback(null, 'data');
}

return PersonalizeEvents;
});

こちらは試してないですが、もしAmplify SDK + TypeScriptでAmazon PersonalizeのputEventやろうとしてエラー踏んだ場合は、こいつを疑ってください。