@dra2020/baseclient 1.0.11 → 1.0.14
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/README.md +16 -5
- package/dist/all/all.d.ts +18 -2
- package/dist/all/allclient.d.ts +18 -0
- package/dist/base.js +33010 -0
- package/dist/base.js.map +1 -0
- package/dist/baseclient.js +166 -1279
- package/dist/baseclient.js.map +1 -1
- package/dist/dbabstract/all.d.ts +1 -0
- package/dist/dbabstract/db.d.ts +83 -0
- package/dist/dbdynamo/all.d.ts +1 -0
- package/dist/dbdynamo/dbdynamo.d.ts +190 -0
- package/dist/filterexpr/filterexpr.d.ts +0 -3
- package/dist/fsm/fsm.d.ts +0 -1
- package/dist/fsmfile/all.d.ts +1 -0
- package/dist/fsmfile/fsmfile.d.ts +47 -0
- package/dist/jsonstream/all.d.ts +1 -0
- package/dist/jsonstream/jsonstream.d.ts +130 -0
- package/dist/lambda/all.d.ts +1 -0
- package/dist/lambda/env.d.ts +10 -0
- package/dist/lambda/lambda.d.ts +18 -0
- package/dist/logserver/all.d.ts +5 -0
- package/dist/logserver/log.d.ts +11 -0
- package/dist/logserver/logaccum.d.ts +154 -0
- package/dist/logserver/logblob.d.ts +24 -0
- package/dist/logserver/logconcat.d.ts +55 -0
- package/dist/logserver/logkey.d.ts +28 -0
- package/dist/memsqs/all.d.ts +4 -0
- package/dist/memsqs/client.d.ts +13 -0
- package/dist/memsqs/loopback.d.ts +11 -0
- package/dist/memsqs/orderedlist.d.ts +19 -0
- package/dist/memsqs/queue.d.ts +84 -0
- package/dist/memsqs/server.d.ts +37 -0
- package/dist/ot-js/otsession.d.ts +0 -3
- package/dist/poly/union.d.ts +0 -1
- package/dist/storage/all.d.ts +4 -0
- package/dist/storage/datablob.d.ts +9 -0
- package/dist/storage/env.d.ts +10 -0
- package/dist/storage/splitsblob.d.ts +13 -0
- package/dist/storage/storage.d.ts +166 -0
- package/dist/storages3/all.d.ts +1 -0
- package/dist/storages3/s3.d.ts +62 -0
- package/docs/dbabstract.md +2 -0
- package/docs/dbdynamo.md +2 -0
- package/docs/fsmfile.md +2 -0
- package/docs/jsonstream.md +44 -0
- package/docs/lambda.md +2 -0
- package/docs/logserver.md +2 -0
- package/docs/storage.md +2 -0
- package/docs/storages3.md +2 -0
- package/lib/all/all.ts +22 -2
- package/lib/all/allclient.ts +19 -0
- package/lib/dbabstract/all.ts +1 -0
- package/lib/dbabstract/db.ts +246 -0
- package/lib/dbdynamo/all.ts +1 -0
- package/lib/dbdynamo/dbdynamo.ts +1551 -0
- package/lib/filterexpr/filterexpr.ts +5 -79
- package/lib/fsm/fsm.ts +2 -12
- package/lib/fsmfile/all.ts +1 -0
- package/lib/fsmfile/fsmfile.ts +236 -0
- package/lib/jsonstream/all.ts +1 -0
- package/lib/jsonstream/jsonstream.ts +940 -0
- package/lib/lambda/all.ts +1 -0
- package/lib/lambda/env.ts +13 -0
- package/lib/lambda/lambda.ts +120 -0
- package/lib/logserver/all.ts +5 -0
- package/lib/logserver/log.ts +565 -0
- package/lib/logserver/logaccum.ts +1445 -0
- package/lib/logserver/logblob.ts +84 -0
- package/lib/logserver/logconcat.ts +313 -0
- package/lib/logserver/logkey.ts +125 -0
- package/lib/memsqs/all.ts +4 -0
- package/lib/memsqs/client.ts +268 -0
- package/lib/memsqs/loopback.ts +64 -0
- package/lib/memsqs/orderedlist.ts +74 -0
- package/lib/memsqs/queue.ts +395 -0
- package/lib/memsqs/server.ts +262 -0
- package/lib/ot-js/otsession.ts +1 -4
- package/lib/poly/hash.ts +1 -1
- package/lib/poly/topo.ts +41 -15
- package/lib/poly/union.ts +0 -17
- package/lib/storage/all.ts +4 -0
- package/lib/storage/datablob.ts +36 -0
- package/lib/storage/env.ts +14 -0
- package/lib/storage/splitsblob.ts +63 -0
- package/lib/storage/storage.ts +604 -0
- package/lib/storages3/all.ts +1 -0
- package/lib/storages3/s3.ts +576 -0
- package/package.json +10 -9
- package/dist/geo/all.d.ts +0 -2
- package/dist/geo/geo.d.ts +0 -67
- package/dist/geo/vfeature.d.ts +0 -4
- package/docs/filterexpr.md +0 -22
- package/lib/geo/all.ts +0 -2
- package/lib/geo/geo.ts +0 -452
- package/lib/geo/vfeature.ts +0 -34
|
@@ -0,0 +1,1551 @@
|
|
|
1
|
+
// Node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as stream from 'stream';
|
|
4
|
+
import * as zlib from 'zlib';
|
|
5
|
+
|
|
6
|
+
// Public dynamodb API
|
|
7
|
+
import * as DynamoDB from 'aws-sdk/clients/dynamodb';
|
|
8
|
+
import * as DynamoDBStreams from 'aws-sdk/clients/dynamodbstreams';
|
|
9
|
+
|
|
10
|
+
// Shared libraries
|
|
11
|
+
import * as Util from '../util/all';
|
|
12
|
+
import * as Context from '../context/all';
|
|
13
|
+
import * as LogAbstract from '../logabstract/all';
|
|
14
|
+
import * as FSM from '../fsm/all';
|
|
15
|
+
import * as DB from '../dbabstract/all';
|
|
16
|
+
import * as Storage from '../storage/all';
|
|
17
|
+
|
|
18
|
+
export interface Environment
|
|
19
|
+
{
|
|
20
|
+
context: Context.IContext;
|
|
21
|
+
log: LogAbstract.ILog;
|
|
22
|
+
fsmManager: FSM.FsmManager;
|
|
23
|
+
storageManager: Storage.StorageManager;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface EnvironmentEx
|
|
27
|
+
{
|
|
28
|
+
context: Context.IContext;
|
|
29
|
+
log: LogAbstract.ILog;
|
|
30
|
+
fsmManager: FSM.FsmManager;
|
|
31
|
+
storageManager: Storage.StorageManager;
|
|
32
|
+
dbx: DynamoClient;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DBDynamoContextDefaults: Context.ContextValues =
|
|
36
|
+
{
|
|
37
|
+
dynamo_error_frequency: 0,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class Deadline
|
|
41
|
+
{
|
|
42
|
+
elapsed: Util.Elapsed;
|
|
43
|
+
msDeadline: number;
|
|
44
|
+
|
|
45
|
+
constructor(msDeadline: number)
|
|
46
|
+
{
|
|
47
|
+
this.elapsed = new Util.Elapsed();
|
|
48
|
+
this.msDeadline = msDeadline;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
start(): void
|
|
52
|
+
{
|
|
53
|
+
this.elapsed.start();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get msRemaining(): number
|
|
57
|
+
{
|
|
58
|
+
this.elapsed.end();
|
|
59
|
+
let msLeft = this.msDeadline - this.elapsed.ms();
|
|
60
|
+
if (msLeft < 0) msLeft = 0;
|
|
61
|
+
return msLeft;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get passed(): boolean
|
|
65
|
+
{
|
|
66
|
+
this.elapsed.end();
|
|
67
|
+
return this.elapsed.ms() >= this.msDeadline;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function rawTypedValue(o: any): any
|
|
72
|
+
{
|
|
73
|
+
// null is special
|
|
74
|
+
if (o === undefined || o == null || (typeof o === 'string' && o === ''))
|
|
75
|
+
return { NULL: true };
|
|
76
|
+
|
|
77
|
+
// raw type
|
|
78
|
+
switch (typeof o)
|
|
79
|
+
{
|
|
80
|
+
case 'boolean': return { BOOL: o };
|
|
81
|
+
case 'number': return { N: String(o) };
|
|
82
|
+
case 'string': return { S: o };
|
|
83
|
+
default: return { S: String(o) };
|
|
84
|
+
case 'object':
|
|
85
|
+
{
|
|
86
|
+
if (Array.isArray(o))
|
|
87
|
+
{
|
|
88
|
+
let a: any[] = [];
|
|
89
|
+
o.forEach((v: any) => a.push(rawTypedValue(v)));
|
|
90
|
+
return { L: a };
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
{
|
|
94
|
+
let v: any = {};
|
|
95
|
+
Object.keys(o).forEach((p: string) => { v[p] = rawTypedValue(o[p]) } );
|
|
96
|
+
return { M: v };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function rawNakedValue(o: any): any
|
|
103
|
+
{
|
|
104
|
+
if (Util.countKeys(o) !== 1)
|
|
105
|
+
throw new Error(`dynamodb: only expect one key in typed value: ${JSON.stringify(o)}`);
|
|
106
|
+
|
|
107
|
+
for (let p in o) if (o.hasOwnProperty(p))
|
|
108
|
+
{
|
|
109
|
+
switch (p)
|
|
110
|
+
{
|
|
111
|
+
case 'S': return String(o[p]);
|
|
112
|
+
case 'B': return o[p];
|
|
113
|
+
case 'BOOL': return Boolean(o[p]);
|
|
114
|
+
case 'NULL': return null;
|
|
115
|
+
case 'N': return Number(o[p]);
|
|
116
|
+
case 'SS': return o[p];
|
|
117
|
+
case 'M':
|
|
118
|
+
{
|
|
119
|
+
let r: any = {};
|
|
120
|
+
Object.keys(o[p]).forEach((pp: string) => { r[pp] = rawNakedValue(o[p][pp]) });
|
|
121
|
+
return r;
|
|
122
|
+
}
|
|
123
|
+
case 'L':
|
|
124
|
+
{
|
|
125
|
+
let a: any[] = [];
|
|
126
|
+
o[p].forEach((v: any) => a.push(rawNakedValue(v)));
|
|
127
|
+
return a;
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`dynamodb: type ${p} not recognized`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function typedValue(key: string, value: any, attributeIndex: any): any
|
|
136
|
+
{
|
|
137
|
+
switch (attributeIndex[key])
|
|
138
|
+
{
|
|
139
|
+
case 'S':
|
|
140
|
+
return (value == null || value === '') ? rawTypedValue(value) : { S: String(value) };
|
|
141
|
+
case 'B':
|
|
142
|
+
return { B: value };
|
|
143
|
+
case 'BOOL':
|
|
144
|
+
return { BOOL: Boolean(!!value) };
|
|
145
|
+
case 'N':
|
|
146
|
+
return { N: String(Number(value)) };
|
|
147
|
+
case 'SS':
|
|
148
|
+
return { SS: value };
|
|
149
|
+
case 'M':
|
|
150
|
+
case 'L':
|
|
151
|
+
case undefined:
|
|
152
|
+
return rawTypedValue(value);
|
|
153
|
+
default:
|
|
154
|
+
throw new Error(`dynamodb: Type ${attributeIndex[key]} unsupported in schema definition`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function keysPresent(o: any, keyschema: any, attributeIndex: any): any
|
|
159
|
+
{
|
|
160
|
+
let typedKey: any = {};
|
|
161
|
+
let bValid: boolean = false;
|
|
162
|
+
|
|
163
|
+
// First determine if all the projected key schema attributes are present in the object
|
|
164
|
+
for (let i: number = 0; i < keyschema.length; i++)
|
|
165
|
+
{
|
|
166
|
+
let k = keyschema[i];
|
|
167
|
+
let v = o[k.AttributeName];
|
|
168
|
+
if (v === undefined)
|
|
169
|
+
return bValid ? typedKey : null;
|
|
170
|
+
else
|
|
171
|
+
{
|
|
172
|
+
typedKey[k.AttributeName] = typedValue(k.AttributeName, v, attributeIndex);
|
|
173
|
+
if (k.KeyType === 'HASH') bValid = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Now verify that the schema attributes is ALL that are available in the query object
|
|
178
|
+
//for (let p in o) if (o.hasOwnProperty(p))
|
|
179
|
+
//if (typedKey[p] === undefined)
|
|
180
|
+
//return null;
|
|
181
|
+
|
|
182
|
+
// OK, this query matches this key schema
|
|
183
|
+
return typedKey;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class FsmAPIWatch extends FSM.Fsm
|
|
188
|
+
{
|
|
189
|
+
constructor(env: Environment)
|
|
190
|
+
{
|
|
191
|
+
super(env);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get env(): Environment { return this._env as Environment }
|
|
195
|
+
|
|
196
|
+
tick(): void
|
|
197
|
+
{
|
|
198
|
+
//this.env.log.value({ event: 'dynamodb: APIs outstanding', value: this.nWaitOn });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const FSM_LISTING = FSM.FSM_CUSTOM1;
|
|
203
|
+
const FSM_DESCRIBING = FSM.FSM_CUSTOM2;
|
|
204
|
+
const FSM_DESCRIBE = FSM.FSM_CUSTOM3;
|
|
205
|
+
|
|
206
|
+
class FsmListTables extends FSM.Fsm
|
|
207
|
+
{
|
|
208
|
+
tables: any;
|
|
209
|
+
workStack: string[];
|
|
210
|
+
|
|
211
|
+
constructor(env: Environment)
|
|
212
|
+
{
|
|
213
|
+
super(env);
|
|
214
|
+
this.tables = {};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
get env(): EnvironmentEx { return this._env as EnvironmentEx }
|
|
218
|
+
|
|
219
|
+
tick(): void
|
|
220
|
+
{
|
|
221
|
+
if (this.ready)
|
|
222
|
+
{
|
|
223
|
+
switch (this.state)
|
|
224
|
+
{
|
|
225
|
+
case FSM.FSM_STARTING:
|
|
226
|
+
this.setState(FSM_LISTING);
|
|
227
|
+
this.env.dbx.dynamodb.listTables({}, (err: any, result: any) => {
|
|
228
|
+
if (err)
|
|
229
|
+
{
|
|
230
|
+
console.log(`dynamodb: listTables error: ${JSON.stringify(err)}`);
|
|
231
|
+
this.env.log.error({ event: 'dynamodb: listTables', detail: JSON.stringify(err) });
|
|
232
|
+
this.setState(FSM.FSM_ERROR);
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
{
|
|
236
|
+
if (result.TableNames)
|
|
237
|
+
result.TableNames.forEach((name: string) => this.tables[name] = {});
|
|
238
|
+
this.workStack = Object.keys(this.tables);
|
|
239
|
+
this.setState(FSM_DESCRIBE);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
case FSM_LISTING:
|
|
245
|
+
case FSM_DESCRIBING:
|
|
246
|
+
// Waiting for callback to move out of this state
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case FSM_DESCRIBE:
|
|
250
|
+
if (this.workStack && this.workStack.length > 0)
|
|
251
|
+
{
|
|
252
|
+
this.setState(FSM_DESCRIBING);
|
|
253
|
+
let params: any = { TableName: this.workStack.pop() };
|
|
254
|
+
this.env.dbx.dynamodb.describeTable(params, (err: any, result: any) => {
|
|
255
|
+
if (err)
|
|
256
|
+
{
|
|
257
|
+
console.log(`dynamodb: describeTable error: ${JSON.stringify(err)}`);
|
|
258
|
+
this.env.log.error({ event: 'dynamodb: describeTable', detail: JSON.stringify(err) });
|
|
259
|
+
this.setState(FSM.FSM_ERROR);
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
{
|
|
263
|
+
this.tables[params.TableName] = result.Table;
|
|
264
|
+
this.setState(FSM_DESCRIBE);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
this.setState(FSM.FSM_DONE);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function create(env: Environment): DB.DBClient { return new DynamoClient(env) }
|
|
277
|
+
|
|
278
|
+
const FSM_CREATE = FSM.FSM_CUSTOM8;
|
|
279
|
+
const FSM_CREATING = FSM.FSM_CUSTOM9;
|
|
280
|
+
|
|
281
|
+
export class DynamoClient extends DB.DBClient
|
|
282
|
+
{
|
|
283
|
+
serializerUpdate: FSM.FsmSerializer;
|
|
284
|
+
fsmAPIWatch: FsmAPIWatch;
|
|
285
|
+
fsmListTables: FsmListTables;
|
|
286
|
+
dynamodb: DynamoDB;
|
|
287
|
+
dynamostream: DynamoDBStreams;
|
|
288
|
+
pendingCols: DynamoCollection[];
|
|
289
|
+
existingCols: { [name: string]: DynamoCollection };
|
|
290
|
+
bList: boolean;
|
|
291
|
+
|
|
292
|
+
constructor(env: Environment)
|
|
293
|
+
{
|
|
294
|
+
super(env);
|
|
295
|
+
env.context.setDefaults(DBDynamoContextDefaults);
|
|
296
|
+
this.env.dbx = this;
|
|
297
|
+
|
|
298
|
+
if (env.context.xstring('aws_access_key_id') === undefined || env.context.xstring('aws_secret_access_key') === undefined)
|
|
299
|
+
{
|
|
300
|
+
this.env.log.error('DynamoDB: not configured: exiting');
|
|
301
|
+
this.env.log.dump();
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.dynamodb = new DynamoDB({apiVersion: '2012-08-10', region: 'us-west-2'});
|
|
306
|
+
this.dynamostream = new DynamoDBStreams({apiVersion: '2012-08-10', region: 'us-west-2'});
|
|
307
|
+
|
|
308
|
+
this.pendingCols = [];
|
|
309
|
+
this.existingCols = {};
|
|
310
|
+
this.serializerUpdate = new FSM.FsmSerializer(env);
|
|
311
|
+
this.fsmAPIWatch = new FsmAPIWatch(env);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
get env(): EnvironmentEx { return this._env as EnvironmentEx; }
|
|
315
|
+
|
|
316
|
+
get Production(): boolean { return this.env.context.xflag('production'); }
|
|
317
|
+
get DBName(): string { return this.Production ? 'prod' : 'dev'; }
|
|
318
|
+
get dynamoErrorFrequency(): number { return this.env.context.xnumber('dynamo_error_frequency'); }
|
|
319
|
+
|
|
320
|
+
createCollection(name: string, options: any): DB.DBCollection
|
|
321
|
+
{
|
|
322
|
+
if (this.existingCols[name]) return this.existingCols[name];
|
|
323
|
+
const defaultOptions: any = { Schema: { id: 'S' }, KeySchema: { id: 'HASH' } };
|
|
324
|
+
options = Util.deepCopy(Util.shallowAssignImmutable(defaultOptions, options));
|
|
325
|
+
options.AttributeDefinitions = DB.fromCompactSchema(options.Schema);
|
|
326
|
+
options.KeySchema = DB.fromCompactKey(options.KeySchema);
|
|
327
|
+
if (options.GlobalSecondaryIndexes)
|
|
328
|
+
options.GlobalSecondaryIndexes = options.GlobalSecondaryIndexes.map((v: any) => DB.fromCompactIndex(v));
|
|
329
|
+
let col = new DynamoCollection(this.env, this, name, options);
|
|
330
|
+
this.ensureCollection(col);
|
|
331
|
+
this.existingCols[name] = col;
|
|
332
|
+
this.fsmAPIWatch.waitOn(col);
|
|
333
|
+
return col;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
createStream(col: DynamoCollection): FSM.FsmArray
|
|
337
|
+
{
|
|
338
|
+
return col.createStream();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
closeStream(col: DynamoCollection): void
|
|
342
|
+
{
|
|
343
|
+
col.closeStream();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
ensureCollection(col: DynamoCollection): void
|
|
347
|
+
{
|
|
348
|
+
this.pendingCols.push(col);
|
|
349
|
+
if (this.done) this.setState(FSM_CREATE);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
createUpdate(col: DynamoCollection, query: any, values: any): DB.DBUpdate
|
|
353
|
+
{
|
|
354
|
+
let update = new DynamoUpdate(this.env, col, query, values);
|
|
355
|
+
if (query && query.id)
|
|
356
|
+
this.serializerUpdate.serialize(query.id, update);
|
|
357
|
+
this.fsmAPIWatch.waitOn(update);
|
|
358
|
+
return update;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
createUnset(col: DynamoCollection, query: any, values: any): DB.DBUnset
|
|
362
|
+
{
|
|
363
|
+
let unset = new DynamoUnset(this.env, col, query, values);
|
|
364
|
+
if (query && query.id)
|
|
365
|
+
this.serializerUpdate.serialize(query.id, unset);
|
|
366
|
+
this.fsmAPIWatch.waitOn(unset);
|
|
367
|
+
return unset;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
createDelete(col: DynamoCollection, query: any): DB.DBDelete
|
|
371
|
+
{
|
|
372
|
+
let del = new DynamoDelete(this.env, col, query);
|
|
373
|
+
this.fsmAPIWatch.waitOn(del);
|
|
374
|
+
return del;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
createFind(col: DynamoCollection, filter: any): DB.DBFind
|
|
378
|
+
{
|
|
379
|
+
let find = new DynamoFind(this.env, col, filter);
|
|
380
|
+
this.fsmAPIWatch.waitOn(find);
|
|
381
|
+
return find;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
createQuery(col: DynamoCollection, filter: any): DB.DBQuery
|
|
385
|
+
{
|
|
386
|
+
let query = new DynamoQuery(this.env, col, filter);
|
|
387
|
+
this.fsmAPIWatch.waitOn(query);
|
|
388
|
+
return query;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
createIndex(col: DynamoCollection, uid: string): DB.DBIndex
|
|
392
|
+
{
|
|
393
|
+
let index = new DynamoIndex(this.env, col, uid);
|
|
394
|
+
this.fsmAPIWatch.waitOn(index);
|
|
395
|
+
return index;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
createClose(): DB.DBClose
|
|
399
|
+
{
|
|
400
|
+
let dbclose = new DynamoClose(this.env, this);
|
|
401
|
+
this.fsmAPIWatch.waitOn(dbclose);
|
|
402
|
+
return dbclose;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
forceError(): boolean
|
|
406
|
+
{
|
|
407
|
+
if (!this.Production && (Math.random() < this.dynamoErrorFrequency))
|
|
408
|
+
return true;
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
tick(): void
|
|
413
|
+
{
|
|
414
|
+
if (this.ready && this.isDependentError)
|
|
415
|
+
this.setState(FSM.FSM_ERROR);
|
|
416
|
+
else if (this.ready)
|
|
417
|
+
{
|
|
418
|
+
switch (this.state)
|
|
419
|
+
{
|
|
420
|
+
case FSM.FSM_STARTING:
|
|
421
|
+
if (this.bList || this.fsmListTables == null)
|
|
422
|
+
{
|
|
423
|
+
this.fsmListTables = new FsmListTables(this.env);
|
|
424
|
+
this.waitOn(this.fsmListTables);
|
|
425
|
+
this.setState(FSM_LISTING);
|
|
426
|
+
this.bList = false;
|
|
427
|
+
}
|
|
428
|
+
this.setState(FSM_LISTING);
|
|
429
|
+
break;
|
|
430
|
+
case FSM_LISTING:
|
|
431
|
+
this.setState(this.pendingCols.length > 0 ? FSM_CREATE : FSM.FSM_DONE);
|
|
432
|
+
break;
|
|
433
|
+
case FSM_CREATE:
|
|
434
|
+
// while there are tables to create, create them, otherwise re-list the tables
|
|
435
|
+
while (this.pendingCols.length)
|
|
436
|
+
{
|
|
437
|
+
let col = this.pendingCols.pop();
|
|
438
|
+
if (this.fsmListTables.tables[col.col.TableName] === undefined)
|
|
439
|
+
{
|
|
440
|
+
this.waitOn(new FsmExecuteCreate(this.env, col));
|
|
441
|
+
this.setState(FSM_CREATING);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
this.setState(FSM.FSM_STARTING);
|
|
446
|
+
break;
|
|
447
|
+
case FSM_CREATING:
|
|
448
|
+
this.bList = true;
|
|
449
|
+
this.setState(FSM_CREATE);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class FsmExecuteCreate extends FSM.Fsm
|
|
457
|
+
{
|
|
458
|
+
col: DynamoCollection;
|
|
459
|
+
|
|
460
|
+
constructor(env: Environment, col: DynamoCollection)
|
|
461
|
+
{
|
|
462
|
+
super(env);
|
|
463
|
+
this.col = col;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
get env(): Environment { return this._env as Environment }
|
|
467
|
+
|
|
468
|
+
create(): void
|
|
469
|
+
{
|
|
470
|
+
let i: number;
|
|
471
|
+
let options: any = {};
|
|
472
|
+
options.TableName = this.col.col.TableName;
|
|
473
|
+
options.AttributeDefinitions = this.col.options.AttributeDefinitions;
|
|
474
|
+
options.BillingMode = this.col.options.BillingMode || 'PAY_PER_REQUEST';
|
|
475
|
+
//if (options.ProvisionedThroughput === undefined)
|
|
476
|
+
//options.ProvisionedThroughput = { ReadCapacityUnits: 1, WriteCapacityUnits: 1 };
|
|
477
|
+
options.StreamSpecification = this.col.options.StreamSpecification || { StreamEnabled: true, StreamViewType: 'KEYS_ONLY' };
|
|
478
|
+
if (this.col.options.GlobalSecondaryIndexes)
|
|
479
|
+
{
|
|
480
|
+
options.GlobalSecondaryIndexes = [];
|
|
481
|
+
this.col.options.GlobalSecondaryIndexes.forEach((ix: any) => {
|
|
482
|
+
if (ix.Projection === undefined)
|
|
483
|
+
ix.Projection = { ProjectionType: 'ALL' };
|
|
484
|
+
//if (ix.ProvisionedThroughput === undefined)
|
|
485
|
+
//ix.ProvisionedThroughput = { ReadCapacityUnits: '1', WriteCapacityUnits: '1' };
|
|
486
|
+
options.GlobalSecondaryIndexes.push(ix);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let keys: any = {};
|
|
491
|
+
if (this.col.options.KeySchema)
|
|
492
|
+
{
|
|
493
|
+
options.KeySchema = this.col.options.KeySchema;
|
|
494
|
+
options.KeySchema.forEach((p: any) => keys[p.AttributeName] = true);
|
|
495
|
+
}
|
|
496
|
+
if (options.GlobalSecondaryIndexes)
|
|
497
|
+
options.GlobalSecondaryIndexes.forEach((ix: any) => ix.KeySchema.forEach((p: any) => keys[p.AttributeName] = true));
|
|
498
|
+
|
|
499
|
+
// Only include attribute definitions that are used in KeySchema or SecondaryIndices
|
|
500
|
+
for (i = options.AttributeDefinitions.length - 1; i >= 0; i--)
|
|
501
|
+
{
|
|
502
|
+
let id: any = options.AttributeDefinitions[i];
|
|
503
|
+
if (keys[id.AttributeName] === undefined)
|
|
504
|
+
options.AttributeDefinitions.splice(i, 1);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
this.setState(FSM.FSM_PENDING);
|
|
508
|
+
this.col.dynamodb.createTable(options, (err: any, data: any) => {
|
|
509
|
+
if (err)
|
|
510
|
+
{
|
|
511
|
+
console.log(`dynamodb: createTable error: ${JSON.stringify(err)}`);
|
|
512
|
+
this.env.log.error({ event: 'dynamodb: createTable', detail: JSON.stringify(err) });
|
|
513
|
+
this.setState(FSM.FSM_ERROR);
|
|
514
|
+
}
|
|
515
|
+
else
|
|
516
|
+
{
|
|
517
|
+
this.env.log.event({ event: 'createTable', detail: options.TableName });
|
|
518
|
+
this.setState(FSM.FSM_DONE);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
tick(): void
|
|
524
|
+
{
|
|
525
|
+
if (this.ready)
|
|
526
|
+
{
|
|
527
|
+
switch (this.state)
|
|
528
|
+
{
|
|
529
|
+
case FSM.FSM_STARTING:
|
|
530
|
+
this.create();
|
|
531
|
+
break;
|
|
532
|
+
|
|
533
|
+
// other state transitions happen in callback
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export class DynamoCollection extends DB.DBCollection
|
|
540
|
+
{
|
|
541
|
+
attributeIndex: any;
|
|
542
|
+
keyIndex: any;
|
|
543
|
+
fsmStream: FsmTableStream;
|
|
544
|
+
|
|
545
|
+
constructor(env: Environment, client: DynamoClient, name: string, options: any)
|
|
546
|
+
{
|
|
547
|
+
super(env, client, name, options);
|
|
548
|
+
this.waitOn(client);
|
|
549
|
+
this.col = { TableName: `dra-${client.DBName}-${name}` };
|
|
550
|
+
this.constructIndex();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
get env(): Environment { return this._env as Environment; }
|
|
554
|
+
|
|
555
|
+
get dynclient(): DynamoClient { return this.client as DynamoClient }
|
|
556
|
+
get dynamodb(): DynamoDB { return this.dynclient.dynamodb }
|
|
557
|
+
|
|
558
|
+
createStream(): FSM.FsmArray
|
|
559
|
+
{
|
|
560
|
+
if (this.fsmStream == null)
|
|
561
|
+
this.fsmStream = new FsmTableStream(this.env, this);
|
|
562
|
+
return this.fsmStream.fsmResult;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
closeStream(): void
|
|
566
|
+
{
|
|
567
|
+
if (this.fsmStream)
|
|
568
|
+
{
|
|
569
|
+
this.fsmStream.end();
|
|
570
|
+
this.fsmStream = null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
constructIndex(): void
|
|
575
|
+
{
|
|
576
|
+
this.attributeIndex = {};
|
|
577
|
+
if (this.options.AttributeDefinitions)
|
|
578
|
+
this.options.AttributeDefinitions.forEach((p: any) => this.attributeIndex[p.AttributeName] = p.AttributeType);
|
|
579
|
+
this.keyIndex = {};
|
|
580
|
+
if (this.options.KeySchema)
|
|
581
|
+
this.options.KeySchema.forEach((p: any) => this.keyIndex[p.AttributeName] = p.KeyType);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
addConditionExpression(query: any, key: any): void
|
|
585
|
+
{
|
|
586
|
+
query.isquery = true;
|
|
587
|
+
let j: number = 0;
|
|
588
|
+
let ev: any = {};
|
|
589
|
+
query.ExpressionAttributeValues = ev;
|
|
590
|
+
let en: any = {};
|
|
591
|
+
query.ExpressionAttributeNames = en;
|
|
592
|
+
for (let p in key) if (key.hasOwnProperty(p))
|
|
593
|
+
{
|
|
594
|
+
en[`#n${j}`] = p;
|
|
595
|
+
ev[`:v${j}`] = key[p];
|
|
596
|
+
j++;
|
|
597
|
+
}
|
|
598
|
+
query.KeyConditionExpression = this.toTestExpression(query);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
toInternalQuery(query: any): any
|
|
602
|
+
{
|
|
603
|
+
let q: any = {};
|
|
604
|
+
q.TableName = this.col.TableName;
|
|
605
|
+
let key = keysPresent(query, this.options.KeySchema, this.attributeIndex);
|
|
606
|
+
if (key)
|
|
607
|
+
{
|
|
608
|
+
if (Util.countKeys(this.options.KeySchema) != Util.countKeys(key))
|
|
609
|
+
this.addConditionExpression(q, key);
|
|
610
|
+
else
|
|
611
|
+
q.Key = key;
|
|
612
|
+
return q;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (this.options.GlobalSecondaryIndexes !== undefined)
|
|
616
|
+
{
|
|
617
|
+
for (let i: number = 0; i < this.options.GlobalSecondaryIndexes.length; i++)
|
|
618
|
+
{
|
|
619
|
+
let ix = this.options.GlobalSecondaryIndexes[i];
|
|
620
|
+
let key = keysPresent(query, ix.KeySchema, this.attributeIndex);
|
|
621
|
+
if (key)
|
|
622
|
+
{
|
|
623
|
+
q.IndexName = ix.IndexName;
|
|
624
|
+
this.addConditionExpression(q, key);
|
|
625
|
+
return q;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return q;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
toInternalExpression(o: any): any
|
|
633
|
+
{
|
|
634
|
+
let ex: any = {};
|
|
635
|
+
let j: number = 0;
|
|
636
|
+
let ev: any = {};
|
|
637
|
+
ex.ExpressionAttributeValues = ev;
|
|
638
|
+
let en: any = {};
|
|
639
|
+
ex.ExpressionAttributeNames = en;
|
|
640
|
+
for (let p in o) if (o.hasOwnProperty(p))
|
|
641
|
+
{
|
|
642
|
+
if (this.keyIndex[p] === undefined)
|
|
643
|
+
{
|
|
644
|
+
let v = typedValue(p, o[p], this.attributeIndex);
|
|
645
|
+
if (v && (v.SS === undefined || v.SS.length > 0))
|
|
646
|
+
{
|
|
647
|
+
en[`#n${j}`] = p;
|
|
648
|
+
ev[`:v${j}`] = v;
|
|
649
|
+
j++;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Ensure we don't have an empty update expression
|
|
655
|
+
if (Util.countKeys(ex.ExpressionAttributeNames) == 0)
|
|
656
|
+
{
|
|
657
|
+
ex.ExpressionAttributeNames['#n0'] = '__nonempty';
|
|
658
|
+
ex.ExpressionAttributeValues[':v0'] = { NULL: true };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return ex;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
toTestExpression(expr: any): string
|
|
665
|
+
{
|
|
666
|
+
let sa: string[] = [];
|
|
667
|
+
let j: number = 0;
|
|
668
|
+
let n: number = Util.countKeys(expr.ExpressionAttributeNames);
|
|
669
|
+
for (let i: number = 0; i < n; i++)
|
|
670
|
+
sa.push(`#n${i} = :v${i}`);
|
|
671
|
+
return sa.join(', ');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
toUpdateExpression(expr: any): string
|
|
675
|
+
{
|
|
676
|
+
let saSet: string[] = [];
|
|
677
|
+
let saAdd: string[] = [];
|
|
678
|
+
let j: number = 0;
|
|
679
|
+
let n: number = Util.countKeys(expr.ExpressionAttributeNames);
|
|
680
|
+
for (let i: number = 0; i < n; i++)
|
|
681
|
+
{
|
|
682
|
+
let sv = `:v${i}`;
|
|
683
|
+
let sn = `#n${i}`;
|
|
684
|
+
let v = expr.ExpressionAttributeValues[sv];
|
|
685
|
+
if (v.SS !== undefined)
|
|
686
|
+
saAdd.push(`${sn} ${sv}`);
|
|
687
|
+
else
|
|
688
|
+
saSet.push(`${sn} = ${sv}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
let fullExpr: string = '';
|
|
692
|
+
if (saSet.length > 0)
|
|
693
|
+
fullExpr += `SET ${saSet.join(', ')}`;
|
|
694
|
+
if (saAdd.length > 0)
|
|
695
|
+
fullExpr += ` ADD ${saAdd.join(', ')}`;
|
|
696
|
+
return fullExpr;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
toRemoveExpression(expr: any): string
|
|
700
|
+
{
|
|
701
|
+
let saRemove: string[] = [];
|
|
702
|
+
let saDelete: string[] = [];
|
|
703
|
+
let j: number = 0;
|
|
704
|
+
let n: number = Util.countKeys(expr.ExpressionAttributeNames);
|
|
705
|
+
for (let i: number = 0; i < n; i++)
|
|
706
|
+
{
|
|
707
|
+
let sv = `:v${i}`;
|
|
708
|
+
let sn = `#n${i}`;
|
|
709
|
+
let v = expr.ExpressionAttributeValues[sv];
|
|
710
|
+
if (v.SS !== undefined)
|
|
711
|
+
saDelete.push(`${sn} ${sv}`);
|
|
712
|
+
else
|
|
713
|
+
{
|
|
714
|
+
delete expr.ExpressionAttributeValues[sv];
|
|
715
|
+
saRemove.push(`#n${i}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (Util.isEmpty(expr.ExpressionAttributeValues))
|
|
720
|
+
delete expr.ExpressionAttributeValues;
|
|
721
|
+
|
|
722
|
+
let fullExpr: string = '';
|
|
723
|
+
if (saRemove.length > 0)
|
|
724
|
+
fullExpr += `REMOVE ${saRemove.join(', ')}`;
|
|
725
|
+
if (saDelete.length > 0)
|
|
726
|
+
fullExpr += ` DELETE ${saDelete.join(', ')}`;
|
|
727
|
+
return fullExpr;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
toExternal(result: any): any
|
|
731
|
+
{
|
|
732
|
+
if (result)
|
|
733
|
+
{
|
|
734
|
+
let x: any = {};
|
|
735
|
+
Object.keys(result).forEach((p: string) => { x[p] = rawNakedValue(result[p]) });
|
|
736
|
+
delete x.__nonempty;
|
|
737
|
+
return x;
|
|
738
|
+
}
|
|
739
|
+
return result;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
forceError(): boolean
|
|
743
|
+
{
|
|
744
|
+
return (this.client as DynamoClient).forceError();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
tick(): void
|
|
748
|
+
{
|
|
749
|
+
if (this.ready && this.state == FSM.FSM_STARTING)
|
|
750
|
+
this.setState(FSM.FSM_DONE);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export class DynamoUpdate extends DB.DBUpdate
|
|
755
|
+
{
|
|
756
|
+
trace: LogAbstract.AsyncTimer;
|
|
757
|
+
|
|
758
|
+
constructor(env: Environment, col: DynamoCollection, query: any, values: any)
|
|
759
|
+
{
|
|
760
|
+
super(env, col, col.toInternalQuery(query), col.toInternalExpression(values));
|
|
761
|
+
if (this.query.Key === undefined)
|
|
762
|
+
{
|
|
763
|
+
console.log(`dynamodb: DynamoUpdate internal failure: colquery missing Key: ${JSON.stringify(query)}`);
|
|
764
|
+
this.setState(FSM.FSM_ERROR);
|
|
765
|
+
}
|
|
766
|
+
else
|
|
767
|
+
{
|
|
768
|
+
this.waitOn(col);
|
|
769
|
+
this.trace = new LogAbstract.AsyncTimer(env.log, `dynamodb: update(col=${col.name})`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
get env(): Environment { return this._env as Environment; }
|
|
774
|
+
|
|
775
|
+
get dyncol(): DynamoCollection { return this.col as DynamoCollection }
|
|
776
|
+
|
|
777
|
+
forceError(): boolean
|
|
778
|
+
{
|
|
779
|
+
return (this.col.client as DynamoClient).forceError();
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
tick(): void
|
|
783
|
+
{
|
|
784
|
+
if (this.ready)
|
|
785
|
+
{
|
|
786
|
+
if (this.isDependentError)
|
|
787
|
+
this.setState(FSM.FSM_ERROR);
|
|
788
|
+
else if (this.forceError())
|
|
789
|
+
{
|
|
790
|
+
this.setState(FSM.FSM_ERROR);
|
|
791
|
+
this.env.log.error('dynamodb: updateItem: forcing error');
|
|
792
|
+
}
|
|
793
|
+
else if (this.state == FSM.FSM_STARTING)
|
|
794
|
+
{
|
|
795
|
+
this.setState(FSM.FSM_PENDING);
|
|
796
|
+
let params: any = Util.shallowAssignImmutable(this.query, this.values);
|
|
797
|
+
params.UpdateExpression = this.dyncol.toUpdateExpression(params);
|
|
798
|
+
if (params.UpdateExpression === '')
|
|
799
|
+
{
|
|
800
|
+
this.setState(FSM.FSM_DONE);
|
|
801
|
+
}
|
|
802
|
+
this.dyncol.dynamodb.updateItem(params, (err: any, result: any) => {
|
|
803
|
+
if (this.done)
|
|
804
|
+
return;
|
|
805
|
+
else if (err)
|
|
806
|
+
{
|
|
807
|
+
this.setState(FSM.FSM_ERROR);
|
|
808
|
+
this.trace.log();
|
|
809
|
+
this.env.log.error({ event: 'dynamodb: update error', detail: `error: ${JSON.stringify(err)} query: ${JSON.stringify(params)}` });
|
|
810
|
+
}
|
|
811
|
+
else
|
|
812
|
+
{
|
|
813
|
+
this.setState(FSM.FSM_DONE);
|
|
814
|
+
this.result = result;
|
|
815
|
+
this.trace.log();
|
|
816
|
+
if (this.env.context.xnumber('verbosity'))
|
|
817
|
+
this.env.log.event({ event: 'dynamodb: updateItem', detail: JSON.stringify(result) });
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
export class DynamoUnset extends DB.DBUnset
|
|
826
|
+
{
|
|
827
|
+
trace: LogAbstract.AsyncTimer;
|
|
828
|
+
|
|
829
|
+
constructor(env: Environment, col: DynamoCollection, query: any, values: any)
|
|
830
|
+
{
|
|
831
|
+
super(env, col, col.toInternalQuery(query), col.toInternalExpression(values));
|
|
832
|
+
if (this.query.Key === undefined)
|
|
833
|
+
{
|
|
834
|
+
console.log(`dynamodb: DynamoUnset internal failure: query missing Key: ${JSON.stringify(query)}`);
|
|
835
|
+
this.setState(FSM.FSM_ERROR);
|
|
836
|
+
}
|
|
837
|
+
else
|
|
838
|
+
{
|
|
839
|
+
this.waitOn(col);
|
|
840
|
+
this.trace = new LogAbstract.AsyncTimer(env.log, `dynamodb: unset(col=${col.name})`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
get env(): Environment { return this._env as Environment; }
|
|
845
|
+
|
|
846
|
+
get dyncol(): DynamoCollection { return this.col as DynamoCollection }
|
|
847
|
+
|
|
848
|
+
forceError(): boolean
|
|
849
|
+
{
|
|
850
|
+
return (this.col.client as DynamoClient).forceError();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
tick(): void
|
|
854
|
+
{
|
|
855
|
+
if (this.ready)
|
|
856
|
+
{
|
|
857
|
+
if (this.isDependentError)
|
|
858
|
+
this.setState(FSM.FSM_ERROR);
|
|
859
|
+
else if (this.forceError())
|
|
860
|
+
{
|
|
861
|
+
this.setState(FSM.FSM_ERROR);
|
|
862
|
+
this.env.log.error('dynamodb: unset: forcing error');
|
|
863
|
+
}
|
|
864
|
+
else if (this.state == FSM.FSM_STARTING)
|
|
865
|
+
{
|
|
866
|
+
this.setState(FSM.FSM_PENDING);
|
|
867
|
+
let params: any = Util.shallowAssignImmutable(this.query, this.values);
|
|
868
|
+
params.UpdateExpression = this.dyncol.toRemoveExpression(params);
|
|
869
|
+
this.dyncol.dynamodb.updateItem(params, (err: any, result: any) => {
|
|
870
|
+
if (this.done)
|
|
871
|
+
return;
|
|
872
|
+
else if (err)
|
|
873
|
+
{
|
|
874
|
+
this.setState(FSM.FSM_ERROR);
|
|
875
|
+
this.trace.log();
|
|
876
|
+
this.env.log.error({ event: 'dynamodb: unset error', detail: `error: ${JSON.stringify(err)} query: ${JSON.stringify(params)}` });
|
|
877
|
+
}
|
|
878
|
+
else
|
|
879
|
+
{
|
|
880
|
+
this.setState(FSM.FSM_DONE);
|
|
881
|
+
this.result = result;
|
|
882
|
+
this.trace.log();
|
|
883
|
+
if (this.env.context.xnumber('verbosity'))
|
|
884
|
+
this.env.log.event({ event: 'dynamodb: unset', detail: JSON.stringify(result) });
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export class DynamoDelete extends DB.DBDelete
|
|
893
|
+
{
|
|
894
|
+
trace: LogAbstract.AsyncTimer;
|
|
895
|
+
|
|
896
|
+
constructor(env: Environment, col: DynamoCollection, query: any)
|
|
897
|
+
{
|
|
898
|
+
super(env, col, col.toInternalQuery(query));
|
|
899
|
+
if (this.query.Key === undefined)
|
|
900
|
+
{
|
|
901
|
+
console.log(`dynamodb: DynamoDelete internal failure: query missing Key: ${JSON.stringify(query)}`);
|
|
902
|
+
this.setState(FSM.FSM_ERROR);
|
|
903
|
+
}
|
|
904
|
+
else
|
|
905
|
+
{
|
|
906
|
+
this.waitOn(col);
|
|
907
|
+
this.trace = new LogAbstract.AsyncTimer(env.log, `dynamodb: delete(col=${col.name})`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
get env(): Environment { return this._env as Environment; }
|
|
912
|
+
|
|
913
|
+
get dyncol(): DynamoCollection { return this.col as DynamoCollection }
|
|
914
|
+
|
|
915
|
+
forceError(): boolean
|
|
916
|
+
{
|
|
917
|
+
return (this.col.client as DynamoClient).forceError();
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
tick(): void
|
|
921
|
+
{
|
|
922
|
+
if (this.ready)
|
|
923
|
+
{
|
|
924
|
+
if (this.isDependentError)
|
|
925
|
+
this.setState(FSM.FSM_ERROR);
|
|
926
|
+
else if (this.forceError())
|
|
927
|
+
{
|
|
928
|
+
this.setState(FSM.FSM_ERROR);
|
|
929
|
+
this.env.log.error('dynamodb: deleteItem: forcing error');
|
|
930
|
+
}
|
|
931
|
+
else if (this.state == FSM.FSM_STARTING)
|
|
932
|
+
{
|
|
933
|
+
this.setState(FSM.FSM_PENDING);
|
|
934
|
+
this.dyncol.dynamodb.deleteItem(this.query, (err: any, result: any) => {
|
|
935
|
+
if (this.done)
|
|
936
|
+
return;
|
|
937
|
+
else if (err)
|
|
938
|
+
{
|
|
939
|
+
this.setState(FSM.FSM_ERROR);
|
|
940
|
+
this.trace.log();
|
|
941
|
+
this.env.log.error({ event: 'dynamodb: deleteItem: error', detail: JSON.stringify(err) });
|
|
942
|
+
}
|
|
943
|
+
else
|
|
944
|
+
{
|
|
945
|
+
this.setState(FSM.FSM_DONE);
|
|
946
|
+
this.result = result;
|
|
947
|
+
this.trace.log();
|
|
948
|
+
if (this.env.context.xnumber('verbosity'))
|
|
949
|
+
this.env.log.event({ event: 'dynamodb: deleteItem: succeeded', detail: JSON.stringify(result) });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
export class DynamoFind extends DB.DBFind
|
|
958
|
+
{
|
|
959
|
+
trace: LogAbstract.AsyncTimer;
|
|
960
|
+
|
|
961
|
+
constructor(env: Environment, col: DynamoCollection, filter: any)
|
|
962
|
+
{
|
|
963
|
+
super(env, col, col.toInternalQuery(filter));
|
|
964
|
+
if (this.filter.Key === undefined && this.filter.IndexName === undefined)
|
|
965
|
+
{
|
|
966
|
+
console.log(`dynamodb: DynamoFind internal failure: (col=${col.name}) missing Key: ${JSON.stringify(filter)}`);
|
|
967
|
+
this.setState(FSM.FSM_ERROR);
|
|
968
|
+
}
|
|
969
|
+
else
|
|
970
|
+
{
|
|
971
|
+
this.waitOn(col);
|
|
972
|
+
this.trace = new LogAbstract.AsyncTimer(env.log, `dynamodb: find(col=${col.name})`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
get env(): Environment { return this._env as Environment; }
|
|
977
|
+
|
|
978
|
+
get dyncol(): DynamoCollection { return this.col as DynamoCollection }
|
|
979
|
+
|
|
980
|
+
forceError(): boolean
|
|
981
|
+
{
|
|
982
|
+
return (this.col.client as DynamoClient).forceError();
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
tick(): void
|
|
986
|
+
{
|
|
987
|
+
if (this.ready)
|
|
988
|
+
{
|
|
989
|
+
if (this.isDependentError)
|
|
990
|
+
this.setState(FSM.FSM_ERROR);
|
|
991
|
+
else if (this.forceError())
|
|
992
|
+
{
|
|
993
|
+
this.setState(FSM.FSM_ERROR);
|
|
994
|
+
this.env.log.error('dynamodb: find: forcing error');
|
|
995
|
+
}
|
|
996
|
+
else if (this.state == FSM.FSM_STARTING)
|
|
997
|
+
{
|
|
998
|
+
this.setState(FSM.FSM_PENDING);
|
|
999
|
+
if (this.filter.isquery === undefined)
|
|
1000
|
+
{
|
|
1001
|
+
this.dyncol.dynamodb.getItem(this.filter, (err: any, result: any) => {
|
|
1002
|
+
if (this.done)
|
|
1003
|
+
return;
|
|
1004
|
+
else if (err)
|
|
1005
|
+
{
|
|
1006
|
+
this.setState(FSM.FSM_ERROR);
|
|
1007
|
+
this.trace.log();
|
|
1008
|
+
this.env.log.error({ event: 'dynamodb: getItem error', detail: JSON.stringify(err) });
|
|
1009
|
+
}
|
|
1010
|
+
else
|
|
1011
|
+
{
|
|
1012
|
+
this.result = this.dyncol.toExternal(result.Item);
|
|
1013
|
+
this.trace.log();
|
|
1014
|
+
if (this.env.context.xnumber('verbosity'))
|
|
1015
|
+
this.env.log.event( { event: 'dynamodb: getItem', detail: JSON.stringify(result) });
|
|
1016
|
+
this.setState(FSM.FSM_DONE);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
else
|
|
1021
|
+
{
|
|
1022
|
+
delete this.filter.isquery;
|
|
1023
|
+
this.dyncol.dynamodb.query(this.filter, (err: any, result: any) => {
|
|
1024
|
+
if (this.done)
|
|
1025
|
+
return;
|
|
1026
|
+
else if (err)
|
|
1027
|
+
{
|
|
1028
|
+
this.trace.log();
|
|
1029
|
+
this.env.log.error({ event: 'dynamodb: query error', detail: JSON.stringify(err) });
|
|
1030
|
+
this.setState(FSM.FSM_ERROR);
|
|
1031
|
+
}
|
|
1032
|
+
else
|
|
1033
|
+
{
|
|
1034
|
+
if (result.Items && result.Items.length > 0)
|
|
1035
|
+
{
|
|
1036
|
+
let x = this.dyncol.toExternal(result.Items[0]);
|
|
1037
|
+
this.filter = this.dyncol.toInternalQuery(x);
|
|
1038
|
+
this.setState(FSM.FSM_STARTING);
|
|
1039
|
+
}
|
|
1040
|
+
else
|
|
1041
|
+
{
|
|
1042
|
+
this.trace.log();
|
|
1043
|
+
this.setState(FSM.FSM_DONE);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
const FSM_SCANNING = FSM.FSM_CUSTOM1;
|
|
1055
|
+
|
|
1056
|
+
export class DynamoQuery extends DB.DBQuery
|
|
1057
|
+
{
|
|
1058
|
+
trace: LogAbstract.AsyncTimer;
|
|
1059
|
+
lastKey: any;
|
|
1060
|
+
|
|
1061
|
+
constructor(env: Environment, col: DynamoCollection, filter: any)
|
|
1062
|
+
{
|
|
1063
|
+
super(env, col, col.toInternalQuery(filter));
|
|
1064
|
+
this.waitOn(col);
|
|
1065
|
+
this.trace = new LogAbstract.AsyncTimer(env.log, `dynamodb: query(col=${col.name})`);
|
|
1066
|
+
if (this.env.context.xnumber('verbosity'))
|
|
1067
|
+
this.env.log.event({ event: `dynamodb: query in ${col.name}`, detail: JSON.stringify(filter) });
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
get env(): Environment { return this._env as Environment; }
|
|
1071
|
+
|
|
1072
|
+
get dyncol(): DynamoCollection { return this.col as DynamoCollection }
|
|
1073
|
+
|
|
1074
|
+
forceError(): boolean
|
|
1075
|
+
{
|
|
1076
|
+
return (this.col.client as DynamoClient).forceError();
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
doScan(): void
|
|
1080
|
+
{
|
|
1081
|
+
let param: any = Util.deepCopy(this.filter);
|
|
1082
|
+
param.Select = 'ALL_ATTRIBUTES';
|
|
1083
|
+
if (this.lastKey)
|
|
1084
|
+
{
|
|
1085
|
+
param.ExclusiveStartKey = this.lastKey;
|
|
1086
|
+
this.lastKey = undefined;
|
|
1087
|
+
}
|
|
1088
|
+
this.setState(FSM_SCANNING);
|
|
1089
|
+
this.dyncol.dynamodb.scan(param, (err: any, result: any) => {
|
|
1090
|
+
if (this.done)
|
|
1091
|
+
return;
|
|
1092
|
+
else if (err)
|
|
1093
|
+
{
|
|
1094
|
+
this.setState(FSM.FSM_ERROR);
|
|
1095
|
+
this.trace.log();
|
|
1096
|
+
this.env.log.error({ event: 'dynamodb: query error', detail: JSON.stringify(err) });
|
|
1097
|
+
}
|
|
1098
|
+
else
|
|
1099
|
+
{
|
|
1100
|
+
if (result.Items)
|
|
1101
|
+
for (let i: number = 0; i < result.Items.length; i++)
|
|
1102
|
+
this.fsmResult.push(this.dyncol.toExternal(result.Items[i]));
|
|
1103
|
+
if (result.LastEvaluatedKey)
|
|
1104
|
+
{
|
|
1105
|
+
this.lastKey = result.LastEvaluatedKey;
|
|
1106
|
+
this.setState(FSM.FSM_STARTING);
|
|
1107
|
+
}
|
|
1108
|
+
else
|
|
1109
|
+
{
|
|
1110
|
+
this.fsmResult.setState(FSM.FSM_DONE);
|
|
1111
|
+
this.setState(FSM.FSM_DONE);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
tick(): void
|
|
1118
|
+
{
|
|
1119
|
+
if (this.ready && this.isDependentError)
|
|
1120
|
+
{
|
|
1121
|
+
this.fsmResult.setState(FSM.FSM_ERROR);
|
|
1122
|
+
this.setState(FSM.FSM_ERROR);
|
|
1123
|
+
}
|
|
1124
|
+
else if (this.ready)
|
|
1125
|
+
{
|
|
1126
|
+
if (this.state == FSM.FSM_STARTING)
|
|
1127
|
+
{
|
|
1128
|
+
if (Util.countKeys(this.filter) == 1) // Only specified TableName
|
|
1129
|
+
this.doScan();
|
|
1130
|
+
else
|
|
1131
|
+
{
|
|
1132
|
+
let param: any = Util.deepCopy(this.filter);
|
|
1133
|
+
delete param.isquery;
|
|
1134
|
+
if (this.lastKey)
|
|
1135
|
+
{
|
|
1136
|
+
param.ExclusiveStartKey = this.lastKey;
|
|
1137
|
+
this.lastKey = undefined;
|
|
1138
|
+
}
|
|
1139
|
+
this.setState(FSM.FSM_PENDING);
|
|
1140
|
+
this.dyncol.dynamodb.query(param, (err: any, result: any) => {
|
|
1141
|
+
if (this.done)
|
|
1142
|
+
return;
|
|
1143
|
+
else if (err)
|
|
1144
|
+
{
|
|
1145
|
+
this.setState(FSM.FSM_ERROR);
|
|
1146
|
+
this.trace.log();
|
|
1147
|
+
this.env.log.error({ event: 'dynamodb: query error', detail: JSON.stringify(err) });
|
|
1148
|
+
}
|
|
1149
|
+
else
|
|
1150
|
+
{
|
|
1151
|
+
if (result.Items && result.Items.length > 0)
|
|
1152
|
+
result.Items.forEach((i: any) => this.fsmResult.push(this.dyncol.toExternal(i)));
|
|
1153
|
+
if (result.LastEvaluatedKey)
|
|
1154
|
+
{
|
|
1155
|
+
this.lastKey = result.LastEvaluatedKey;
|
|
1156
|
+
this.setState(FSM.FSM_STARTING);
|
|
1157
|
+
}
|
|
1158
|
+
else
|
|
1159
|
+
{
|
|
1160
|
+
this.trace.log();
|
|
1161
|
+
this.fsmResult.setState(FSM.FSM_DONE);
|
|
1162
|
+
this.setState(FSM.FSM_DONE);
|
|
1163
|
+
}
|
|
1164
|
+
if (this.env.context.xnumber('verbosity'))
|
|
1165
|
+
this.env.log.event( { event: 'dynamodb: query', detail: JSON.stringify(result) });
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export class DynamoIndex extends DB.DBIndex
|
|
1175
|
+
{
|
|
1176
|
+
constructor(env: Environment, col: DynamoCollection, uid: string)
|
|
1177
|
+
{
|
|
1178
|
+
super(env, col, uid);
|
|
1179
|
+
this.waitOn(col);
|
|
1180
|
+
this.setState(FSM.FSM_DONE);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export class DynamoClose extends DB.DBClose
|
|
1185
|
+
{
|
|
1186
|
+
constructor(env: Environment, client: DynamoClient)
|
|
1187
|
+
{
|
|
1188
|
+
super(env, client);
|
|
1189
|
+
this.setState(FSM.FSM_DONE);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
interface RangeDescription
|
|
1194
|
+
{
|
|
1195
|
+
StartingSequenceNumber: string;
|
|
1196
|
+
EndingSequenceNumber?: string;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
interface ShardDescription
|
|
1200
|
+
{
|
|
1201
|
+
ShardId: string;
|
|
1202
|
+
SequenceNumberRange: RangeDescription;
|
|
1203
|
+
ShardIterator?: string;
|
|
1204
|
+
ParentShardId?: string;
|
|
1205
|
+
LastSequenceNumber?: string;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function shardSort(s1: ShardDescription, s2: ShardDescription): number
|
|
1209
|
+
{
|
|
1210
|
+
if (s1.ShardId === s2.ParentShardId)
|
|
1211
|
+
return -1;
|
|
1212
|
+
if (s2.ShardId === s1.ParentShardId)
|
|
1213
|
+
return 1;
|
|
1214
|
+
return 0;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
export class FsmTableShards extends FSM.Fsm
|
|
1218
|
+
{
|
|
1219
|
+
table: any;
|
|
1220
|
+
stream: any;
|
|
1221
|
+
lastKey: string;
|
|
1222
|
+
fsmResult: FSM.FsmArray;
|
|
1223
|
+
|
|
1224
|
+
constructor(env: Environment, table: any)
|
|
1225
|
+
{
|
|
1226
|
+
super(env);
|
|
1227
|
+
this.table = table;
|
|
1228
|
+
this.fsmResult = new FSM.FsmArray(env);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
get env(): EnvironmentEx { return this._env as EnvironmentEx }
|
|
1232
|
+
|
|
1233
|
+
tick(): void
|
|
1234
|
+
{
|
|
1235
|
+
if (this.ready)
|
|
1236
|
+
{
|
|
1237
|
+
switch (this.state)
|
|
1238
|
+
{
|
|
1239
|
+
case FSM.FSM_STARTING:
|
|
1240
|
+
this.setState(FSM_LISTING);
|
|
1241
|
+
this.env.dbx.dynamostream.listStreams({ TableName: this.table.TableName}, (err: any, result: any) => {
|
|
1242
|
+
if (err)
|
|
1243
|
+
{
|
|
1244
|
+
console.log(`dynamodb: listStreams error: ${JSON.stringify(err)}`);
|
|
1245
|
+
this.env.log.error({ event: 'dynamodb: listStreams', detail: JSON.stringify(err) });
|
|
1246
|
+
this.fsmResult.setState(FSM.FSM_ERROR);
|
|
1247
|
+
this.setState(FSM.FSM_ERROR);
|
|
1248
|
+
}
|
|
1249
|
+
else
|
|
1250
|
+
{
|
|
1251
|
+
if (result.Streams && result.Streams.length > 0)
|
|
1252
|
+
{
|
|
1253
|
+
this.stream = result.Streams[0];
|
|
1254
|
+
this.setState(FSM_DESCRIBE);
|
|
1255
|
+
}
|
|
1256
|
+
else
|
|
1257
|
+
{
|
|
1258
|
+
this.setState(FSM.FSM_DONE);
|
|
1259
|
+
this.fsmResult.setState(FSM.FSM_DONE);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
break;
|
|
1264
|
+
|
|
1265
|
+
case FSM_LISTING:
|
|
1266
|
+
case FSM_DESCRIBING:
|
|
1267
|
+
// Come out of this state in the callback
|
|
1268
|
+
break;
|
|
1269
|
+
|
|
1270
|
+
case FSM_DESCRIBE:
|
|
1271
|
+
this.setState(FSM_DESCRIBING);
|
|
1272
|
+
let params: any = { StreamArn: this.stream.StreamArn };
|
|
1273
|
+
if (this.lastKey)
|
|
1274
|
+
{
|
|
1275
|
+
params.ExclusiveStartShardId = this.lastKey;
|
|
1276
|
+
delete this.lastKey;
|
|
1277
|
+
}
|
|
1278
|
+
this.env.dbx.dynamostream.describeStream(params, (err: any, result: any) => {
|
|
1279
|
+
if (err)
|
|
1280
|
+
{
|
|
1281
|
+
console.log(`dynamodb: describeStream failure: ${JSON.stringify(err)}`);
|
|
1282
|
+
this.env.log.error({ event: 'dynamodb: describeStream', detail: JSON.stringify(err) });
|
|
1283
|
+
this.setState(FSM.FSM_ERROR);
|
|
1284
|
+
}
|
|
1285
|
+
else
|
|
1286
|
+
{
|
|
1287
|
+
Util.shallowAssign(this.stream, result.StreamDescription);
|
|
1288
|
+
if (this.stream.Shards)
|
|
1289
|
+
{
|
|
1290
|
+
this.stream.Shards.forEach((shard: ShardDescription) => {
|
|
1291
|
+
this.fsmResult.push(shard);
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
if (this.stream.LastEvaluatedShardId)
|
|
1295
|
+
{
|
|
1296
|
+
this.lastKey = this.stream.LastEvaluatedShardId;
|
|
1297
|
+
delete this.stream.LastEvaluatedShardId;
|
|
1298
|
+
this.setState(FSM_DESCRIBE);
|
|
1299
|
+
}
|
|
1300
|
+
else
|
|
1301
|
+
{
|
|
1302
|
+
this.fsmResult.setState(FSM.FSM_DONE);
|
|
1303
|
+
this.setState(FSM.FSM_DONE);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
class KeySet implements FSM.ISet
|
|
1314
|
+
{
|
|
1315
|
+
keyschema: any[];
|
|
1316
|
+
set: any;
|
|
1317
|
+
|
|
1318
|
+
constructor(col: DynamoCollection)
|
|
1319
|
+
{
|
|
1320
|
+
this.keyschema = col.options.KeySchema;
|
|
1321
|
+
this.set = {};
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
test(o: any): boolean
|
|
1325
|
+
{
|
|
1326
|
+
let key: string = '';
|
|
1327
|
+
this.keyschema.forEach((p: any) => { if (o[p.AttributeName] !== undefined) key += String(o[p.AttributeName]); });
|
|
1328
|
+
let b = this.set[key] !== undefined;
|
|
1329
|
+
this.set[key] = true;
|
|
1330
|
+
return b;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
reset(): void
|
|
1334
|
+
{
|
|
1335
|
+
this.set = {};
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const FSM_READING = FSM.FSM_CUSTOM4;
|
|
1340
|
+
const FSM_NEXTSHARD = FSM.FSM_CUSTOM5;
|
|
1341
|
+
const FSM_GETITERATOR = FSM.FSM_CUSTOM6;
|
|
1342
|
+
const FSM_CALLITERATOR = FSM.FSM_CUSTOM7;
|
|
1343
|
+
const FSM_GETRECORDS = FSM.FSM_CUSTOM8;
|
|
1344
|
+
const FSM_LOOPING = FSM.FSM_CUSTOM9;
|
|
1345
|
+
const MinLoopInterval = 1000 * 60 * 5;
|
|
1346
|
+
const MaxShardInterval = 1000 * 60 * 2;
|
|
1347
|
+
const MaxGetRecordRetries = 100;
|
|
1348
|
+
|
|
1349
|
+
class FsmReadOneShard extends FSM.Fsm
|
|
1350
|
+
{
|
|
1351
|
+
col: DynamoCollection;
|
|
1352
|
+
stream: any;
|
|
1353
|
+
shard: ShardDescription;
|
|
1354
|
+
shardClosed: boolean;
|
|
1355
|
+
fsmResult: FSM.FsmArray;
|
|
1356
|
+
elapsed: Util.Elapsed;
|
|
1357
|
+
nTries: number;
|
|
1358
|
+
deadline: Deadline;
|
|
1359
|
+
|
|
1360
|
+
constructor(env: Environment, col: DynamoCollection, stream: any, shard: ShardDescription, fsmResult: FSM.FsmArray)
|
|
1361
|
+
{
|
|
1362
|
+
super(env);
|
|
1363
|
+
this.col = col;
|
|
1364
|
+
this.stream = stream;
|
|
1365
|
+
this.shard = shard;
|
|
1366
|
+
this.fsmResult = fsmResult;
|
|
1367
|
+
this.elapsed = new Util.Elapsed();
|
|
1368
|
+
this.nTries = 0;
|
|
1369
|
+
this.shardClosed = this.shard.SequenceNumberRange.EndingSequenceNumber !== undefined;
|
|
1370
|
+
this.deadline = new Deadline(MaxShardInterval);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
get env(): EnvironmentEx { return this._env as EnvironmentEx }
|
|
1374
|
+
|
|
1375
|
+
tick(): void
|
|
1376
|
+
{
|
|
1377
|
+
if (this.ready && this.isDependentError)
|
|
1378
|
+
this.setState(FSM.FSM_ERROR);
|
|
1379
|
+
else if (this.ready)
|
|
1380
|
+
{
|
|
1381
|
+
switch (this.state)
|
|
1382
|
+
{
|
|
1383
|
+
case FSM.FSM_STARTING:
|
|
1384
|
+
this.deadline.start();
|
|
1385
|
+
this.setState(FSM_GETITERATOR);
|
|
1386
|
+
let iterParams: any = {
|
|
1387
|
+
ShardId: this.shard.ShardId,
|
|
1388
|
+
ShardIteratorType: 'TRIM_HORIZON',
|
|
1389
|
+
StreamArn: this.stream.StreamArn
|
|
1390
|
+
};
|
|
1391
|
+
if (this.shard.LastSequenceNumber !== undefined)
|
|
1392
|
+
{
|
|
1393
|
+
iterParams.ShardIteratorType = 'AFTER_SEQUENCE_NUMBER';
|
|
1394
|
+
iterParams.SequenceNumber = this.shard.LastSequenceNumber;
|
|
1395
|
+
}
|
|
1396
|
+
this.env.dbx.dynamostream.getShardIterator(iterParams, (err: any, result: any) => {
|
|
1397
|
+
// Cancel by externally setting to DONE
|
|
1398
|
+
if (this.done)
|
|
1399
|
+
return;
|
|
1400
|
+
if (err)
|
|
1401
|
+
{
|
|
1402
|
+
console.log(`dynamodb: getShardIterator: failure: ${JSON.stringify(err)}`);
|
|
1403
|
+
this.env.log.error({ event: 'dynamodb: getShardIterator', detail: JSON.stringify(err) });
|
|
1404
|
+
this.setState(FSM.FSM_ERROR);
|
|
1405
|
+
}
|
|
1406
|
+
else
|
|
1407
|
+
{
|
|
1408
|
+
this.shard.ShardIterator = result.ShardIterator;
|
|
1409
|
+
this.setState(FSM_CALLITERATOR);
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
break;
|
|
1413
|
+
|
|
1414
|
+
case FSM_CALLITERATOR:
|
|
1415
|
+
this.elapsed.start();
|
|
1416
|
+
this.setState(FSM_GETRECORDS);
|
|
1417
|
+
let recordParams: any = { ShardIterator: this.shard.ShardIterator };
|
|
1418
|
+
this.env.dbx.dynamostream.getRecords(recordParams, (err: any, result: any) => {
|
|
1419
|
+
// Cancel by externally setting to DONE
|
|
1420
|
+
if (this.done)
|
|
1421
|
+
return;
|
|
1422
|
+
if (err)
|
|
1423
|
+
{
|
|
1424
|
+
console.log(`dynamodb: getRecords: failure: ${JSON.stringify(err)}`);
|
|
1425
|
+
this.env.log.error({ event: 'dynamodb: getRecords', detail: JSON.stringify(err) });
|
|
1426
|
+
this.setState(FSM.FSM_ERROR);
|
|
1427
|
+
}
|
|
1428
|
+
else
|
|
1429
|
+
{
|
|
1430
|
+
this.fsmResult.concat(result.Records.map((o: any) => this.col.toExternal(o.dynamodb.Keys) ));
|
|
1431
|
+
if (result.Records.length > 0)
|
|
1432
|
+
{
|
|
1433
|
+
let r: any = result.Records[result.Records.length-1];
|
|
1434
|
+
this.shard.LastSequenceNumber = r.dynamodb.SequenceNumber;
|
|
1435
|
+
}
|
|
1436
|
+
if (result.NextShardIterator)
|
|
1437
|
+
{
|
|
1438
|
+
this.nTries++;
|
|
1439
|
+
this.shard.ShardIterator = result.NextShardIterator;
|
|
1440
|
+
let bContinue = this.shardClosed || (this.nTries <= MaxGetRecordRetries && !this.deadline.passed);
|
|
1441
|
+
this.setState(bContinue ? FSM_CALLITERATOR : FSM.FSM_DONE);
|
|
1442
|
+
}
|
|
1443
|
+
else
|
|
1444
|
+
this.setState(FSM.FSM_DONE);
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
export class FsmTableStream extends FSM.Fsm
|
|
1454
|
+
{
|
|
1455
|
+
col: DynamoCollection;
|
|
1456
|
+
table: any;
|
|
1457
|
+
fsmShards: FsmTableShards;
|
|
1458
|
+
shardsDone: any;
|
|
1459
|
+
shardsLast: any;
|
|
1460
|
+
shardsQueue: FsmReadOneShard[];
|
|
1461
|
+
fsmResult: FSM.FsmArray;
|
|
1462
|
+
deadline: Deadline
|
|
1463
|
+
|
|
1464
|
+
constructor(env: Environment, col: DB.DBCollection)
|
|
1465
|
+
{
|
|
1466
|
+
super(env);
|
|
1467
|
+
this.col = col as DynamoCollection;
|
|
1468
|
+
this.table = col.col;
|
|
1469
|
+
this.shardsDone = {};
|
|
1470
|
+
this.shardsLast = {};
|
|
1471
|
+
this.shardsQueue = [];
|
|
1472
|
+
this.fsmResult = new FSM.FsmArray(env, new KeySet(this.col));
|
|
1473
|
+
this.deadline = new Deadline(MinLoopInterval);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
get env(): Environment { return this._env as Environment }
|
|
1477
|
+
|
|
1478
|
+
end(): void
|
|
1479
|
+
{
|
|
1480
|
+
this.shardsDone = {};
|
|
1481
|
+
this.shardsLast = {};
|
|
1482
|
+
this.shardsQueue = [];
|
|
1483
|
+
this.setState(FSM.FSM_DONE);
|
|
1484
|
+
this.fsmResult.setState(FSM.FSM_DONE);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
error(): void
|
|
1488
|
+
{
|
|
1489
|
+
this.clearDependentError();
|
|
1490
|
+
this.setState(FSM_LOOPING);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
tick(): void
|
|
1494
|
+
{
|
|
1495
|
+
if (this.ready && this.isDependentError)
|
|
1496
|
+
this.error();
|
|
1497
|
+
else if (this.ready)
|
|
1498
|
+
{
|
|
1499
|
+
switch (this.state)
|
|
1500
|
+
{
|
|
1501
|
+
case FSM_LOOPING:
|
|
1502
|
+
let msLeft = this.deadline.msRemaining;
|
|
1503
|
+
if (msLeft > 0)
|
|
1504
|
+
this.waitOn(new FSM.FsmSleep(this.env, msLeft));
|
|
1505
|
+
this.setState(FSM.FSM_STARTING);
|
|
1506
|
+
break;
|
|
1507
|
+
|
|
1508
|
+
case FSM.FSM_STARTING:
|
|
1509
|
+
this.deadline.start();
|
|
1510
|
+
this.setState(FSM_LISTING);
|
|
1511
|
+
this.shardsQueue = [];
|
|
1512
|
+
this.fsmShards = new FsmTableShards(this.env, this.table);
|
|
1513
|
+
this.waitOn(this.fsmShards.fsmResult);
|
|
1514
|
+
break;
|
|
1515
|
+
|
|
1516
|
+
case FSM_LISTING:
|
|
1517
|
+
this.fsmShards.fsmResult.a.forEach((shard: ShardDescription) => {
|
|
1518
|
+
if (this.shardsDone[shard.ShardId] === undefined)
|
|
1519
|
+
{
|
|
1520
|
+
if (this.shardsLast[shard.ShardId])
|
|
1521
|
+
shard.LastSequenceNumber = this.shardsLast[shard.ShardId];
|
|
1522
|
+
let fsmReadOne = new FsmReadOneShard(this.env, this.col, this.fsmShards.stream, shard, this.fsmResult);
|
|
1523
|
+
this.waitOn(fsmReadOne);
|
|
1524
|
+
this.shardsQueue.push(fsmReadOne);
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
// Make sure child shard processed before parent shard
|
|
1529
|
+
// TODO: not concerned about record ordering here
|
|
1530
|
+
//this.shardsQueue.sort(shardSort);
|
|
1531
|
+
|
|
1532
|
+
// If still listing shards, re-wait on the result set and stay in this state
|
|
1533
|
+
this.fsmShards.fsmResult.reset();
|
|
1534
|
+
if (! this.fsmShards.done)
|
|
1535
|
+
this.waitOn(this.fsmShards.fsmResult);
|
|
1536
|
+
else
|
|
1537
|
+
this.setState(FSM_READING);
|
|
1538
|
+
break;
|
|
1539
|
+
|
|
1540
|
+
case FSM_READING:
|
|
1541
|
+
this.shardsQueue.forEach((fsmReadOne: FsmReadOneShard) => {
|
|
1542
|
+
if (!fsmReadOne.iserror && fsmReadOne.shardClosed)
|
|
1543
|
+
this.shardsDone[fsmReadOne.shard.ShardId] = true;
|
|
1544
|
+
this.shardsLast[fsmReadOne.shard.ShardId] = fsmReadOne.shard.LastSequenceNumber;
|
|
1545
|
+
});
|
|
1546
|
+
this.setState(FSM_LOOPING);
|
|
1547
|
+
break;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
}
|