@naanlang/naan 1.1.0 → 1.2.1
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 +12 -10
- package/bin/index.js +2 -2
- package/dist/naan.min.js +13 -13
- package/frameworks/browser/https_request.nlg +4 -2
- package/frameworks/browser/sworker.js +23 -23
- package/frameworks/browser/terminals.nlg +77 -45
- package/frameworks/browser/ws_client.nlg +6 -2
- package/frameworks/client/apiclient.nlg +80 -115
- package/frameworks/client/psm_client.nlg +39 -18
- package/frameworks/client/relaycon.nlg +2 -0
- package/frameworks/common/common.nlg +10 -4
- package/frameworks/node/https_request.nlg +2 -1
- package/frameworks/node/psm_server.nlg +2 -2
- package/frameworks/node/ws_client.nlg +7 -2
- package/frameworks/project/build.nlg +12 -11
- package/frameworks/project/projects.nlg +7 -5
- package/frameworks/running/executors.nlg +131 -38
- package/frameworks/running/taskexec.nlg +3 -2
- package/frameworks/storage/psm.nlg +2 -1
- package/frameworks/storage/psm_dbtables.nlg +23 -13
- package/lib/browser/env_web.js +6 -6
- package/lib/core/naanlib.js +13 -13
- package/package.json +1 -1
- package/plugins/serviceAws/aws_dynamo.nlg +91 -32
- package/plugins/serviceAws/dbt_aws.nlg +4 -4
- package/test/harness.nlg +1 -1
- package/test/test_02_context.nlg +1 -1
- package/test/test_03_jsinterop.nlg +2 -2
- package/test/test_05_strings.nlg +7 -4
- package/test/test_07_lingo.nlg +3 -0
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2021-
|
|
9
|
+
* Copyright (c) 2021-2024 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
38
|
closure TimeKeys(prefix, baseMS, local timekeys, prelen) {
|
|
39
|
+
global(UUID)
|
|
39
40
|
if !UUID.proc
|
|
40
41
|
throw("TimeKeys requires UUID()")
|
|
41
42
|
timekeys = new(object, this)
|
|
@@ -158,6 +159,7 @@ closure TimeKeys(prefix, baseMS, local timekeys, prelen) {
|
|
|
158
159
|
*/
|
|
159
160
|
|
|
160
161
|
closure DynaConverter(local conv) {
|
|
162
|
+
global()
|
|
161
163
|
conv = new(object, this)
|
|
162
164
|
|
|
163
165
|
// fieldTypeDynToNaan
|
|
@@ -250,7 +252,7 @@ closure DynaConverter(local conv) {
|
|
|
250
252
|
if dtype == "BS" || dtype == "SS"
|
|
251
253
|
dvalue
|
|
252
254
|
else if dtype == "NS"
|
|
253
|
-
new(dvalue).map(Number.parseFloat(item))
|
|
255
|
+
new(dvalue).map(function(item) { Number.parseFloat(item) })
|
|
254
256
|
else if dtype == "M"
|
|
255
257
|
recordDynToNaan(dvalue)
|
|
256
258
|
else if dtype == "L"
|
|
@@ -294,12 +296,24 @@ closure DynaConverter(local conv) {
|
|
|
294
296
|
// genAttributeNameAdder
|
|
295
297
|
//
|
|
296
298
|
// Return a function that, when called repeatedly, adds attribute name definitions to the
|
|
297
|
-
// specified parameter block.
|
|
299
|
+
// specified parameter block. You can specify an attribute name that is a nested key expression
|
|
300
|
+
// resulting from makePath() below. This adds the individual components to the
|
|
301
|
+
// ExpressionAttributeNames list, but generates a return string that references nested elements.
|
|
298
302
|
//
|
|
299
303
|
closure genAttributeNameAdder(params, local adex) {
|
|
300
304
|
function attrib(name, local key, data) {
|
|
301
|
-
if
|
|
302
|
-
name = tostring(name)
|
|
305
|
+
if numeric(name) || name && symbol(name)
|
|
306
|
+
name = tostring(name)
|
|
307
|
+
else if !string(name) || string.trim().length == 0 { // compound or invalid
|
|
308
|
+
if tuple(name) && name.length == 3 {
|
|
309
|
+
if name.0 == `. // quote(a.b) => (`. a b)
|
|
310
|
+
return (strcat(attrib(name.1), ".", attrib(name.2)))
|
|
311
|
+
else if name.0 == `deref && integer(name.2) // quote(a[99]) => (`deref a 99)
|
|
312
|
+
return (strcat(attrib(name.1), "[", attrib(name.2), "]"))
|
|
313
|
+
}
|
|
314
|
+
debuglog("DynaConverter.genAttributeNameAdder: can't make expression from", typeof(name), name)
|
|
315
|
+
name = tostring(name)
|
|
316
|
+
}
|
|
303
317
|
if !params.ExpressionAttributeNames
|
|
304
318
|
params.ExpressionAttributeNames = { }
|
|
305
319
|
if !adex
|
|
@@ -313,6 +327,33 @@ closure DynaConverter(local conv) {
|
|
|
313
327
|
}
|
|
314
328
|
}
|
|
315
329
|
|
|
330
|
+
|
|
331
|
+
// makePath
|
|
332
|
+
//
|
|
333
|
+
// Make an attribute path expression that genAttributeNameAdder can use to make an evaluated
|
|
334
|
+
// attribute path. Specify a path expression and then list the symbols that should be evaluated,
|
|
335
|
+
// if any. For example:
|
|
336
|
+
// > b=4
|
|
337
|
+
// > makePath(a.b.c, b)
|
|
338
|
+
// $: a.4.c
|
|
339
|
+
//
|
|
340
|
+
conv.makePath = macro makePath args {
|
|
341
|
+
function mkatpa(path, local exp) {
|
|
342
|
+
exp = path.map(function(el) {
|
|
343
|
+
if tuple(el)
|
|
344
|
+
exp = mkatpa(el)
|
|
345
|
+
else if member(el, args)
|
|
346
|
+
car(el)
|
|
347
|
+
else
|
|
348
|
+
el
|
|
349
|
+
})
|
|
350
|
+
if !tuple(exp) || exp.0 != `deref || integer(exp.2)
|
|
351
|
+
exp
|
|
352
|
+
else
|
|
353
|
+
cons(`., cdr(exp)) // use `. for dictionary lookup
|
|
354
|
+
} (pop(args))
|
|
355
|
+
}
|
|
356
|
+
|
|
316
357
|
// genAttributeValueAdder
|
|
317
358
|
//
|
|
318
359
|
// Return a function that, when called repeatedly, adds attribute value definitions to the
|
|
@@ -357,7 +398,7 @@ closure DynaConverter(local conv) {
|
|
|
357
398
|
//
|
|
358
399
|
// condition dictionary:
|
|
359
400
|
// {
|
|
360
|
-
// key: <keyname, e.g. partition key name or path
|
|
401
|
+
// key: <keyname, e.g. partition key name or path> // required
|
|
361
402
|
// "=": <expression> // exclusive of other comparisons
|
|
362
403
|
// ">": <expression> // exclusive of other comparisons
|
|
363
404
|
// "<": <expression> // exclusive of other comparisons
|
|
@@ -368,9 +409,12 @@ closure DynaConverter(local conv) {
|
|
|
368
409
|
// exists: <boolean> // keyname existence matches boolean
|
|
369
410
|
// }
|
|
370
411
|
//
|
|
412
|
+
// The key entry above can be a keyname or a path expression resulting from a call to makePath().
|
|
413
|
+
// Please note that array subscripts, which index into lists, must be integers.
|
|
414
|
+
//
|
|
371
415
|
|
|
372
416
|
conv.createKeyExpression = function createKeyExpression(params, conds
|
|
373
|
-
local attval, attname, rangename, range, keyname, op, expr, rangex, arg1, arg2) {
|
|
417
|
+
local attval, attname, rangename, ranges, range, keyname, op, expr, rangex, arg1, arg2) {
|
|
374
418
|
|
|
375
419
|
// dupeop
|
|
376
420
|
//
|
|
@@ -389,18 +433,9 @@ closure DynaConverter(local conv) {
|
|
|
389
433
|
ranges = [ranges]
|
|
390
434
|
for range in ranges {
|
|
391
435
|
rangex = false
|
|
392
|
-
if
|
|
393
|
-
keyname = []
|
|
394
|
-
expr = range.key
|
|
395
|
-
while expr
|
|
396
|
-
keyname.push(attname(pop(expr)))
|
|
397
|
-
keyname = keyname.join(".")
|
|
398
|
-
}
|
|
399
|
-
else
|
|
400
|
-
keyname = tostring(range.key)
|
|
401
|
-
if !keyname
|
|
436
|
+
if !range.key
|
|
402
437
|
return (list(Error("key expression requires key name")))
|
|
403
|
-
keyname = attname(
|
|
438
|
+
keyname = attname(range.key)
|
|
404
439
|
for `(op, expr) in range {
|
|
405
440
|
if op == "key"
|
|
406
441
|
continue
|
|
@@ -465,7 +500,7 @@ closure DynaConverter(local conv) {
|
|
|
465
500
|
// value is a standard (error, data) tuple where the data is just { ok: true } if no error.
|
|
466
501
|
//
|
|
467
502
|
conv.createUpdateExpression = function createUpdateExpression(params, updates,
|
|
468
|
-
local attval, attname, output, item) {
|
|
503
|
+
local attval, attname, output, item, key, value) {
|
|
469
504
|
|
|
470
505
|
if params.UpdateExpression
|
|
471
506
|
return (list(Error("conv.createUpdateExpression: UpdateExpression already exists")))
|
|
@@ -519,13 +554,28 @@ closure DynaConverter(local conv) {
|
|
|
519
554
|
*
|
|
520
555
|
* DynamoDB table.
|
|
521
556
|
*
|
|
557
|
+
* options:
|
|
558
|
+
* {
|
|
559
|
+
* hashKey: <symbol> // hash key symbol
|
|
560
|
+
* rangeKey: <symbol> // range key symbol (requres hash key)
|
|
561
|
+
* }
|
|
562
|
+
*
|
|
522
563
|
*/
|
|
523
564
|
|
|
524
|
-
closure DynaTable(dyna, tablename, local table) {
|
|
565
|
+
closure DynaTable(dyna, tablename, options, local table) {
|
|
566
|
+
global()
|
|
525
567
|
table = new(object, this)
|
|
526
568
|
table.name = tablename
|
|
527
569
|
if dyna.ycdb
|
|
528
570
|
table.ycdb = true // Yandex Cloud version of DynamoDB
|
|
571
|
+
if options.hashKey || options.rangeKey {
|
|
572
|
+
if options.hashKey && symbol(options.hashKey) && symbol(options.rangeKey) { // NB: false is a symbol
|
|
573
|
+
table.hashKey = options.hashKey
|
|
574
|
+
if options.rangeKey
|
|
575
|
+
table.rangeKey = options.rangeKey
|
|
576
|
+
} else
|
|
577
|
+
debuglog("DynaTable: invalid (hashKey/rangeKey) option:", tablename, list(options.hashKey, options.rangeKey))
|
|
578
|
+
}
|
|
529
579
|
|
|
530
580
|
// info
|
|
531
581
|
//
|
|
@@ -544,13 +594,25 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
544
594
|
// rangeType: <name> // string | numeric (if exists)
|
|
545
595
|
// }
|
|
546
596
|
//
|
|
547
|
-
|
|
597
|
+
// 20240313: Note that the YCDB / AWS DynamoDB SDK combination has been found to occasionally
|
|
598
|
+
// fail to execute the callback within 30 seconds with multiple overlapping info() calls. In the
|
|
599
|
+
// current example three calls to a single table are made from inside asyncArray(), and all of
|
|
600
|
+
// them get hung up here lacking a callback. Unclear whether the cause is the SDK, the lack of
|
|
601
|
+
// using the newer promises API, or the underlying YDB service. The main reason this is called
|
|
602
|
+
// is to discover the hash and range keys, which can be statically specified. The pragmatic
|
|
603
|
+
// resolution is to allow the caller to specify the keys, which is faster and should avoid this
|
|
604
|
+
// particular challenge. But also info() now combines overlapping calls.
|
|
605
|
+
//
|
|
606
|
+
table.info = closure info(local params, pending, result) {
|
|
548
607
|
if !dyna.aws
|
|
549
608
|
return (list(Error("DynaTable.info: not logged into AWS")))
|
|
550
609
|
params = {
|
|
551
610
|
TableName: tablename
|
|
552
611
|
}
|
|
612
|
+
if (pending = table.info_pending)
|
|
613
|
+
return (pending.wait()) // use the first call's results
|
|
553
614
|
pending = new(nonce)
|
|
615
|
+
table.info_pending = pending // combine overlapping info() calls
|
|
554
616
|
dyna.aws.describeTable(params, function(error, data, local output, key) {
|
|
555
617
|
if error {
|
|
556
618
|
dynaTableError = error
|
|
@@ -581,7 +643,9 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
581
643
|
}
|
|
582
644
|
pending.signal(list(error, output))
|
|
583
645
|
})
|
|
584
|
-
pending.wait()
|
|
646
|
+
result = pending.wait()
|
|
647
|
+
table.info_pending = false
|
|
648
|
+
result
|
|
585
649
|
}
|
|
586
650
|
|
|
587
651
|
// create
|
|
@@ -817,7 +881,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
817
881
|
|
|
818
882
|
|
|
819
883
|
table.deleteRecord = closure deleteRecord(hashValue, rangeValue, options,
|
|
820
|
-
local params, pending) {
|
|
884
|
+
local params, error, pending) {
|
|
821
885
|
if preflightError(this)
|
|
822
886
|
return
|
|
823
887
|
params = {
|
|
@@ -881,7 +945,6 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
881
945
|
// indexHashKey: <string> // name of index hashKey
|
|
882
946
|
// range: <key-expression> // range of keys to retrieve
|
|
883
947
|
// filter: <key-expression> // conditions for items to return
|
|
884
|
-
// attmap: <dictionary> // map attribute names to #shortcuts
|
|
885
948
|
// reverse: <boolean> // reverse order
|
|
886
949
|
// project: <array> // project only specified attributes
|
|
887
950
|
// paging: <exclusive start key> // for paging; updated each call
|
|
@@ -924,8 +987,6 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
924
987
|
if error
|
|
925
988
|
return (list(Error("table.queryRecords filter:", error)))
|
|
926
989
|
}
|
|
927
|
-
if options.attmap
|
|
928
|
-
params.ExpressionAttributeNames = options.attmap
|
|
929
990
|
if options.reverse
|
|
930
991
|
params.ScanIndexForward = false
|
|
931
992
|
if options.project
|
|
@@ -955,7 +1016,6 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
955
1016
|
// {
|
|
956
1017
|
// index: <string> // index name in table
|
|
957
1018
|
// filter: <key-expression> // conditions for items to return
|
|
958
|
-
// attmap: <dictionary> // map attribute names to #shortcuts
|
|
959
1019
|
// project: <array> // project only specified attributes
|
|
960
1020
|
// paging: <exclusive start key> // for paging; updated each call
|
|
961
1021
|
// consistentRead: <boolean> // true to use consistent read
|
|
@@ -980,8 +1040,6 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
980
1040
|
if error
|
|
981
1041
|
return (list(Error("table.scanRecords filter:", error)))
|
|
982
1042
|
}
|
|
983
|
-
if options.attmap
|
|
984
|
-
params.ExpressionAttributeNames = attmap
|
|
985
1043
|
if options.project
|
|
986
1044
|
params.ProjectionExpression = options.project.join(", ")
|
|
987
1045
|
if options.paging
|
|
@@ -1016,6 +1074,7 @@ closure DynaTable(dyna, tablename, local table) {
|
|
|
1016
1074
|
*/
|
|
1017
1075
|
|
|
1018
1076
|
closure DynamoDB(local dyna) {
|
|
1077
|
+
global(awsSDK)
|
|
1019
1078
|
dyna = new(object, this)
|
|
1020
1079
|
dyna.timeout = 20000 // default 20-second timeout
|
|
1021
1080
|
dyna.conv = DynaConverter()
|
|
@@ -1045,12 +1104,12 @@ closure DynamoDB(local dyna) {
|
|
|
1045
1104
|
// table
|
|
1046
1105
|
//
|
|
1047
1106
|
// Return a new table access object for the table of the specified name. The table may not
|
|
1048
|
-
// yet exist; see DynaTable for methods.
|
|
1107
|
+
// yet exist; see DynaTable for options and methods.
|
|
1049
1108
|
//
|
|
1050
|
-
dyna.table = function table(name) {
|
|
1109
|
+
dyna.table = function table(name, options) {
|
|
1051
1110
|
if !dyna.aws
|
|
1052
1111
|
debuglog("DynamoDB.table: database not open:", name)
|
|
1053
|
-
DynaTable(dyna, name)
|
|
1112
|
+
DynaTable(dyna, name, options)
|
|
1054
1113
|
}
|
|
1055
1114
|
|
|
1056
1115
|
// timekeys
|
|
@@ -480,7 +480,7 @@ closure dawsTable(database, path, tableOptions, local table) {
|
|
|
480
480
|
table.delete = closure delete(doc, callback) {
|
|
481
481
|
if !callback
|
|
482
482
|
return (syncAdapter(delete, doc))
|
|
483
|
-
if !doc._id || !doc._id.startsWith(table.s3prefix) || !(doc._md5 || doc._id.slice(-1) == "/")
|
|
483
|
+
if !doc._id || !doc._id.startsWith(table.s3prefix) || !(doc.md5 || doc._md5 || doc._id.slice(-1) == "/")
|
|
484
484
|
return (asyncResult(callback, Error("invalid argument")))
|
|
485
485
|
database.bucket.delete(doc._id, function(error, resp) {
|
|
486
486
|
if error
|
|
@@ -513,7 +513,7 @@ closure dawsTable(database, path, tableOptions, local table) {
|
|
|
513
513
|
resp._error = "JSON decoding failed"
|
|
514
514
|
} }
|
|
515
515
|
else if resp.contentType.startsWith("image/")
|
|
516
|
-
|
|
516
|
+
resp.content = readableStreamToArrayBuffer(resp.content)
|
|
517
517
|
else if jsTypedArray(resp.content)
|
|
518
518
|
resp.content = xnew(TextDecoder, "utf-8").decode(resp.content)
|
|
519
519
|
else if jsInstanceOf(resp.content, js.w.ReadableStream)
|
|
@@ -537,8 +537,8 @@ closure dawsTable(database, path, tableOptions, local table) {
|
|
|
537
537
|
database.bucket.get(id, true, function(error, resp) {
|
|
538
538
|
if error
|
|
539
539
|
error = Error("md5 failed", error, id, { status: error.status })
|
|
540
|
-
else if resp.
|
|
541
|
-
resp = resp.
|
|
540
|
+
else if resp.md5
|
|
541
|
+
resp = resp.md5
|
|
542
542
|
else
|
|
543
543
|
resp = ""
|
|
544
544
|
callback(error, resp)
|
package/test/harness.nlg
CHANGED
package/test/test_02_context.nlg
CHANGED
|
@@ -35,11 +35,11 @@ RegisterTestCategory("JSinterop", [
|
|
|
35
35
|
|
|
36
36
|
[ "o(3);", {
|
|
37
37
|
"dev": { errors: "xcall \"[object Object]\": TypeError: object.apply is not a function" }
|
|
38
|
-
"prod": { errors: "xcall \"[object Object]\": TypeError:
|
|
38
|
+
"prod": { errors: "xcall \"[object Object]\": TypeError: e[3].apply is not a function" }
|
|
39
39
|
}[ "prod"]],
|
|
40
40
|
[ "o(Number);", {
|
|
41
41
|
"dev": { errors: "xcall \"[object Object]\": TypeError: object.apply is not a function" }
|
|
42
|
-
"prod": { errors: "xcall \"[object Object]\": TypeError:
|
|
42
|
+
"prod": { errors: "xcall \"[object Object]\": TypeError: e[3].apply is not a function" }
|
|
43
43
|
}[ "prod"]],
|
|
44
44
|
[ "Number(3);", "[Number 3]" ],
|
|
45
45
|
[ "Number(3).toString();", "\"3\"" ],
|
package/test/test_05_strings.nlg
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2017-
|
|
9
|
+
* Copyright (c) 2017-2024 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -52,11 +52,14 @@ RegisterTestCategory("strings", [
|
|
|
52
52
|
// template strings
|
|
53
53
|
//
|
|
54
54
|
|
|
55
|
-
[ "'$${4+3}';", '"7"' ], //
|
|
55
|
+
[ "'$${4+3}';", '"7"' ], // needs extra $escape to be source
|
|
56
56
|
[ "'$$${4+3}';", '"$${4+3}"' ],
|
|
57
57
|
[ "'a$${4+3}b';", '"a7b"' ],
|
|
58
|
-
[ "'$${}';",
|
|
59
|
-
[ "'$${';", '' ],
|
|
58
|
+
[ "'$${}';", { errors: "incomplete expression at (1, 3) found ;" } ],
|
|
59
|
+
[ "'$${';", '"${"' ],
|
|
60
|
+
[ "'$$${';", '"$${"' ],
|
|
61
|
+
[ "'a$${\"\"}z';", '"az"'],
|
|
62
|
+
[ "'a$$${}z';", '"a$${}z"'],
|
|
60
63
|
[ "quote('a$${4*5}b');>", '(strcat "a" (* 4 5) "b")' ],
|
|
61
64
|
|
|
62
65
|
|
package/test/test_07_lingo.nlg
CHANGED
|
@@ -266,6 +266,7 @@ RegisterTestCategory("Lingo", [
|
|
|
266
266
|
[ "vv -= 2;", "3" ],
|
|
267
267
|
[ "vv *= 2;", "6" ],
|
|
268
268
|
[ "vv /= 2;", "3" ],
|
|
269
|
+
[ "vv %= 2;", "1" ],
|
|
269
270
|
|
|
270
271
|
[ "oo = {a:3};", "{ a: 3 }" ],
|
|
271
272
|
[ "oo.a;", "3" ],
|
|
@@ -281,6 +282,7 @@ RegisterTestCategory("Lingo", [
|
|
|
281
282
|
[ "oo.a -= 2;", "3" ],
|
|
282
283
|
[ "oo.a *= 2;", "6" ],
|
|
283
284
|
[ "oo.a /= 2;", "3" ],
|
|
285
|
+
[ "oo.a %= 2;", "1" ],
|
|
284
286
|
|
|
285
287
|
[ "aa = [2,3];", "[2, 3]" ],
|
|
286
288
|
[ "++aa[1];", "4" ],
|
|
@@ -295,6 +297,7 @@ RegisterTestCategory("Lingo", [
|
|
|
295
297
|
[ "aa[1] -= 2;", "3" ],
|
|
296
298
|
[ "aa[1] *= 2;", "6" ],
|
|
297
299
|
[ "aa[1] /= 2;", "3" ],
|
|
300
|
+
[ "aa[1] %= 2;", "1" ],
|
|
298
301
|
|
|
299
302
|
|
|
300
303
|
//
|