@dra2020/baseclient 1.0.13 → 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.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/all/all.d.ts +36 -0
  4. package/dist/all/allclient.d.ts +18 -0
  5. package/dist/base.js +33010 -0
  6. package/dist/base.js.map +1 -0
  7. package/dist/baseclient.js +8991 -0
  8. package/dist/baseclient.js.map +1 -0
  9. package/dist/context/all.d.ts +1 -0
  10. package/dist/context/context.d.ts +13 -0
  11. package/dist/dbabstract/all.d.ts +1 -0
  12. package/dist/dbabstract/db.d.ts +83 -0
  13. package/dist/dbdynamo/all.d.ts +1 -0
  14. package/dist/dbdynamo/dbdynamo.d.ts +190 -0
  15. package/dist/filterexpr/all.d.ts +1 -0
  16. package/dist/filterexpr/filterexpr.d.ts +64 -0
  17. package/dist/fsm/all.d.ts +1 -0
  18. package/dist/fsm/fsm.d.ts +118 -0
  19. package/dist/fsmfile/all.d.ts +1 -0
  20. package/dist/fsmfile/fsmfile.d.ts +47 -0
  21. package/dist/jsonstream/all.d.ts +1 -0
  22. package/dist/jsonstream/jsonstream.d.ts +130 -0
  23. package/dist/lambda/all.d.ts +1 -0
  24. package/dist/lambda/env.d.ts +10 -0
  25. package/dist/lambda/lambda.d.ts +18 -0
  26. package/dist/logabstract/all.d.ts +1 -0
  27. package/dist/logabstract/log.d.ts +26 -0
  28. package/dist/logclient/all.d.ts +1 -0
  29. package/dist/logclient/log.d.ts +6 -0
  30. package/dist/logserver/all.d.ts +5 -0
  31. package/dist/logserver/log.d.ts +11 -0
  32. package/dist/logserver/logaccum.d.ts +154 -0
  33. package/dist/logserver/logblob.d.ts +24 -0
  34. package/dist/logserver/logconcat.d.ts +55 -0
  35. package/dist/logserver/logkey.d.ts +28 -0
  36. package/dist/memsqs/all.d.ts +4 -0
  37. package/dist/memsqs/client.d.ts +13 -0
  38. package/dist/memsqs/loopback.d.ts +11 -0
  39. package/dist/memsqs/orderedlist.d.ts +19 -0
  40. package/dist/memsqs/queue.d.ts +84 -0
  41. package/dist/memsqs/server.d.ts +37 -0
  42. package/dist/ot-editutil/all.d.ts +2 -0
  43. package/dist/ot-editutil/oteditutil.d.ts +14 -0
  44. package/dist/ot-editutil/otmaputil.d.ts +21 -0
  45. package/dist/ot-js/all.d.ts +9 -0
  46. package/dist/ot-js/otarray.d.ts +111 -0
  47. package/dist/ot-js/otclientengine.d.ts +38 -0
  48. package/dist/ot-js/otcomposite.d.ts +37 -0
  49. package/dist/ot-js/otcounter.d.ts +17 -0
  50. package/dist/ot-js/otengine.d.ts +22 -0
  51. package/dist/ot-js/otmap.d.ts +19 -0
  52. package/dist/ot-js/otserverengine.d.ts +38 -0
  53. package/dist/ot-js/otsession.d.ts +111 -0
  54. package/dist/ot-js/ottypes.d.ts +29 -0
  55. package/dist/poly/all.d.ts +15 -0
  56. package/dist/poly/blend.d.ts +1 -0
  57. package/dist/poly/boundbox.d.ts +16 -0
  58. package/dist/poly/cartesian.d.ts +5 -0
  59. package/dist/poly/graham-scan.d.ts +8 -0
  60. package/dist/poly/hash.d.ts +1 -0
  61. package/dist/poly/matrix.d.ts +24 -0
  62. package/dist/poly/minbound.d.ts +1 -0
  63. package/dist/poly/poly.d.ts +52 -0
  64. package/dist/poly/polybin.d.ts +5 -0
  65. package/dist/poly/polylabel.d.ts +7 -0
  66. package/dist/poly/polypack.d.ts +30 -0
  67. package/dist/poly/polyround.d.ts +1 -0
  68. package/dist/poly/polysimplify.d.ts +1 -0
  69. package/dist/poly/quad.d.ts +48 -0
  70. package/dist/poly/selfintersect.d.ts +1 -0
  71. package/dist/poly/shamos.d.ts +1 -0
  72. package/dist/poly/simplify.d.ts +2 -0
  73. package/dist/poly/topo.d.ts +46 -0
  74. package/dist/poly/union.d.ts +48 -0
  75. package/dist/storage/all.d.ts +4 -0
  76. package/dist/storage/datablob.d.ts +9 -0
  77. package/dist/storage/env.d.ts +10 -0
  78. package/dist/storage/splitsblob.d.ts +13 -0
  79. package/dist/storage/storage.d.ts +166 -0
  80. package/dist/storages3/all.d.ts +1 -0
  81. package/dist/storages3/s3.d.ts +62 -0
  82. package/dist/util/all.d.ts +5 -0
  83. package/dist/util/bintrie.d.ts +93 -0
  84. package/dist/util/countedhash.d.ts +19 -0
  85. package/dist/util/gradient.d.ts +15 -0
  86. package/dist/util/indexedarray.d.ts +15 -0
  87. package/dist/util/util.d.ts +68 -0
  88. package/docs/context.md +2 -0
  89. package/docs/dbabstract.md +2 -0
  90. package/docs/dbdynamo.md +2 -0
  91. package/docs/fsm.md +243 -0
  92. package/docs/fsmfile.md +2 -0
  93. package/docs/jsonstream.md +44 -0
  94. package/docs/lambda.md +2 -0
  95. package/docs/logabstract.md +2 -0
  96. package/docs/logclient.md +2 -0
  97. package/docs/logserver.md +2 -0
  98. package/docs/ot-editutil.md +2 -0
  99. package/docs/ot-js.md +95 -0
  100. package/docs/poly.md +103 -0
  101. package/docs/storage.md +2 -0
  102. package/docs/storages3.md +2 -0
  103. package/docs/util.md +2 -0
  104. package/lib/all/all.ts +41 -0
  105. package/lib/all/allclient.ts +19 -0
  106. package/lib/context/all.ts +1 -0
  107. package/lib/context/context.ts +82 -0
  108. package/lib/dbabstract/all.ts +1 -0
  109. package/lib/dbabstract/db.ts +246 -0
  110. package/lib/dbdynamo/all.ts +1 -0
  111. package/lib/dbdynamo/dbdynamo.ts +1551 -0
  112. package/lib/filterexpr/all.ts +1 -0
  113. package/lib/filterexpr/filterexpr.ts +625 -0
  114. package/lib/fsm/all.ts +1 -0
  115. package/lib/fsm/fsm.ts +549 -0
  116. package/lib/fsmfile/all.ts +1 -0
  117. package/lib/fsmfile/fsmfile.ts +236 -0
  118. package/lib/jsonstream/all.ts +1 -0
  119. package/lib/jsonstream/jsonstream.ts +940 -0
  120. package/lib/lambda/all.ts +1 -0
  121. package/lib/lambda/env.ts +13 -0
  122. package/lib/lambda/lambda.ts +120 -0
  123. package/lib/logabstract/all.ts +1 -0
  124. package/lib/logabstract/log.ts +55 -0
  125. package/lib/logclient/all.ts +1 -0
  126. package/lib/logclient/log.ts +105 -0
  127. package/lib/logserver/all.ts +5 -0
  128. package/lib/logserver/log.ts +565 -0
  129. package/lib/logserver/logaccum.ts +1445 -0
  130. package/lib/logserver/logblob.ts +84 -0
  131. package/lib/logserver/logconcat.ts +313 -0
  132. package/lib/logserver/logkey.ts +125 -0
  133. package/lib/memsqs/all.ts +4 -0
  134. package/lib/memsqs/client.ts +268 -0
  135. package/lib/memsqs/loopback.ts +64 -0
  136. package/lib/memsqs/orderedlist.ts +74 -0
  137. package/lib/memsqs/queue.ts +395 -0
  138. package/lib/memsqs/server.ts +262 -0
  139. package/lib/ot-editutil/all.ts +2 -0
  140. package/lib/ot-editutil/oteditutil.ts +180 -0
  141. package/lib/ot-editutil/otmaputil.ts +209 -0
  142. package/lib/ot-js/all.ts +9 -0
  143. package/lib/ot-js/otarray.ts +1168 -0
  144. package/lib/ot-js/otclientengine.ts +327 -0
  145. package/lib/ot-js/otcomposite.ts +247 -0
  146. package/lib/ot-js/otcounter.ts +145 -0
  147. package/lib/ot-js/otengine.ts +71 -0
  148. package/lib/ot-js/otmap.ts +144 -0
  149. package/lib/ot-js/otserverengine.ts +329 -0
  150. package/lib/ot-js/otsession.ts +199 -0
  151. package/lib/ot-js/ottypes.ts +98 -0
  152. package/lib/poly/all.ts +15 -0
  153. package/lib/poly/blend.ts +27 -0
  154. package/lib/poly/boundbox.ts +102 -0
  155. package/lib/poly/cartesian.ts +130 -0
  156. package/lib/poly/graham-scan.ts +401 -0
  157. package/lib/poly/hash.ts +15 -0
  158. package/lib/poly/matrix.ts +309 -0
  159. package/lib/poly/minbound.ts +211 -0
  160. package/lib/poly/poly.ts +767 -0
  161. package/lib/poly/polybin.ts +218 -0
  162. package/lib/poly/polylabel.ts +204 -0
  163. package/lib/poly/polypack.ts +458 -0
  164. package/lib/poly/polyround.ts +30 -0
  165. package/lib/poly/polysimplify.ts +24 -0
  166. package/lib/poly/quad.ts +272 -0
  167. package/lib/poly/selfintersect.ts +87 -0
  168. package/lib/poly/shamos.ts +297 -0
  169. package/lib/poly/simplify.ts +119 -0
  170. package/lib/poly/topo.ts +525 -0
  171. package/lib/poly/union.ts +371 -0
  172. package/lib/storage/all.ts +4 -0
  173. package/lib/storage/datablob.ts +36 -0
  174. package/lib/storage/env.ts +14 -0
  175. package/lib/storage/splitsblob.ts +63 -0
  176. package/lib/storage/storage.ts +604 -0
  177. package/lib/storages3/all.ts +1 -0
  178. package/lib/storages3/s3.ts +576 -0
  179. package/lib/util/all.ts +5 -0
  180. package/lib/util/bintrie.ts +603 -0
  181. package/lib/util/countedhash.ts +83 -0
  182. package/lib/util/gradient.ts +108 -0
  183. package/lib/util/indexedarray.ts +80 -0
  184. package/lib/util/util.ts +695 -0
  185. package/package.json +8 -8
@@ -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
+ }