presidium 0.15.28 → 0.15.32

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.
package/KinesisStream.js CHANGED
@@ -29,11 +29,6 @@ const {
29
29
  * endpoint: string,
30
30
  * shardIteratorType: 'AT_SEQUENCE_NUMBER'|'AFTER_SEQUENCE_NUMBER'|'TRIM_HORIZON'|'LATEST'|'AT_TIMESTAMP',
31
31
  * timestamp: Date|string|number, // find events at date (requires shardIteratorType 'AT_TIMESTAMP')
32
- * startingSequenceNumber: string, // find events at data record (requires shardIteratorType 'AT_SEQUENCE_NUMBER' or 'AFTER_SEQUENCE_NUMBER')
33
- * shardFilterType: 'AFTER_SHARD_ID'|'AT_TRIM_HORIZON'|'FROM_TRIM_HORIZON'|'AT_LATEST'|'AT_TIMESTAMP'|'FROM_TIMESTAMP',
34
- * shardFilterShardId: string,
35
- * shardFilterTimestamp: Date|string|number,
36
- * streamCreationTimestamp: Date|string|number, // distinguishes streams of same name e.g. after deleting
37
32
  * }) -> KinesisStream
38
33
  * ```
39
34
  *
@@ -53,13 +48,8 @@ const KinesisStream = function (options) {
53
48
  this.shardUpdatePeriod = options.shardUpdatePeriod ?? 15000
54
49
  this.getRecordsInterval = options.getRecordsInterval ?? 1000
55
50
  this.shardIteratorType = options.shardIteratorType ?? 'LATEST'
56
- this.shardIteratorTimestamp = options.shardIteratorTimestamp
57
- this.shardFilterType = options.shardFilterType
58
- this.shardFilterShardId = options.shardFilterShardId
59
- this.shardFilterTimestamp = options.shardFilterTimestamp
60
- this.streamCreationTimestamp = options.streamCreationTimestamp
51
+ this.shardCount = options.shardCount ?? 1
61
52
  this.timestamp = options.timestamp
62
- this.startingSequenceNumber = options.startingSequenceNumber
63
53
  this.kinesis = new Kinesis(omit(['name'])(options))
64
54
  this.cancelToken = new Promise((_, reject) => (this.canceller = reject))
65
55
 
@@ -76,7 +66,7 @@ const KinesisStream = function (options) {
76
66
  }).catch(async () => {
77
67
  await this.kinesis.client.createStream({
78
68
  StreamName: this.name,
79
- ShardCount: 1,
69
+ ShardCount: this.shardCount,
80
70
  }).promise()
81
71
  await this.kinesis.client.waitFor('streamExists', {
82
72
  StreamName: this.name
@@ -103,7 +93,7 @@ KinesisStream.prototype.delete = function deleteStream() {
103
93
  * @synopsis
104
94
  * ```coffeescript [specscript]
105
95
  * KinesisStream(options).putRecord(
106
- * data string|binary,
96
+ * data string|Buffer,
107
97
  * options {
108
98
  * partitionKey: string, // input to aws hash function to determine which shard
109
99
  * explicitHashKey: string, // skips aws hash function to determine which shard
@@ -129,6 +119,57 @@ KinesisStream.prototype.putRecord = async function putRecord(data, options = {})
129
119
  }).promise()
130
120
  }
131
121
 
122
+ /**
123
+ * @name KinesisStream.prototype.putRecords
124
+ *
125
+ * @synopsis
126
+ * ```coffeescript [specscript]
127
+ * KinesisStream(options).putRecords(records Array<{
128
+ * data: string|Buffer,
129
+ * partitionKey: string, // input to aws hash function to determine which shard
130
+ * explicitHashKey: string, // skips aws hash function to determine which shard
131
+ * }>) -> Promise<{
132
+ * FailedRecordCount: number, // number of unsuccessfully processed records
133
+ * Records: Array<{
134
+ * SequenceNumber: string,
135
+ * ShardId: string,
136
+ * ErrorCode?: 'ProvisionedThroughputExceededException'|'InternalFailure',
137
+ * ErrorMessage?: string,
138
+ * }>,
139
+ * EncryptionType: 'NONE'|'KMS',
140
+ * }>
141
+ * ```
142
+ *
143
+ * @description
144
+ * Limits/Quotas:
145
+ * * 500 records max per request
146
+ * * 1 MB per record max
147
+ * * 5 MB per request max
148
+ * * 1000 records per second per shard
149
+ * * 1 MB per second per shard
150
+ *
151
+ * https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Kinesis.html#putRecords-property
152
+ */
153
+ KinesisStream.prototype.putRecords = async function putRecords(records) {
154
+ return this.kinesis.client.putRecords({
155
+ StreamName: this.name,
156
+ Records: records.map(({ data, partitionKey, explicitHashKey }) => ({
157
+ Data: data,
158
+ PartitionKey: partitionKey ?? data.slice(0, 255),
159
+ ...explicitHashKey && { ExplicitHashKey: explicitHashKey },
160
+ }))
161
+ }).promise().then(tap(response => {
162
+ if (response.Records.some(has('ErrorCode'))) {
163
+ const errors = response.Records.filter(has('ErrorCode')).map(Record => {
164
+ const error = new Error(Record.ErrorMessage)
165
+ error.code = Record.ErrorCode
166
+ return error
167
+ })
168
+ throw new AggregateError(errors, 'Some records failed to process')
169
+ }
170
+ }))
171
+ }
172
+
132
173
  // () => ()
