@naanlang/naan 1.0.2 → 1.0.5
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/LICENSE.md +1 -1
- package/README.md +10 -8
- package/bin/index.js +2 -2
- package/dist/env_web.js +172 -22
- package/dist/naan.min.js +6 -6
- package/frameworks/browser/https_request.nlg +120 -0
- package/frameworks/browser/sworker.js +299 -91
- package/frameworks/browser/terminals.nlg +28 -13
- package/frameworks/browser/workers.nlg +27 -12
- package/frameworks/client/apiclient.nlg +17 -4
- package/frameworks/client/psm_client.nlg +184 -81
- package/frameworks/common/common.nlg +105 -25
- package/frameworks/common/preferences.nlg +1 -0
- package/frameworks/common/watching.nlg +1 -0
- package/frameworks/node/apiserver.nlg +31 -15
- package/frameworks/node/filesystem.nlg +230 -46
- package/frameworks/node/gitter.nlg +27 -6
- package/frameworks/node/https_request.nlg +119 -0
- package/frameworks/node/node.nlg +1 -0
- package/frameworks/node/psm_server.nlg +113 -37
- package/frameworks/project/build.nlg +40 -23
- package/frameworks/project/proj_cliser.nlg +11 -3
- package/frameworks/project/proj_console.nlg +13 -4
- package/frameworks/project/proj_folder.nlg +10 -1
- package/frameworks/project/proj_lambda.nlg +13 -4
- package/frameworks/project/proj_static.nlg +11 -3
- package/frameworks/project/projects.nlg +128 -40
- package/frameworks/running/debugnub.nlg +2 -5
- package/frameworks/running/executors.nlg +1 -0
- package/frameworks/storage/csv.nlg +120 -0
- package/frameworks/storage/dbt_pouch.nlg +41 -25
- package/frameworks/storage/psm.nlg +108 -28
- package/frameworks/storage/psm_dbtables.nlg +7 -3
- package/frameworks/storage/resources.nlg +30 -2
- package/lib/browser/env_web.js +170 -20
- package/lib/browser/env_webworker.js +1 -1
- package/lib/browser/require.js +1 -1
- package/lib/core/naanlib.js +6 -6
- package/package.json +2 -1
- package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
- package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +8 -7
- package/plugins/serviceAws/aws_dynamo.nlg +715 -148
- package/plugins/serviceAws/aws_dynextra.nlg +294 -0
- package/plugins/serviceAws/aws_lambda.nlg +11 -4
- package/plugins/serviceAws/aws_s3.nlg +44 -14
- package/plugins/serviceAws/dbt_aws.nlg +58 -45
- package/plugins/serviceAws/psm_aws.nlg +61 -40
- package/plugins/serviceAws/serviceAws.nlg +7 -4
- package/plugins/serviceGitHub/psm_github.nlg +41 -25
- package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
- package/test/harness.nlg +3 -1
- package/test/test_01_core.nlg +5 -5
- package/test/test_02_context.nlg +4 -4
- package/test/test_03_jsinterop.nlg +9 -3
- package/plugins/serviceAws/aws_cognito.nlg +0 -76
|
@@ -6,26 +6,164 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2021 by Richard C. Zulch
|
|
9
|
+
* Copyright (c) 2021-2022 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/*
|
|
15
|
-
*
|
|
15
|
+
* TimeKeys
|
|
16
16
|
*
|
|
17
|
-
* DynamoDB
|
|
17
|
+
* TimeKeys implement a time-ordered key paradigm for DynamoDB and similar databases. Each item
|
|
18
|
+
* stored under a timeKey is assigned an ID comprising a timestamp and a random suffix derived from
|
|
19
|
+
* a version-4 UUID. The ID has a 26-character format: <13-digit timestamp>-<12-character-random>.
|
|
20
|
+
* A prefix option inserts a fixed-length string before the timestamp, enabling categorization when
|
|
21
|
+
* multiple datatypes inhabit the same keyspace. From the ID is derived a partition key in the form
|
|
22
|
+
* YYYYDDD, where DDD is the day of the year from 1 to 365. TimeKeys have the following useful
|
|
23
|
+
* properties:
|
|
24
|
+
* - IDs sort in date order
|
|
25
|
+
* - IDs allow direct access
|
|
26
|
+
* - Over time IDs become distributed over partitions
|
|
27
|
+
* - Can iterate partitions to query longer timeframes
|
|
28
|
+
*
|
|
29
|
+
* The principal challenge is that IDs can cause a hot partition problem if you try to create them
|
|
30
|
+
* too quickly, because they all share the same day. Likewise if you access recently-created IDs
|
|
31
|
+
* frequently. In that case a separate table for today's items might be preferable.
|
|
32
|
+
* A potential challenge is that rarely-created keys will be sparsely distributed among the
|
|
33
|
+
* potential partition keys. For data is not created every day, you may want to cache the available
|
|
34
|
+
* days, or choose a different key paradigm.
|
|
18
35
|
*
|
|
19
36
|
*/
|
|
20
37
|
|
|
21
|
-
closure
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
closure TimeKeys(prefix, baseMS, local timekeys, prelen) {
|
|
39
|
+
if !UUID.proc
|
|
40
|
+
throw("TimeKeys requires UUID()")
|
|
41
|
+
timekeys = new(object, this)
|
|
42
|
+
if !prefix
|
|
43
|
+
prefix = ""
|
|
44
|
+
prelen = prefix.length
|
|
45
|
+
if !baseMS
|
|
46
|
+
baseMS = 1640995200000 // 01/01/2022 UTC - earliest valid date
|
|
47
|
+
|
|
48
|
+
// utcYearStartMS
|
|
49
|
+
//
|
|
50
|
+
// Get the millisecond timestamp for the start of the given UTC year.
|
|
51
|
+
//
|
|
52
|
+
function utcYearStartMS(year) {
|
|
53
|
+
Date("01/01/".concat(year, "GMT")).getTime()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// makePartID
|
|
57
|
+
//
|
|
58
|
+
// Return the partition ID for a millisecond timestamp, in the form YYYYDDD where DDD is the day
|
|
59
|
+
// of the year from 001 to 365. If the timestamp is invalid it returns false.
|
|
60
|
+
//
|
|
61
|
+
timekeys.makePartID = function makePartID(msec, local tsdate, yyyy, doy) {
|
|
62
|
+
if msec >= baseMS {
|
|
63
|
+
tsdate = Date(msec)
|
|
64
|
+
yyyy = tsdate.getUTCFullYear()
|
|
65
|
+
doy = Math.floor((msec - utcYearStartMS(yyyy))/(1000*3600*24))
|
|
66
|
+
prefix.concat(yyyy.tostring, "000".concat(doy+1).slice(-3))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// comparePartID
|
|
71
|
+
//
|
|
72
|
+
// Compare two partID values, returning -1, 0, 1 if the first is <, ==, or > to the second.
|
|
73
|
+
//
|
|
74
|
+
timekeys.comparePartID = function comparePartID(a, b) {
|
|
75
|
+
a = a.slice(prelen)
|
|
76
|
+
b = b.slice(prelen)
|
|
77
|
+
if (a < b)
|
|
78
|
+
-1
|
|
79
|
+
else if a > b
|
|
80
|
+
1
|
|
81
|
+
else
|
|
82
|
+
0
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// adjustPartID
|
|
86
|
+
//
|
|
87
|
+
// Increment or decrement the partition key by the specified number of days returning the next
|
|
88
|
+
// partition key in the specified direction, or false if there are no more keys. Dates in the
|
|
89
|
+
// future are considered invalid and return false.
|
|
90
|
+
//
|
|
91
|
+
timekeys.adjustPartID = function adjustPartID(partID, adjust, local year, doy, dayms, msec) {
|
|
92
|
+
partID = partID.slice(prelen)
|
|
93
|
+
yyyy = toint(partID.substring(0,4))
|
|
94
|
+
doy = toint(partID.substring(4,7))
|
|
95
|
+
dayms = 1000*3600*24
|
|
96
|
+
msec = utcYearStartMS(yyyy) + (doy-1) * dayms
|
|
97
|
+
msec += adjust * dayms
|
|
98
|
+
if msec < Date.now()
|
|
99
|
+
makePartID(msec)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// partIDfromTimeID
|
|
103
|
+
//
|
|
104
|
+
// Return the partID for a timeID.
|
|
105
|
+
//
|
|
106
|
+
timekeys.partIDfromTimeID = function partIDfromTimeID(timeID) {
|
|
107
|
+
makePartID(timeIDtimestamp(timeID))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// newTimeID
|
|
111
|
+
//
|
|
112
|
+
// Return a new, unique timeID for the present moment.
|
|
113
|
+
//
|
|
114
|
+
timekeys.newTimeID = function newTimeID(now) {
|
|
115
|
+
if !now
|
|
116
|
+
now = Date.now()
|
|
117
|
+
prefix.concat(now.tostring, UUID().slice(-13))
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// isTimeID
|
|
121
|
+
//
|
|
122
|
+
// Return true iff this looks like our timeID with the right prefix, as opposed to a timestamp.
|
|
123
|
+
//
|
|
124
|
+
timekeys.isTimeID = function isTimeID(tstid) {
|
|
125
|
+
string(tstid) && tstid.startsWith(prefix) && tstid.length-prelen == 26
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// timeIDtimestamp
|
|
129
|
+
//
|
|
130
|
+
// Return the numeric millisecond timestamp for a timeID.
|
|
131
|
+
//
|
|
132
|
+
timekeys.timeIDtimestamp = function timeIDtimestamp(timeID) {
|
|
133
|
+
toint(timeID.slice(prelen, prelen+13))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// makeTimeKey
|
|
137
|
+
//
|
|
138
|
+
// Make a range key from a timestamp, creating the earliest possible range key, which will sort
|
|
139
|
+
// earlier than all possible timeIDs for that timestamp.
|
|
140
|
+
//
|
|
141
|
+
timekeys.makeTimeKey = function makeTimeKey(timestamp) {
|
|
142
|
+
if !integer(timestamp) || timestamp < baseMS
|
|
143
|
+
timestamp = baseMS
|
|
144
|
+
prefix.concat(timestamp.tostring, "-", space.repeat(12))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// finis
|
|
148
|
+
|
|
149
|
+
timekeys
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* DynaConverter
|
|
155
|
+
*
|
|
156
|
+
* Translate between Naan and DynamoDB.
|
|
157
|
+
*
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
closure DynaConverter(local conv) {
|
|
161
|
+
conv = new(object, this)
|
|
24
162
|
|
|
25
163
|
// fieldTypeDynToNaan
|
|
26
164
|
// Convert field type from DynamoDB to Naan
|
|
27
165
|
//
|
|
28
|
-
function fieldTypeDynToNaan(dtype) {
|
|
166
|
+
conv.fieldTypeDynToNaan = function fieldTypeDynToNaan(dtype) {
|
|
29
167
|
if dtype == "S",
|
|
30
168
|
"utf8"
|
|
31
169
|
else if dtype == "N",
|
|
@@ -39,7 +177,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
39
177
|
// fieldTypeNaanToDyn
|
|
40
178
|
// Convert field type from Naan to DynamoDB
|
|
41
179
|
//
|
|
42
|
-
function fieldTypeNaanToDyn(ntype) {
|
|
180
|
+
conv.fieldTypeNaanToDyn = function fieldTypeNaanToDyn(ntype) {
|
|
43
181
|
if ntype == "utf8",
|
|
44
182
|
"S"
|
|
45
183
|
else if ntype == "numeric",
|
|
@@ -53,7 +191,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
53
191
|
// datumNaanToDyn
|
|
54
192
|
// Convert a Naan datum to DynamoDB format.
|
|
55
193
|
//
|
|
56
|
-
function datumNaanToDyn(datum, local output, ntype, dtype) {
|
|
194
|
+
conv.datumNaanToDyn = function datumNaanToDyn(datum, local output, ntype, dtype) {
|
|
57
195
|
output = { }
|
|
58
196
|
if tuple(datum)
|
|
59
197
|
datum = datum.toarray
|
|
@@ -84,11 +222,12 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
84
222
|
else if datum === true || datum === false
|
|
85
223
|
dtype = "BOOL"
|
|
86
224
|
else if datum === undefined
|
|
87
|
-
|
|
225
|
+
return (undefined) // Zen!
|
|
88
226
|
else
|
|
89
|
-
|
|
227
|
+
return (datum.tostring)
|
|
228
|
+
|
|
90
229
|
} else
|
|
91
|
-
debuglog("
|
|
230
|
+
debuglog("DynaConverter.datumNaanToDyn: invalid type", ntype)
|
|
92
231
|
}
|
|
93
232
|
output[dtype] = datum
|
|
94
233
|
output
|
|
@@ -97,7 +236,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
97
236
|
// recordNaanToDyn
|
|
98
237
|
// Convert a Naan dictionary to DynamoDB format.
|
|
99
238
|
//
|
|
100
|
-
function recordNaanToDyn(nitem, local output, key, data) {
|
|
239
|
+
conv.recordNaanToDyn = function recordNaanToDyn(nitem, local output, key, data) {
|
|
101
240
|
output = { }
|
|
102
241
|
for `(key, data) in nitem
|
|
103
242
|
output[key] = datumNaanToDyn(data)
|
|
@@ -107,7 +246,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
107
246
|
// datumDynToNaan
|
|
108
247
|
// Convert a DynamoDB typed attribute to Naan format.
|
|
109
248
|
//
|
|
110
|
-
function datumDynToNaan(dtype, dvalue, local output) {
|
|
249
|
+
conv.datumDynToNaan = function datumDynToNaan(dtype, dvalue, local output) {
|
|
111
250
|
if dtype == "BS" || dtype == "SS"
|
|
112
251
|
dvalue
|
|
113
252
|
else if dtype == "NS"
|
|
@@ -136,45 +275,247 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
136
275
|
else if dtype == "B" || dtype == "S"
|
|
137
276
|
dvalue
|
|
138
277
|
else {
|
|
139
|
-
debuglog("
|
|
278
|
+
debuglog("DynaConverter.datumDynToNaan: unknown type", dtype, typeof(dvalue), dvalue)
|
|
140
279
|
false }
|
|
141
280
|
}
|
|
142
281
|
|
|
143
282
|
// recordDynToNaan
|
|
144
283
|
// Convert a DynamoDB typed item dictionary to Naan format.
|
|
145
284
|
//
|
|
146
|
-
function recordDynToNaan(ditem, local output, key, data, dtype, dvalue) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
285
|
+
conv.recordDynToNaan = function recordDynToNaan(ditem, local output, key, data, dtype, dvalue) {
|
|
286
|
+
if ditem.constructor === Array.prototype.constructor || array(ditem) { // process an array
|
|
287
|
+
output = []
|
|
288
|
+
for data in ditem
|
|
289
|
+
output.push(recordDynToNaan(data))
|
|
290
|
+
}
|
|
291
|
+
else { // process a single expression
|
|
292
|
+
output = { }
|
|
293
|
+
for `(key, data) in ditem {
|
|
294
|
+
dtype = data.*.0
|
|
295
|
+
dvalue = data[dtype]
|
|
296
|
+
output[key] = datumDynToNaan(dtype, dvalue)
|
|
297
|
+
}
|
|
152
298
|
}
|
|
153
299
|
output
|
|
154
300
|
}
|
|
155
|
-
|
|
301
|
+
|
|
302
|
+
// genAttributeNameAdder
|
|
156
303
|
//
|
|
157
|
-
//
|
|
304
|
+
// Return a function that, when called repeatedly, adds attribute name definitions to the
|
|
305
|
+
// specified parameter block.
|
|
158
306
|
//
|
|
159
|
-
|
|
160
|
-
|
|
307
|
+
closure genAttributeNameAdder(params, local adex) {
|
|
308
|
+
function attrib(name, local adef, atype, key, data) {
|
|
309
|
+
if !string(name)
|
|
310
|
+
name = tostring(name) // try to make it a string
|
|
311
|
+
if !params.ExpressionAttributeNames
|
|
312
|
+
params.ExpressionAttributeNames = { }
|
|
313
|
+
if !adex
|
|
314
|
+
adex = (keys(params.ExpressionAttributeNames).length || 0) + 1
|
|
315
|
+
for `(key, data) in params.ExpressionAttributeNames
|
|
316
|
+
if data == name
|
|
317
|
+
return (key) // found matching name
|
|
318
|
+
key = "#n".concat(adex++)
|
|
319
|
+
params.ExpressionAttributeNames[key] = name
|
|
320
|
+
key
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// genAttributeValueAdder
|
|
161
325
|
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
params.
|
|
172
|
-
|
|
326
|
+
// Return a function that, when called repeatedly, adds attribute value definitions to the
|
|
327
|
+
// specified parameter block. Symbols are interpreted as literal names, e.g. attribute names, and
|
|
328
|
+
// are not defined as values here.
|
|
329
|
+
//
|
|
330
|
+
closure genAttributeValueAdder(params, local adex) {
|
|
331
|
+
function attrib(expr, local adef, atype, key, data) {
|
|
332
|
+
if symbol(expr) && !member(expr, `(true, false, null))
|
|
333
|
+
return (expr.tostring) // literal attribute name
|
|
334
|
+
if !params.ExpressionAttributeValues
|
|
335
|
+
params.ExpressionAttributeValues = { }
|
|
336
|
+
if !adex
|
|
337
|
+
adex = (keys(params.ExpressionAttributeValues).length || 0) + 1
|
|
338
|
+
adef = datumNaanToDyn(expr)
|
|
339
|
+
atype = keys(adef).0
|
|
340
|
+
for `(key, data) in params.ExpressionAttributeValues
|
|
341
|
+
if data[atype] == adef[atype]
|
|
342
|
+
return (key) // found matching attribute
|
|
343
|
+
key = ":v".concat(adex++)
|
|
344
|
+
params.ExpressionAttributeValues[key] = adef
|
|
345
|
+
key
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//
|
|
350
|
+
// createKeyExpression
|
|
351
|
+
//
|
|
352
|
+
// Update the specified parameter block with one or more condition expressions, preserving any
|
|
353
|
+
// that are already present. Specify each condition with a dictionary as follows:
|
|
354
|
+
// {
|
|
355
|
+
// [KeyConditionExpression: <condition-dictionary>]
|
|
356
|
+
// [FilterExpression: <condition-dictionary]
|
|
357
|
+
// [etc.]
|
|
358
|
+
// }
|
|
359
|
+
// If successful the parameter block is updated, otherwise it may be partly updated. The return
|
|
360
|
+
// value is a standard (error, data) tuple where the data is just { ok: true } if no error.
|
|
361
|
+
// This will parse only one expression per dictionary, but as many such conditions as you
|
|
362
|
+
// you wish to use by specifying an array. This does not check for semantics enforced by the
|
|
363
|
+
// API. E.g. Key conditions can only have one condition expression for the sort key, and only
|
|
364
|
+
// equality is allowed for the partition key. Only filter expressions can use the <> operator.
|
|
365
|
+
//
|
|
366
|
+
// condition dictionary:
|
|
367
|
+
// {
|
|
368
|
+
// key: <keyname, e.g. partition key name> // required
|
|
369
|
+
// "=": <expression> // exclusive of other comparisons
|
|
370
|
+
// ">": <expression> // exclusive of other comparisons
|
|
371
|
+
// "<": <expression> // exclusive of other comparisons
|
|
372
|
+
// "<>": <expression> // exclusive of other comparisons
|
|
373
|
+
// beginsWith: <string-expression> // exclusive of other comparisons
|
|
374
|
+
// ">=": <expression> // with <= or alone
|
|
375
|
+
// "<=": <expression> // with >= or alone
|
|
376
|
+
// }
|
|
377
|
+
//
|
|
378
|
+
|
|
379
|
+
conv.createKeyExpression = function createKeyExpression(params, conds
|
|
380
|
+
local error, attval, rangename, range, keyname, op, expr, rangex, arg1, arg2) {
|
|
381
|
+
|
|
382
|
+
// dupeop
|
|
383
|
+
//
|
|
384
|
+
// Return a duplicate operation error.
|
|
385
|
+
function dupeop() {
|
|
386
|
+
list(Error("key expression cannot combine", op, "with", rangex.0))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
//
|
|
390
|
+
// build the expression
|
|
391
|
+
//
|
|
392
|
+
attval = genAttributeValueAdder(params)
|
|
393
|
+
for `(rangename, ranges) in conds {
|
|
394
|
+
if !array(ranges)
|
|
395
|
+
ranges = [ranges]
|
|
396
|
+
for range in ranges {
|
|
397
|
+
rangex = false
|
|
398
|
+
keyname = tostring(range.key)
|
|
399
|
+
if !keyname
|
|
400
|
+
return (list(Error("key expression requires key name")))
|
|
401
|
+
for `(op, expr) in range {
|
|
402
|
+
if op == "key"
|
|
403
|
+
continue
|
|
404
|
+
expr = attval(expr)
|
|
405
|
+
if op == ">=" {
|
|
406
|
+
if rangex.0 == "<="
|
|
407
|
+
rangex = list("between", expr, rangex.1)
|
|
408
|
+
else if rangex.0
|
|
409
|
+
return (dupeop())
|
|
410
|
+
else
|
|
411
|
+
rangex = list(op, expr) }
|
|
412
|
+
else if op == "<=" {
|
|
413
|
+
if rangex.0 == ">="
|
|
414
|
+
rangex = list("between", rangex.1, expr)
|
|
415
|
+
else if rangex.0
|
|
416
|
+
return (dupeop())
|
|
417
|
+
else
|
|
418
|
+
rangex = list(op, expr) }
|
|
419
|
+
else if rangex.0
|
|
420
|
+
return (dupeop())
|
|
421
|
+
else
|
|
422
|
+
rangex = list(op, expr)
|
|
423
|
+
}
|
|
424
|
+
if rangex {
|
|
425
|
+
arg1 = rangex.1
|
|
426
|
+
if rangex.0 == "between" {
|
|
427
|
+
arg2 = rangex.2
|
|
428
|
+
expr = keyname.concat(" between ", arg1, " and ", arg2)
|
|
429
|
+
}
|
|
430
|
+
else if rangex.0 == "beginsWith"
|
|
431
|
+
expr = "begins_with(".concat(keyname, ", ", arg1, ")")
|
|
432
|
+
else
|
|
433
|
+
expr = keyname.concat(space, rangex.0, space, arg1)
|
|
434
|
+
if params[rangename]
|
|
435
|
+
params[rangename] = params[rangename].concat(" and ", expr)
|
|
436
|
+
else
|
|
437
|
+
params[rangename] = expr
|
|
438
|
+
}
|
|
173
439
|
}
|
|
174
|
-
}
|
|
440
|
+
}
|
|
441
|
+
list(false, { ok: true })
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// createUpdateExpression
|
|
445
|
+
//
|
|
446
|
+
// Update the specified parameter block with one or more update expressions, preserving any that
|
|
447
|
+
// are already present. Specify the updates with a dictionary as follows:
|
|
448
|
+
// {
|
|
449
|
+
// set: [`(attribute, value)] // set attributes to values
|
|
450
|
+
// sum: [`(attribute, value)] // attributes += values
|
|
451
|
+
// diff: [`(attribute, value)] // attributes -= values
|
|
452
|
+
// add: [`(attribute, value)] // ADD(attributes, values) -- attrib need not exist
|
|
453
|
+
// remove: [attribute] // remove attributes from the record
|
|
454
|
+
// }
|
|
455
|
+
// If successful the parameter block is updated, otherwise it may be partly updated. The return
|
|
456
|
+
// value is a standard (error, data) tuple where the data is just { ok: true } if no error.
|
|
457
|
+
//
|
|
458
|
+
conv.createUpdateExpression = function createUpdateExpression(params, updates,
|
|
459
|
+
local attval, attname, output, item) {
|
|
460
|
+
|
|
461
|
+
if params.UpdateExpression
|
|
462
|
+
return (list(Error("conv.createUpdateExpression: UpdateExpression already exists")))
|
|
463
|
+
attval = genAttributeValueAdder(params)
|
|
464
|
+
attname = genAttributeNameAdder(params)
|
|
465
|
+
output = {
|
|
466
|
+
SET: []
|
|
467
|
+
ADD: []
|
|
468
|
+
REMOVE: []
|
|
469
|
+
}
|
|
470
|
+
for item in updates.set
|
|
471
|
+
output.SET.push(strcat(attname(item.0), "=", attval(item.1)))
|
|
472
|
+
for item in updates.sum
|
|
473
|
+
output.SET.push(strcat(attname(item.0), "=", attname(item.0), "+", attval(item.1)))
|
|
474
|
+
for item in updates.diff
|
|
475
|
+
output.SET.push(strcat(attname(item.0), "=", attname(item.0), "-", attval(item.1)))
|
|
476
|
+
for item in updates.add
|
|
477
|
+
output.ADD.push(strcat(attname(item.0), space, attval(item.1)))
|
|
478
|
+
for item in updates.remove
|
|
479
|
+
output.REMOVE.push(attname(item))
|
|
480
|
+
params.UpdateExpression = ""
|
|
481
|
+
for `(key, value) in output
|
|
482
|
+
if value.length > 0
|
|
483
|
+
params.UpdateExpression = params.UpdateExpression.concat(key, space, value.join(", "), space)
|
|
484
|
+
list(false, { ok: true })
|
|
175
485
|
}
|
|
176
486
|
|
|
487
|
+
// genBackoffDelayer
|
|
177
488
|
//
|
|
489
|
+
// Return a function that, when called repeatedly, will sleep for an exponentially increasing
|
|
490
|
+
// maximum time interval starting with 50 msec and doubling each time. The actual delay varies by
|
|
491
|
+
// a random jitter of up to 100% of the delay. The function returns the maximum milliseconds of
|
|
492
|
+
// the next delay, so the caller can give up if this becomes too large.
|
|
493
|
+
//
|
|
494
|
+
conv.genBackoffDelayer = closure genBackoffDelayer(local delay) {
|
|
495
|
+
delay = 50
|
|
496
|
+
function delayer() {
|
|
497
|
+
sleep(delay * Math.random()) // jitter up to 100%
|
|
498
|
+
delay *= 2
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// finis
|
|
503
|
+
|
|
504
|
+
conv
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
/*
|
|
509
|
+
* DynaTable
|
|
510
|
+
*
|
|
511
|
+
* DynamoDB table.
|
|
512
|
+
*
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
closure DynaTable(dyna, tablename, local table) {
|
|
516
|
+
table = new(object, this)
|
|
517
|
+
table.name = tablename
|
|
518
|
+
|
|
178
519
|
// info
|
|
179
520
|
//
|
|
180
521
|
// Return info about the table, or an error, in standard (error, dictionary) tuple format.
|
|
@@ -202,7 +543,8 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
202
543
|
dyna.aws.describeTable(params, function(error, data, local output, key) {
|
|
203
544
|
if error {
|
|
204
545
|
dynaTableError = error
|
|
205
|
-
debuglog("aws.describeTable failed", tablename, error)
|
|
546
|
+
debuglog("aws.describeTable failed", tablename, ErrorString(error))
|
|
547
|
+
error = Error("table.info(".concat(tablename, ") failed"), error)
|
|
206
548
|
} else {
|
|
207
549
|
data = data.Table
|
|
208
550
|
output = {
|
|
@@ -219,18 +561,17 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
219
561
|
output.range = key.AttributeName
|
|
220
562
|
for key in data.AttributeDefinitions
|
|
221
563
|
if key.AttributeName == output.hash
|
|
222
|
-
output.hashType = fieldTypeDynToNaan(key.AttributeType)
|
|
564
|
+
output.hashType = dyna.conv.fieldTypeDynToNaan(key.AttributeType)
|
|
223
565
|
else if key.AttributeName == output.range
|
|
224
|
-
output.rangeType = fieldTypeDynToNaan(key.AttributeType)
|
|
225
|
-
table.hashKey = output.hash
|
|
226
|
-
table.rangeKey = output.range
|
|
566
|
+
output.rangeType = dyna.conv.fieldTypeDynToNaan(key.AttributeType)
|
|
567
|
+
table.hashKey = compress(output.hash) // hash attribute name
|
|
568
|
+
table.rangeKey = compress(output.range) // range attribute name or false
|
|
227
569
|
}
|
|
228
570
|
pending.signal(list(error, output))
|
|
229
571
|
})
|
|
230
572
|
pending.wait()
|
|
231
573
|
}
|
|
232
574
|
|
|
233
|
-
//
|
|
234
575
|
// create
|
|
235
576
|
//
|
|
236
577
|
// Create the table with specified parameters, blocking until the table is ready for use.
|
|
@@ -242,11 +583,11 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
242
583
|
// range: <name> // primary range key attribute name (optional)
|
|
243
584
|
// rangeType: <name> // string | numeric (if range specified)
|
|
244
585
|
// }
|
|
245
|
-
|
|
586
|
+
//
|
|
246
587
|
table.create = closure create(parameters, local hashType, rangeType, params, pending, error, data, start) {
|
|
247
588
|
if !dyna.aws
|
|
248
589
|
return (list(Error("DynaTable.create: not logged into AWS")))
|
|
249
|
-
hashType = fieldTypeNaanToDyn(parameters.hashType)
|
|
590
|
+
hashType = dyna.conv.fieldTypeNaanToDyn(parameters.hashType)
|
|
250
591
|
if !parameters.hash || !hashType
|
|
251
592
|
return (list(Error("DynoTable.create: primary key hash/hashType required")))
|
|
252
593
|
params = {
|
|
@@ -265,7 +606,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
265
606
|
]
|
|
266
607
|
}
|
|
267
608
|
if parameters.range {
|
|
268
|
-
rangeType = fieldTypeNaanToDyn(parameters.rangeType)
|
|
609
|
+
rangeType = dyna.conv.fieldTypeNaanToDyn(parameters.rangeType)
|
|
269
610
|
if !rangeType
|
|
270
611
|
return (list(Error("DynoTable.create: range requires rangeType")))
|
|
271
612
|
params.AttributeDefinitions.push({
|
|
@@ -281,7 +622,8 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
281
622
|
dyna.aws.createTable(params, function(error, data) {
|
|
282
623
|
if error {
|
|
283
624
|
dynaTableError = error
|
|
284
|
-
debuglog("aws.createTable failed", tablename, error)
|
|
625
|
+
debuglog("aws.createTable failed", tablename, ErrorString(error))
|
|
626
|
+
error = Error("table.create(".concat(tablename, ") failed"), error)
|
|
285
627
|
}
|
|
286
628
|
pending.signal(list(error, data))
|
|
287
629
|
})
|
|
@@ -293,7 +635,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
293
635
|
`(error, data) = info()
|
|
294
636
|
if data.status == "ACTIVE"
|
|
295
637
|
break
|
|
296
|
-
if milliseconds() - start >
|
|
638
|
+
if milliseconds() - start > dyna.timeout { // 20 seconds
|
|
297
639
|
if !error
|
|
298
640
|
error = data.status
|
|
299
641
|
return(list(Error("DynoTable.create: timeout waiting for new table to become active", error)))
|
|
@@ -302,27 +644,81 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
302
644
|
}
|
|
303
645
|
list(false, data)
|
|
304
646
|
}
|
|
305
|
-
|
|
647
|
+
|
|
648
|
+
// preflightError
|
|
306
649
|
//
|
|
307
|
-
//
|
|
650
|
+
// Ensure that the table is ready before executing an operation. This returns an error tuple or
|
|
651
|
+
// false to proceed.
|
|
308
652
|
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
653
|
+
function preflightError(op) {
|
|
654
|
+
if !dyna.aws
|
|
655
|
+
return (list(Error("DynaTable.".concat(op, ": not logged into AWS"))))
|
|
312
656
|
if !table.hashKey {
|
|
313
657
|
result = info()
|
|
314
658
|
if result.0
|
|
315
659
|
return(result)
|
|
316
660
|
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// makeDynKey
|
|
664
|
+
//
|
|
665
|
+
// Make a dynamoDB key dictionary for the table using the specified primary key, which can be a
|
|
666
|
+
// tuple of hash/range or just a hash if only a hash is needed for this table.
|
|
667
|
+
//
|
|
668
|
+
conv.makeDynKey = function makeDynKey(primaryKey, local hashValue, rangeValue, key) {
|
|
669
|
+
if tuple(primaryKey)
|
|
670
|
+
`(hashValue, rangeValue) = primaryKey
|
|
671
|
+
else
|
|
672
|
+
hashValue = primaryKey
|
|
673
|
+
key = { }
|
|
674
|
+
key[table.hashKey] = dyna.conv.datumNaanToDyn(hashValue)
|
|
675
|
+
if table.rangeKey
|
|
676
|
+
key[table.rangeKey] = dyna.conv.datumNaanToDyn(rangeValue)
|
|
677
|
+
key
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// getRecord
|
|
681
|
+
//
|
|
682
|
+
// Get a record from the table with the specified hash value, and optional range value.
|
|
683
|
+
//
|
|
684
|
+
table.getRecord = closure getRecord(hashValue, rangeValue, local result, params, pending) {
|
|
685
|
+
if preflightError(this)
|
|
686
|
+
return
|
|
317
687
|
params = {
|
|
318
688
|
TableName: tablename
|
|
319
|
-
|
|
689
|
+
}
|
|
690
|
+
params.Key = makeDynKey(list(hashValue, rangeValue))
|
|
691
|
+
pending = new(nonce)
|
|
692
|
+
dyna.aws.getItem(params, function(error, data, local output) {
|
|
693
|
+
if error {
|
|
694
|
+
dynaTableError = error
|
|
695
|
+
debuglog("aws.getItem failed", tablename, ErrorString(error))
|
|
696
|
+
error = Error("table.getRecord(".concat(tablename, ") failed"), error)
|
|
697
|
+
} else
|
|
698
|
+
data = dyna.conv.recordDynToNaan(data.Item)
|
|
699
|
+
pending.signal(list(error, data))
|
|
700
|
+
})
|
|
701
|
+
pending.wait()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
//
|
|
705
|
+
// putRecord
|
|
706
|
+
//
|
|
707
|
+
// Put a record in the table, replacing any existing record with the same key.
|
|
708
|
+
|
|
709
|
+
table.putRecord = closure putRecord(record, local result, params, pending) {
|
|
710
|
+
if preflightError(this)
|
|
711
|
+
return
|
|
712
|
+
params = {
|
|
713
|
+
TableName: tablename
|
|
714
|
+
Item: dyna.conv.recordNaanToDyn(record)
|
|
320
715
|
}
|
|
321
716
|
pending = new(nonce)
|
|
322
717
|
dyna.aws.putItem(params, function(error, data, local output) {
|
|
323
718
|
if error {
|
|
324
719
|
dynaTableError = error
|
|
325
|
-
debuglog("aws.putItem failed", tablename, error)
|
|
720
|
+
debuglog("aws.putItem failed", tablename, ErrorString(error))
|
|
721
|
+
error = Error("table.putRecord(".concat(tablename, ") failed"), error)
|
|
326
722
|
}
|
|
327
723
|
pending.signal(list(error, data))
|
|
328
724
|
})
|
|
@@ -330,113 +726,250 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
330
726
|
}
|
|
331
727
|
|
|
332
728
|
//
|
|
333
|
-
//
|
|
729
|
+
// updateRecord
|
|
334
730
|
//
|
|
335
|
-
//
|
|
731
|
+
// Update a record in the table. This will update the existing record if it exists, or add a new
|
|
732
|
+
// one. Options include conditional updates, and adding or removing attributes as follows:
|
|
733
|
+
// {
|
|
734
|
+
// condition: <range-expression> // conditional expression as in createKeyExpression()
|
|
735
|
+
// set: [`(attribute, value)] // set attributes to values
|
|
736
|
+
// sum: [`(attribute, value)] // attributes += values
|
|
737
|
+
// diff: [`(attribute, value)] // attributes -= values
|
|
738
|
+
// remove: [attribute] // remove attributes from the record
|
|
739
|
+
// return: "all_old" | "all_new" | "updated_old" | "updated_new"
|
|
740
|
+
// // return the record as chosen, otherwise { }
|
|
741
|
+
// }
|
|
742
|
+
// The result is a standard result tuple `(error, record).
|
|
336
743
|
|
|
337
|
-
table.
|
|
338
|
-
if
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
744
|
+
table.updateRecord = closure updateRecord(hashValue, rangeValue, options, local error, result, params, actions, pending) {
|
|
745
|
+
if preflightError(this)
|
|
746
|
+
return
|
|
747
|
+
params = {
|
|
748
|
+
TableName: tablename
|
|
342
749
|
}
|
|
750
|
+
params.Key = makeDynKey(list(hashValue, rangeValue))
|
|
751
|
+
if options.condition {
|
|
752
|
+
`(error) = dyna.conv.createKeyExpression(params, {
|
|
753
|
+
KeyConditionExpression: {
|
|
754
|
+
key: table.hashKey
|
|
755
|
+
"=": hashValue
|
|
756
|
+
}
|
|
757
|
+
})
|
|
758
|
+
if error
|
|
759
|
+
return (list(Error("table.updateRecord:", error)))
|
|
760
|
+
}
|
|
761
|
+
dyna.conv.createUpdateExpression(params, options)
|
|
762
|
+
if options.return {
|
|
763
|
+
params.ReturnValues = {
|
|
764
|
+
all_old: "ALL_OLD"
|
|
765
|
+
all_new: "ALL_NEW"
|
|
766
|
+
updated_old: "UPDATED_OLD"
|
|
767
|
+
updated_new: "UPDATED_NEW"
|
|
768
|
+
}[options.return]
|
|
769
|
+
if !params.ReturnValues
|
|
770
|
+
params.ReturnValues = "NONE"
|
|
771
|
+
}
|
|
772
|
+
pending = new(nonce)
|
|
773
|
+
dyna.aws.updateItem(params, function(error, data) {
|
|
774
|
+
if error {
|
|
775
|
+
dynaTableError = error
|
|
776
|
+
debuglog("aws.updateItem failed", tablename, ErrorString(error), Dialect.print(params))
|
|
777
|
+
error = Error("table.updateRecord(".concat(tablename, ") failed"), error)
|
|
778
|
+
}
|
|
779
|
+
else
|
|
780
|
+
data = dyna.conv.recordDynToNaan(data.Attributes)
|
|
781
|
+
pending.signal(list(error, data))
|
|
782
|
+
})
|
|
783
|
+
pending.wait()
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
//
|
|
787
|
+
// deleteRecord
|
|
788
|
+
//
|
|
789
|
+
// Delete a record in the table. Options include conditional updates as follows:
|
|
790
|
+
// {
|
|
791
|
+
// condition: <range-expression> // conditional expression as in createKeyExpression()
|
|
792
|
+
// return: "all_old" // return previous attribute values, otherwise { }
|
|
793
|
+
// }
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
table.deleteRecord = closure deleteRecord(hashValue, rangeValue, options, local result, params, pending) {
|
|
797
|
+
if preflightError(this)
|
|
798
|
+
return
|
|
343
799
|
params = {
|
|
344
800
|
TableName: tablename
|
|
345
|
-
Key: { }
|
|
346
801
|
}
|
|
347
|
-
params.Key
|
|
348
|
-
if
|
|
349
|
-
|
|
802
|
+
params.Key = makeDynKey(list(hashValue, rangeValue))
|
|
803
|
+
if options.condition {
|
|
804
|
+
`(error) = dyna.conv.createKeyExpression(params, {
|
|
805
|
+
KeyConditionExpression: {
|
|
806
|
+
key: table.hashKey
|
|
807
|
+
"=": hashValue
|
|
808
|
+
}
|
|
809
|
+
})
|
|
810
|
+
if error
|
|
811
|
+
return (list(Error("table.deleteRecord:", error)))
|
|
812
|
+
}
|
|
813
|
+
if options.return {
|
|
814
|
+
params.ReturnValues = {
|
|
815
|
+
all_old: "ALL_OLD"
|
|
816
|
+
}[options.return]
|
|
817
|
+
if !params.ReturnValues
|
|
818
|
+
params.ReturnValues = "NONE"
|
|
819
|
+
}
|
|
350
820
|
pending = new(nonce)
|
|
351
|
-
dyna.aws.
|
|
821
|
+
dyna.aws.deleteItem(params, function(error, data, local output) {
|
|
352
822
|
if error {
|
|
353
823
|
dynaTableError = error
|
|
354
|
-
debuglog("aws.
|
|
355
|
-
|
|
356
|
-
|
|
824
|
+
debuglog("aws.deleteItem failed", tablename, ErrorString(error))
|
|
825
|
+
error = Error("table.deleteRecord(".concat(tablename, ") failed"), error)
|
|
826
|
+
}
|
|
357
827
|
pending.signal(list(error, data))
|
|
358
828
|
})
|
|
359
829
|
pending.wait()
|
|
360
830
|
}
|
|
361
831
|
|
|
832
|
+
//
|
|
833
|
+
// dynQueryPager
|
|
834
|
+
//
|
|
835
|
+
// Perform a paged query. This implements paging conventions specific to DynamoDB. The callback
|
|
836
|
+
// should return the number of items found in the data, or false to stop looping.
|
|
837
|
+
//
|
|
838
|
+
closure dynQueryPager(procname, params, remaining, doneCB) {
|
|
839
|
+
if !remaining
|
|
840
|
+
remaining = 10 // default retrieve limit
|
|
841
|
+
params.Limit = remaining
|
|
842
|
+
QueryPager(dyna.aws, procname, params, function(data, local processed) {
|
|
843
|
+
processed = doneCB(data)
|
|
844
|
+
if processed
|
|
845
|
+
remaining -= processed
|
|
846
|
+
if processed && remaining > 0 && data.LastEvaluatedKey {
|
|
847
|
+
params.limit = remaining // set up for next iteration
|
|
848
|
+
params.ExclusiveStartKey = data.LastEvaluatedKey
|
|
849
|
+
true // continue looping
|
|
850
|
+
}
|
|
851
|
+
})
|
|
852
|
+
}
|
|
853
|
+
|
|
362
854
|
//
|
|
363
855
|
// queryRecords
|
|
364
856
|
//
|
|
365
857
|
// Options:
|
|
366
858
|
// {
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
859
|
+
// indexName: <string> // index name in table
|
|
860
|
+
// indexHashKey: <string> // name of index hashKey
|
|
861
|
+
// range: <key-expression> // range of keys to retrieve
|
|
862
|
+
// filter: <key-expression> // conditions for items to return
|
|
863
|
+
// attmap: <dictionary> // map attribute names to #shortcuts
|
|
864
|
+
// reverse: <boolean> // reverse order
|
|
865
|
+
// project: <array> // project only specified attributes
|
|
866
|
+
// exstart: <exclusive start key> // for paging
|
|
867
|
+
// limit: <number> // maximum count of results
|
|
371
868
|
// }
|
|
372
869
|
//
|
|
373
|
-
|
|
870
|
+
|
|
871
|
+
table.queryRecords = closure queryRecords(hashValue, options
|
|
872
|
+
local error, result, params, hashKey, op, expr, output, error)
|
|
873
|
+
{
|
|
874
|
+
if preflightError(this)
|
|
875
|
+
return
|
|
876
|
+
params = {
|
|
877
|
+
TableName: tablename
|
|
878
|
+
}
|
|
879
|
+
hashKey = table.hashKey
|
|
880
|
+
if options.indexName {
|
|
881
|
+
params.IndexName = options.indexName
|
|
882
|
+
if options.indexHashKey
|
|
883
|
+
hashKey = options.indexHashKey
|
|
884
|
+
}
|
|
885
|
+
dyna.conv.createKeyExpression(params, {
|
|
886
|
+
KeyConditionExpression: {
|
|
887
|
+
key: hashKey
|
|
888
|
+
"=": hashValue
|
|
889
|
+
}
|
|
890
|
+
})
|
|
891
|
+
if options.range {
|
|
892
|
+
`(error) = dyna.conv.createKeyExpression(params, {
|
|
893
|
+
KeyConditionExpression: options.range
|
|
894
|
+
})
|
|
895
|
+
if error
|
|
896
|
+
return (list(Error("table.queryRecords range:", error)))
|
|
897
|
+
}
|
|
898
|
+
if options.filter {
|
|
899
|
+
`(error) = dyna.conv.createKeyExpression(params, {
|
|
900
|
+
FilterExpression: options.filter
|
|
901
|
+
})
|
|
902
|
+
if error
|
|
903
|
+
return (list(Error("table.queryRecords filter:", error)))
|
|
904
|
+
}
|
|
905
|
+
if options.attmap
|
|
906
|
+
params.ExpressionAttributeNames = options.attmap
|
|
907
|
+
if options.reverse
|
|
908
|
+
params.ScanIndexForward = false
|
|
909
|
+
if options.project
|
|
910
|
+
params.ProjectionExpression = options.project.join(", ")
|
|
911
|
+
if options.exstart
|
|
912
|
+
params.ExclusiveStartKey = options.exstart
|
|
913
|
+
output = []
|
|
914
|
+
`(error) = dynQueryPager(`query, params, options.limit, function(data) {
|
|
915
|
+
if data.Count > 0
|
|
916
|
+
output = output.concat(dyna.conv.recordDynToNaan(data.Items))
|
|
917
|
+
data.Count
|
|
918
|
+
})
|
|
919
|
+
if error {
|
|
920
|
+
error = Error("table.queryRecords(".concat(tablename, ") failed"), error)
|
|
921
|
+
output = false
|
|
922
|
+
}
|
|
923
|
+
list(error, output)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
//
|
|
927
|
+
// scanRecords
|
|
928
|
+
//
|
|
929
|
+
// Options:
|
|
374
930
|
// {
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
931
|
+
// index: <string> // index name in table
|
|
932
|
+
// filter: <key-expression> // conditions for items to return
|
|
933
|
+
// attmap: <dictionary> // map attribute names to #shortcuts
|
|
934
|
+
// project: <array> // project only specified attributes
|
|
935
|
+
// exstart: <exclusive start key> // for paging
|
|
936
|
+
// limit: <number> // maximum count of results
|
|
381
937
|
// }
|
|
382
938
|
//
|
|
383
939
|
|
|
384
|
-
table.
|
|
385
|
-
local result, params, op, expr,
|
|
940
|
+
table.scanRecords = closure scanRecords(options
|
|
941
|
+
local result, params, op, expr, output, error)
|
|
386
942
|
{
|
|
387
|
-
if
|
|
388
|
-
|
|
389
|
-
if result.0
|
|
390
|
-
return(result)
|
|
391
|
-
}
|
|
943
|
+
if preflightError(this)
|
|
944
|
+
return
|
|
392
945
|
params = {
|
|
393
946
|
TableName: tablename
|
|
394
|
-
ExpressionAttributeValues: {
|
|
395
|
-
":v1": datumNaanToDyn(hashValue)
|
|
396
|
-
}
|
|
397
|
-
KeyConditionExpression: strcat(table.hashKey, " = :v1")
|
|
398
947
|
}
|
|
399
|
-
if options.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
else
|
|
408
|
-
rangex = list(op, expr) }
|
|
409
|
-
else if op == "<=" {
|
|
410
|
-
if rangex.0 == ">="
|
|
411
|
-
rangex = list("between", rangex.1, expr)
|
|
412
|
-
else if rangex.0
|
|
413
|
-
return (dupeop())
|
|
414
|
-
else
|
|
415
|
-
rangex = list(op, expr) }
|
|
416
|
-
else if rangex.0
|
|
417
|
-
return (dupeop())
|
|
418
|
-
else
|
|
419
|
-
rangex = list(op, expr)
|
|
420
|
-
}
|
|
421
|
-
if rangex {
|
|
422
|
-
params.ExpressionAttributeValues[":v2"] = datumNaanToDyn(rangex.1)
|
|
423
|
-
if rangex.0 == "between" {
|
|
424
|
-
params.ExpressionAttributeValues[":v3"] = datumNaanToDyn(rangex.2)
|
|
425
|
-
expr = strcat(" between :v2 and :v3")
|
|
426
|
-
}
|
|
427
|
-
else
|
|
428
|
-
expr = strcat(space, rangex.0, " :v2")
|
|
429
|
-
params.KeyConditionExpression = params.KeyConditionExpression.concat(" and ", table.rangeKey, expr)
|
|
430
|
-
}
|
|
948
|
+
if options.index
|
|
949
|
+
params.IndexName = options.index
|
|
950
|
+
if options.filter {
|
|
951
|
+
`(error) = dyna.conv.createKeyExpression(params, {
|
|
952
|
+
FilterExpression: options.filter
|
|
953
|
+
})
|
|
954
|
+
if error
|
|
955
|
+
return (list(Error("table.scanRecords filter:", error)))
|
|
431
956
|
}
|
|
957
|
+
if options.attmap
|
|
958
|
+
params.ExpressionAttributeNames = attmap
|
|
959
|
+
if options.project
|
|
960
|
+
params.ProjectionExpression = options.project.join(", ")
|
|
961
|
+
if options.exstart
|
|
962
|
+
params.ExclusiveStartKey = options.exstart
|
|
432
963
|
output = []
|
|
433
|
-
`(error) =
|
|
964
|
+
`(error) = dynQueryPager(`scan, params, options.limit, function(data) {
|
|
434
965
|
if data.Count >= 0
|
|
435
|
-
output = output.concat(recordDynToNaan(data.Items))
|
|
966
|
+
output = output.concat(dyna.conv.recordDynToNaan(data.Items))
|
|
436
967
|
data.Count
|
|
437
968
|
})
|
|
438
|
-
if error
|
|
969
|
+
if error {
|
|
970
|
+
error = Error("table.scanRecords(".concat(tablename, ") failed"), error)
|
|
439
971
|
output = false
|
|
972
|
+
}
|
|
440
973
|
list(error, output)
|
|
441
974
|
}
|
|
442
975
|
|
|
@@ -455,35 +988,69 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
455
988
|
|
|
456
989
|
closure DynamoDB(local dyna) {
|
|
457
990
|
dyna = new(object, this)
|
|
991
|
+
dyna.timeout = 20000 // default 20-second timeout
|
|
992
|
+
dyna.conv = DynaConverter()
|
|
993
|
+
dyna.multi = DynaMultiTable(dyna)
|
|
458
994
|
|
|
459
|
-
//
|
|
460
995
|
// login
|
|
461
996
|
//
|
|
462
|
-
dyna.login =
|
|
997
|
+
dyna.login = function login(creds) {
|
|
463
998
|
dyna.aws = xnew(awsSDK.DynamoDB, {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
999
|
+
region: creds.region
|
|
1000
|
+
credentials: {
|
|
1001
|
+
accessKeyId: creds.keyID
|
|
1002
|
+
secretAccessKey: creds.keySecret
|
|
1003
|
+
}
|
|
1004
|
+
})
|
|
1005
|
+
list(false, { ok: true })
|
|
468
1006
|
}
|
|
469
1007
|
|
|
470
|
-
//
|
|
471
1008
|
// table
|
|
472
1009
|
//
|
|
473
1010
|
// Return a new table access object for the table of the specified name. The table may not
|
|
474
1011
|
// yet exist; see DynaTable for methods.
|
|
475
1012
|
//
|
|
476
|
-
|
|
477
|
-
// {
|
|
478
|
-
// hash: <name> // primary hash key attribute name (required)
|
|
479
|
-
// hashType: <type> // string | numeric (required)
|
|
480
|
-
// range: <name> // primary range key attribute name (optional)
|
|
481
|
-
// rangeType: <name> // string | numeric (required if range specified)
|
|
482
|
-
// }
|
|
483
|
-
//
|
|
484
|
-
dyna.table = closure table(name) {
|
|
1013
|
+
dyna.table = function table(name) {
|
|
485
1014
|
DynaTable(dyna, name)
|
|
486
1015
|
}
|
|
1016
|
+
|
|
1017
|
+
// timekeys
|
|
1018
|
+
//
|
|
1019
|
+
// Return a TimeKeys object with a default no prefix, and baseMS of 1/1/2022.
|
|
1020
|
+
//
|
|
1021
|
+
dyna.timekeys = function timekeys(prefix, baseMS) {
|
|
1022
|
+
TimeKeys(prefix, baseMS)
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// batchGetRecords
|
|
1026
|
+
//
|
|
1027
|
+
dyna.batchGetRecords = function loadBatchGetRecords(request, options) {
|
|
1028
|
+
require("./aws_dynextra.nlg")
|
|
1029
|
+
dyna.batchGetRecords = function bgr(request, options) {
|
|
1030
|
+
dynexBatchGetRecords(dyna, request, options)
|
|
1031
|
+
}
|
|
1032
|
+
dynexBatchGetRecords(dyna, request, options)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// dynexBatchWriteRecords
|
|
1036
|
+
//
|
|
1037
|
+
dyna.batchWriteRecords = function batchWriteRecords(request, options) {
|
|
1038
|
+
require("./aws_dynextra.nlg")
|
|
1039
|
+
dyna.batchWriteRecords = function bwr(request, options) {
|
|
1040
|
+
dynexBatchWriteRecords(dyna, request, options)
|
|
1041
|
+
}
|
|
1042
|
+
dynexBatchWriteRecords(dyna, request, options)
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// dynexTransactGetRecords
|
|
1046
|
+
//
|
|
1047
|
+
dyna.transactGetRecords = function loadTransactGetRecords(request, options) {
|
|
1048
|
+
require("./aws_dynextra.nlg")
|
|
1049
|
+
dyna.transactGetRecords = function tgr(request, options) {
|
|
1050
|
+
dynexTransactGetRecords(dyna, request, options)
|
|
1051
|
+
}
|
|
1052
|
+
dynexTransactGetRecords(dyna, request, options)
|
|
1053
|
+
}
|
|
487
1054
|
|
|
488
1055
|
// finis
|
|
489
1056
|
dyna
|
|
@@ -491,19 +1058,19 @@ closure DynamoDB(local dyna) {
|
|
|
491
1058
|
|
|
492
1059
|
|
|
493
1060
|
/*
|
|
494
|
-
*
|
|
1061
|
+
* dynaInit
|
|
495
1062
|
*
|
|
496
|
-
* Initialize the DynamoDB
|
|
1063
|
+
* Initialize the DynamoDB component.
|
|
497
1064
|
*
|
|
498
1065
|
*/
|
|
499
1066
|
|
|
500
|
-
function
|
|
501
|
-
manifest = `(DynaTable, DynamoDB,
|
|
1067
|
+
function dynaInit(local manifest) {
|
|
1068
|
+
manifest = `(TimeKeys, DynaConverter, DynaTable, DynamoDB, dynaInit)
|
|
502
1069
|
|
|
503
1070
|
Naan.module.build(module.id, "aws_dynamo", function(modobj, compobj) {
|
|
504
1071
|
require("./serviceAws.nlg")
|
|
505
1072
|
compobj.manifest = manifest
|
|
1073
|
+
modobj.exports.TimeKeys = TimeKeys
|
|
506
1074
|
modobj.exports.DynamoDB = DynamoDB
|
|
507
1075
|
})
|
|
508
|
-
|
|
509
1076
|
} ();
|