133
174
  KinesisStream.prototype.close = function close() {
134
175
  this.closed = true
@@ -163,9 +204,7 @@ KinesisStream.prototype.getRecords = async function* getRecords(Shard) {
163
204
  ShardId: Shard.ShardId,
164
205
  StreamName: this.name,
165
206
  ShardIteratorType: this.shardIteratorType,
166
- ...this.shardIteratorTimestamp && {
167
- Timestamp: this.shardIteratorTimestamp,
168
- },
207
+ ...this.timestamp == null ? {} : { Timestamp: this.timestamp },
169
208
  }).promise().then(get('ShardIterator'))
170
209
 
171
210
  let records = await this.kinesis.client.getRecords({
@@ -7,9 +7,11 @@ const map = require('rubico/map')
7
7
  const thunkify = require('rubico/thunkify')
8
8
 
9
9
  const test = new Test('KinesisStream', KinesisStream)
10
+
10
11
  .before(function () {
11
12
  this.streams = []
12
13
  })
14
+
13
15
  .case({
14
16
  name: 'my-stream',
15
17
  endpoint: 'http://localhost:4567',
@@ -21,7 +23,87 @@ const test = new Test('KinesisStream', KinesisStream)
21
23
  await myStream.putRecord('hey')
22
24
  await myStream.putRecord('ho', { partitionKey: 'ho' })
23
25
  await myStream.putRecord('hi', { explicitHashKey: '127' })
26
+ const first3 = await asyncIterableTake(3)(myStream)
27
+ const first3Again = await asyncIterableTake(3)(myStream)
28
+ assert.deepEqual(first3, first3Again)
29
+ this.streams.push(myStream)
30
+ })
31
+
32
+ .case({
33
+ name: 'my-stream',
34
+ endpoint: 'http://localhost:4567',
35
+ shardIteratorType: 'TRIM_HORIZON',
36
+ getRecordsLimit: 1,
37
+ listShardsLimit: 1,
38
+ shardCount: 2,
39
+ }, async function (myStream) {
40
+ await myStream.ready
41
+ await myStream.putRecord('hey', { partitionKey: 'a' })
42
+ await myStream.putRecord('ho', { partitionKey: 'a' })
43
+ await myStream.putRecord('hi', { partitionKey: 'a' })
44
+ const first3 = await asyncIterableTake(3)(myStream)
45
+ const first3Again = await asyncIterableTake(3)(myStream)
46
+ assert.deepEqual(first3, first3Again)
47
+ this.streams.push(myStream)
48
+ })
24
49
 
50
+ .case({
51
+ name: 'my-stream',
52
+ endpoint: 'http://localhost:4567',
53
+ shardIteratorType: 'TRIM_HORIZON',
54
+ }, async function (myStream) {
55
+ await myStream.ready
56
+ await myStream.putRecords([
57
+ { data: 'hey' },
58
+ { data: 'ho', partitionKey: 'ho' },
59
+ { data: 'hi', explicitHashKey: '127' },
60
+ ])
61
+ const first3 = await asyncIterableTake(3)(myStream)
62
+ const first3Again = await asyncIterableTake(3)(myStream)
63
+ assert.deepEqual(first3, first3Again)
64
+ this.streams.push(myStream)
65
+ })
66
+
67
+ .case({
68
+ name: 'my-stream',
69
+ endpoint: 'http://localhost:4567',
70
+ shardIteratorType: 'TRIM_HORIZON',
71
+ }, async function (myStream) {
72
+ myStream.kinesis.client.putRecords = () => ({
73
+ promise: async () => ({
74
+ Records: [
75
+ {
76
+ ErrorCode: 'ProvisionedThroughputExceededException',
77
+ ErrorMessage: 'Some message with accountId, stream name, and shard ID',
78
+ },
79
+ {
80
+ ErrorCode: 'ProvisionedThroughputExceededException',
81
+ ErrorMessage: 'Some message with accountId, stream name, and shard ID',
82
+ },
83
+ ],
84
+ }),
85
+ })
86
+ await myStream.ready
87
+ await assert.rejects(
88
+ () => myStream.putRecords([{ data: 'hey' }]),
89
+ new AggregateError([
90
+ new Error('Some message with accountId, stream name, and shard ID'),
91
+ new Error('Some message with accountId, stream name, and shard ID'),
92
+ ], 'Some records failed to process')
93
+ )
94
+ this.streams.push(myStream)
95
+ })
96
+
97
+ .case({
98
+ name: 'my-stream',
99
+ endpoint: 'http://localhost:4567',
100
+ shardIteratorType: 'AT_TIMESTAMP',
101
+ timestamp: new Date(Date.now() - 5000),
102
+ }, async function (myStream) {
103
+ await myStream.ready
104
+ await myStream.putRecord('hey')
105
+ await myStream.putRecord('ho', { partitionKey: 'ho' })
106
+ await myStream.putRecord('hi', { explicitHashKey: '127' })
25
107
  const first3 = await asyncIterableTake(3)(myStream)
26
108
  const first3Again = await asyncIterableTake(3)(myStream)
27
109
  assert.deepEqual(first3, first3Again)
@@ -69,8 +151,11 @@ const test = new Test('KinesisStream', KinesisStream)
69
151
  await new Promise(resolve => setTimeout(resolve, 1000))
70
152
  })
71
153
 
72
- .after(async function() {
73
- await map(stream => stream.delete())(this.streams)
154
+ .after(async function () {
155
+ await map(async function cleanup(stream) {
156
+ stream.close()
157
+ await stream.delete()
158
+ })(this.streams)
74
159
  })
75
160
 
76
161
  if (process.argv[1] == __filename) {
@@ -115,7 +115,12 @@ const TranscribeStream = function (options) {
115
115
  })
116
116
  this.websocket.on('message', chunk => {
117
117
  const { headers, body } = unmarshalMessage(chunk)
118
- if (body.Transcript.Results.length > 0) {
118
+ if (headers[':message-type'] == 'exception') {
119
+ const error = new Error(body.Message)
120
+ error.name = headers[':exception-type']
121
+ this.emit('error', error)
122
+ }
123
+ else if (body.Transcript.Results.length > 0) {
119
124
  if (body.Transcript.Results[0].IsPartial) {
120
125
  this.emit('partialTranscription', body.Transcript.Results[0])
121
126
  } else {
@@ -177,6 +182,10 @@ TranscribeStream.prototype.sendAudioChunk = function (chunk) {
177
182
  this.websocket.send(bytes)
178
183
  }
179
184
 
185
+ TranscribeStream.prototype.close = function () {
186
+ this.websocket.close()
187
+ }
188
+
180
189
  /**
181
190
  * @name marshalHeaders
182
191
  *
@@ -52,8 +52,6 @@ const test = new Test('TranscribeStream', async function () {
52
52
  wav.fromScratch(1, 8000, '8', Buffer.from(event.media.payload, 'base64'))
53
53
  wav.fromMuLaw()
54
54
  testTranscribeStream.sendAudioChunk(Buffer.from(wav.data.samples))
55
- } else if (event.event == 'stop') {
56
- testTranscribeStream.websocket.close()
57
55
  }
58
56
  })
59
57
 
@@ -64,6 +62,15 @@ const test = new Test('TranscribeStream', async function () {
64
62
  })
65
63
  assert.equal(testTranscription, 'Hello, world.')
66
64
 
65
+ // wait for timeout error to test error handling
66
+ await new Promise(resolve => {
67
+ testTranscribeStream.on('error', error => {
68
+ console.error(error)
69
+ testTranscribeStream.close()
70
+ resolve()
71
+ })
72
+ })
73
+
67
74
  /*
68
75
  // fill media-stream-fixture-aws-keynote.txt
69
76
  const Twilio = require('@claimyr_hq/twilio/Twilio')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "presidium",
3
- "version": "0.15.28",
3
+ "version": "0.15.32",
4
4
  "description": "A library for creating web services",
5
5
  "author": "Richard Tong",
6
6
  "license": "MIT",