@naanlang/naan 1.4.4 → 1.6.0

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 (61) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +4 -4
  3. package/bin/index.js +25 -3
  4. package/dist/env_web.js +164 -22
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/browser.nlg +2 -2
  7. package/frameworks/browser/https_request.nlg +2 -2
  8. package/frameworks/browser/sworker.js +28 -26
  9. package/frameworks/browser/terminals.nlg +342 -700
  10. package/frameworks/browser/worker.nlg +255 -0
  11. package/frameworks/browser/workers.nlg +5 -5
  12. package/frameworks/browser/ws_client.nlg +15 -10
  13. package/frameworks/client/apiclient.nlg +204 -19
  14. package/frameworks/client/client.nlg +22 -2
  15. package/frameworks/client/psm_client.nlg +12 -3
  16. package/frameworks/client/relaycon.nlg +87 -161
  17. package/frameworks/common/events.nlg +101 -0
  18. package/frameworks/common/repltools.nlg +80 -1
  19. package/frameworks/node/apiserver.nlg +39 -246
  20. package/frameworks/node/filesystem.nlg +39 -12
  21. package/frameworks/node/gitter.nlg +9 -7
  22. package/frameworks/node/http_request.nlg +34 -13
  23. package/frameworks/node/https_request.nlg +8 -3
  24. package/frameworks/node/node.nlg +2 -0
  25. package/frameworks/node/pty.nlg +346 -0
  26. package/frameworks/node/shell.nlg +4 -4
  27. package/frameworks/node/worker.nlg +409 -539
  28. package/frameworks/node/ws_client.nlg +2 -2
  29. package/frameworks/project/build.nlg +1 -1
  30. package/frameworks/project/projects.nlg +2 -1
  31. package/frameworks/running/debugnub.nlg +17 -4
  32. package/frameworks/running/executors.nlg +27 -274
  33. package/frameworks/running/instances.nlg +393 -0
  34. package/frameworks/running/remote_req.nlg +143 -0
  35. package/frameworks/running/remote_res.nlg +89 -0
  36. package/frameworks/running/taskexec.nlg +2 -2
  37. package/frameworks/service/imp.nlg +736 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +105 -0
  40. package/frameworks/service/nubweb.nlg +106 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/dbt_pouch.nlg +26 -13
  43. package/frameworks/storage/psm.nlg +39 -5
  44. package/frameworks/storage/psm_dbtables.nlg +10 -2
  45. package/lib/browser/env_web.js +171 -29
  46. package/lib/browser/env_webworker.js +91 -5
  47. package/lib/core/naanlib.js +5 -5
  48. package/lib/env_node.js +184 -8
  49. package/lib/env_nodeworker.js +74 -1
  50. package/package.json +1 -1
  51. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
  52. package/plugins/serviceAws/aws_dynamo.nlg +1 -0
  53. package/plugins/serviceAws/aws_s3.nlg +3 -3
  54. package/plugins/serviceAws/aws_sqs.nlg +3 -1
  55. package/plugins/serviceAws/aws_utils.nlg +3 -1
  56. package/plugins/serviceAws/dbt_aws.nlg +22 -20
  57. package/plugins/serviceAws/psm_aws.nlg +2 -1
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_02_context.nlg +24 -1
  60. package/test/test_03_jsinterop.nlg +32 -3
  61. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -0,0 +1,71 @@
1
+ /*
2
+ * model.nlg
3
+ * Naanlib/frameworks/service
4
+ *
5
+ * Naan service model.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2023-2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * ServiceModel
16
+ *
17
+ * Model object for services, such as instance management. This is used to conceal architectural
18
+ * details from service users.
19
+ *
20
+ */
21
+
22
+ closure ServiceModel(local model) {
23
+ global()
24
+ model = new(object, this)
25
+ model@\.class = new(object)
26
+
27
+ // add
28
+ //
29
+ // Add a location to our model with the specified name.
30
+ //
31
+ model@\.class.add = function add(name, local node) {
32
+ if !string(name) || name.trim() == ""
33
+ throw("ServiceModel.add: invalid name")
34
+ node = new(object)
35
+ node.id = name
36
+ node.parent = self
37
+ node@\.parent = self
38
+ self[name] = node
39
+ node
40
+ }
41
+
42
+ // children
43
+ //
44
+ // List the ids of the children of the specified location, which is false for the root.
45
+ //
46
+ model@\.class.children = function children(parent) {
47
+ self.*.toarray.filter(function(id) { !id.startsWith(".") })
48
+ }
49
+
50
+ // finis
51
+
52
+ model
53
+ };
54
+
55
+
56
+ /*
57
+ * modelInit
58
+ *
59
+ * Initialize the component.
60
+ *
61
+ */
62
+
63
+ function modelInit(local manifest) {
64
+ manifest = `(ServiceModel, modelInit)
65
+
66
+ Naan.module.build(module.id, "model", function(modobj, compobj) {
67
+ require("./service.nlg")
68
+ compobj.manifest = manifest
69
+ modobj.exports.ServiceModel = ServiceModel
70
+ })
71
+ } ();
@@ -0,0 +1,105 @@
1
+ /*
2
+ * nubnode.nlg
3
+ * Naanlib/frameworks/service
4
+ *
5
+ * Nub for node worker access to IMP.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * NodeWorkerNub
16
+ *
17
+ * NodeWorkerNub provides an IMP interface that runs on node workers. This allows the worker to
18
+ * communicate with the IMP network and the main thread. The worker IMP must be created before this.
19
+ *
20
+ */
21
+
22
+ closure NodeWorkerNub(imp, options, local nownub) {
23
+ global(App,js)
24
+ if App.workerNub
25
+ return (App.workerNub) // singleton
26
+
27
+ options = merge({
28
+ name: "NodeWorkerNub"
29
+ type: "node-worker-nub"
30
+ }, options)
31
+ nownub = new(object, this)
32
+ nownub.instanceID = imp.us.instanceID || UUID()
33
+ nownub.dispatcher = require("naanlib:frameworks/running/taskexec.nlg").TaskDispatcher()
34
+
35
+ // impReceive
36
+ //
37
+ // Receive a remote request and reply to it.
38
+ //
39
+ nownub.impReceive = function impReceive(message, sourceID) {
40
+ if message.id == "targetin"
41
+ nownub.dispatcher(message.data, function(reply) {
42
+ nownub.sender(sourceID, {
43
+ id: "targetout",
44
+ data: reply
45
+ })
46
+ })
47
+ }
48
+
49
+ // impUnknown
50
+ //
51
+ // Received an unknown destination notice from the other side.
52
+ //
53
+ nownub.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
54
+ if !gateway
55
+ debuglog("NodeWorkerNub.impUnknownID:", unknownID, "sourceID:", sourceID)
56
+ }
57
+
58
+ // message event
59
+ //
60
+ // Incoming data.
61
+ //
62
+ js.t.msgPort.on("message", function(msg) {
63
+ if msg.id == "naan-imp-node"
64
+ nownub.impconn.dispatch(new(msg.data))
65
+ })
66
+
67
+ // register our connects *after* the receivers are configured above
68
+
69
+ nownub.sender = imp.register(nownub, {
70
+ name: options.name
71
+ type: options.type
72
+ meta: options.meta || undefined
73
+ instanceID: nownub.instanceID
74
+ services: [ "naan-node-worker" ]
75
+ })
76
+ nownub.impconn = imp.requester(function(data) {
77
+ js.t.msgPort.postMessage({ // send to main
78
+ id: "naan-imp-node",
79
+ data: data
80
+ })
81
+ list(false, { sent: true })
82
+ })
83
+
84
+ // finis
85
+
86
+ App.workerNub = nownub
87
+ };
88
+
89
+
90
+ /*
91
+ * nubnoInit
92
+ *
93
+ * Initialize the component.
94
+ *
95
+ */
96
+
97
+ function nubnoInit(local manifest) {
98
+ manifest = `(NodeWorkerNub, nubnoInit)
99
+
100
+ Naan.module.build(module.id, "nubnode", function(modobj, compobj) {
101
+ require("./service.nlg")
102
+ compobj.manifest = manifest
103
+ modobj.exports.NodeWorkerNub = NodeWorkerNub
104
+ })
105
+ } ();
@@ -0,0 +1,106 @@
1
+ /*
2
+ * nubweb.nlg
3
+ * Naanlib/frameworks/service
4
+ *
5
+ * Nub for web worker access to IMP.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * WebWorkerNub
16
+ *
17
+ * WebWorkerNub provides an IMP interface that runs on web workers. This allows the worker to
18
+ * communicate with the IMP network and the main thread. The worker IMP must be created before this.
19
+ *
20
+ */
21
+
22
+ closure WebWorkerNub(imp, options, local wonub) {
23
+ global(App,js)
24
+ if App.workerNub
25
+ return (App.workerNub) // singleton
26
+
27
+ options = merge({
28
+ name: "WebWorkerNub"
29
+ type: "web-worker-nub"
30
+ }, options)
31
+ wonub = new(object, this)
32
+ wonub.instanceID = imp.us.instanceID || UUID()
33
+ wonub.dispatcher = require("naanlib:frameworks/running/taskexec.nlg").TaskDispatcher()
34
+
35
+ // impReceive
36
+ //
37
+ // Receive a remote request and reply to it.
38
+ //
39
+ wonub.impReceive = function impReceive(message, sourceID) {
40
+ if message.id == "targetin"
41
+ wonub.dispatcher(message.data, function(reply) {
42
+ wonub.sender(sourceID, {
43
+ id: "targetout",
44
+ data: reply
45
+ })
46
+ })
47
+ }
48
+
49
+ // impUnknown
50
+ //
51
+ // Received an unknown destination notice from the other side.
52
+ //
53
+ wonub.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
54
+ if !gateway
55
+ debuglog("WebWorkerNub.impUnknownID:", unknownID, "sourceID:", sourceID)
56
+ }
57
+
58
+ // message event
59
+ //
60
+ // Incoming data.
61
+ //
62
+ js.w.addEventListener("message", function message(e, local msg) {
63
+ msg = new(e.data)
64
+ if msg.scope == "naan-imp-web"
65
+ wonub.impconn.dispatch(msg.data)
66
+ })
67
+
68
+ // register our connects *after* the receivers are configured above
69
+
70
+ wonub.sender = imp.register(wonub, {
71
+ name: options.name
72
+ type: options.type
73
+ meta: options.meta || undefined
74
+ instanceID: wonub.instanceID
75
+ services: [ "naan-web-worker" ]
76
+ })
77
+ wonub.impconn = imp.requester(function(data) {
78
+ js.w.postMessage({ // send to main
79
+ scope: "naan-imp-web",
80
+ data: data
81
+ })
82
+ list(false, { sent: true })
83
+ })
84
+
85
+ // finis
86
+
87
+ App.workerNub = wonub
88
+ };
89
+
90
+
91
+ /*
92
+ * nubweInit
93
+ *
94
+ * Initialize the component.
95
+ *
96
+ */
97
+
98
+ function nubweInit(local manifest) {
99
+ manifest = `(WebWorkerNub, nubweInit)
100
+
101
+ Naan.module.build(module.id, "nubweb", function(modobj, compobj) {
102
+ require("./service.nlg")
103
+ compobj.manifest = manifest
104
+ modobj.exports.WebWorkerNub = WebWorkerNub
105
+ })
106
+ } ();
@@ -0,0 +1,28 @@
1
+ /*
2
+ * service.nlg
3
+ * Naanlib/frameworks/service
4
+ *
5
+ * Root component for services functionality.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * svcInit
16
+ *
17
+ * Initialize the service module.
18
+ *
19
+ */
20
+
21
+ function svcInit(local manifest) {
22
+ manifest = `(svcInit)
23
+
24
+ Naan.module.build(module.id, "service", function(modobj, compobj) {
25
+ require("../common/common.nlg").LiveImport()
26
+ compobj.manifest = manifest
27
+ })
28
+ } ();
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * column positioning: // // !
9
9
  *
10
- * Copyright (c) 2017-2024 by Richard C. Zulch
10
+ * Copyright (c) 2017-2025 by Richard C. Zulch
11
11
  *
12
12
  */
13
13
 
@@ -76,21 +76,31 @@ closure dapoTable(database, rootpath, local table, prefix) {
76
76
 
77
77
  // add
78
78
  //
79
- // Add a new record to the table with optional metadata. This will fail if the key already
80
- // exists. Returns the new document record.
81
-
82
- table.add = closure add(key, content, metadata, callback, local id, doc) {
79
+ // Add a new record to the table with options. This will fail if the key already exists. Optional
80
+ // metadata is stored with the record and cannot be changed without calling update. Metadata keys
81
+ // cannot begin with underscore ("_").
82
+ //
83
+ // The returned document, which can be used with update, has the following keys:
84
+ // key - the key name of the record
85
+ // content - the body content stored under in the record
86
+ // _md5 - the MD5 hash of the content
87
+ // _length - the length of the content
88
+ // _id - the PouchDB ID
89
+ // _rev - the PouchDB revision
90
+ // <...> - various metadata keys
91
+
92
+ table.add = closure add(key, content, options, callback, local id, doc) {
83
93
  if !callback
84
- return (syncAdapter(add, key, content, metadata))
94
+ return (syncAdapter(add, key, content, options))
85
95
  id = makeID(key)
86
96
  if !string(id)
87
97
  return (asyncResult(callback, id)) // error from makeID
88
- if metadata && !dictionary(metadata)
98
+ if options.metadata && !dictionary(options.metadata)
89
99
  return (asyncResult(callback, Error("invalid arguments")))
90
100
  if !database.pouch
91
101
  return (asyncResult(callback, Error("database closed")))
92
- if metadata {
93
- doc = new(metadata) // start with metadata
102
+ if options.metadata {
103
+ doc = new(options.metadata) // start with metadata
94
104
  doc._rev = undefined } // _rev not allowed in metadata
95
105
  else
96
106
  doc = { }
@@ -115,12 +125,13 @@ closure dapoTable(database, rootpath, local table, prefix) {
115
125
 
116
126
  // update
117
127
  //
118
- // Update a record in the table using the document previously returned by add or get.
119
- // This will fail if the record has been modified by someone else in the meantime.
128
+ // Update a record in the table using the document previously returned by add or get. Specify
129
+ // options to update those values, otherwise existing/default values are used. This will fail
130
+ // if the record has been modified by someone else in the meantime.
120
131
 
121
- table.update = closure update(doc, callback) {
132
+ table.update = closure update(doc, options, callback) {
122
133
  if !callback
123
- return (syncAdapter(update, doc))
134
+ return (syncAdapter(update, doc, options))
124
135
  if !doc._id || !doc._id.startsWith(table.prefix) || !doc._rev
125
136
  return (asyncResult(callback, Error("invalid argument")))
126
137
  if makeID(doc.key) != doc._id
@@ -130,6 +141,8 @@ closure dapoTable(database, rootpath, local table, prefix) {
130
141
  doc = new(doc)
131
142
  doc._md5 = undefined
132
143
  doc._length = undefined
144
+ if options.metadata
145
+ doc = merge(doc, options.metadata)
133
146
  database.pouch.put(doc, function(error, resp) {
134
147
  if error
135
148
  error = Error("update failed", error)
@@ -252,6 +252,14 @@ closure MakePSMutil(fs, local util) {
252
252
  // follow: <boolean> -- follow links instead of treating them as files
253
253
  // idmode: <boolean> -- preserve id/mode where possible
254
254
  // times: <boolean> -- preserve time where possible
255
+ // cacheControl: [
256
+ // { -- apply cacheControl to files matching regex
257
+ // match: <regex-string>
258
+ // flags: <regex-flags> -- default "i" ignore case
259
+ // cacheControl: <string>
260
+ // }
261
+ // ...
262
+ // ]
255
263
  // }
256
264
  //
257
265
  // Limitations:
@@ -295,10 +303,13 @@ closure MakePSMutil(fs, local util) {
295
303
  if destpath == "."
296
304
  destpath = "" // joined empty strings
297
305
  `(error, stat) = fs.info(srcpath, fsopt)
298
- if stat.type != "directory" && (!options.follow || stat.type != "symlink") {
306
+ if stat.type == "symlink" && options.follow && !fs.dirList(srcpath).0
307
+ stat.type = "directory" // for our purposes this is a directory
308
+ if stat.type != "directory" {
299
309
  copies.push({ // copy file or symlink
300
310
  source: srcpath
301
311
  dest: destpath
312
+ entry: entry
302
313
  })
303
314
  return (list(false, { ok: true })) // generic success not used
304
315
  }
@@ -346,10 +357,29 @@ closure MakePSMutil(fs, local util) {
346
357
  }).wait()
347
358
  }
348
359
  //
360
+ // Pass 3 - determine cache control values
361
+ //
362
+ let (regexes, item, file) {
363
+ regexes = new(options.cacheControl)
364
+ try {
365
+ for item in regexes
366
+ item.regex = RegExp(item.match, item.flags || "i")
367
+ } catch {
368
+ ErrorDebuglog("PsmUtil.deepcopy: RegExp exception", exception)
369
+ regexes = []
370
+ }
371
+ for file in copies
372
+ for item in regexes
373
+ if item.regex.test(file.entry) {
374
+ file.cacheControl = item.cacheControl
375
+ break
376
+ }
377
+ } ()
378
+ //
349
379
  // Pass 3 - copy files and links
350
380
  //
351
381
  asyncArray(copies, 10, closure cpfiles(entry,
352
- local error, data, dest, instat, outstat) {
382
+ local error, data, dest, instat, outstat, foptions) {
353
383
  `(error, instat) = fs.info(entry.source, fsopt)
354
384
  if !error {
355
385
  `(error, outstat) = outfs.info(entry.dest)
@@ -372,9 +402,13 @@ closure MakePSMutil(fs, local util) {
372
402
  `(error, data) = outfs.symlink(data, entry.dest)
373
403
  }
374
404
  else {
375
- `(error, data) = fs.readFile(entry.source, { encoding: "binary"})
376
- if !error
377
- `(error, data) = outfs.writeFile(entry.dest, data, { encoding: "binary"})
405
+ foptions = { encoding: "binary"}
406
+ `(error, data) = fs.readFile(entry.source, foptions)
407
+ if !error {
408
+ if entry.cacheControl
409
+ foptions.cacheControl = entry.cacheControl
410
+ `(error, data) = outfs.writeFile(entry.dest, data, foptions)
411
+ }
378
412
  if !error && (options.idmode || options.times) {
379
413
  if !options.idmode
380
414
  instat.mode = instat.uid = instat.gid = false
@@ -185,6 +185,7 @@ closure FSview(table, rootpath, local view) {
185
185
  // emptyok: true -- allow empty directory names (leading "/" or embdedded "//")
186
186
  // md5: <hash> -- existing file content must match hash
187
187
  // writenew: true -- write as a new file, with updated create time
188
+ // cacheControl: <string> -- set the cacheControl header
188
189
 
189
190
  closure dbWriteRecord(path, content, options, callback) {
190
191
  if !callback
@@ -247,9 +248,15 @@ closure FSview(table, rootpath, local view) {
247
248
  path: path
248
249
  })))
249
250
  if doc
250
- `(error, data) = view.table.update(doc) // update existing record
251
+ `(error, data) = view.table.update(doc, { // update existing record
252
+ metadata: metadata
253
+ cacheControl: options.cacheControl
254
+ })
251
255
  else
252
- `(error, data) = view.table.add(path, content, metadata) // create new record
256
+ `(error, data) = view.table.add(path, content, { // create new record
257
+ metadata: metadata
258
+ cacheControl: options.cacheControl
259
+ })
253
260
  if error
254
261
  callback(error)
255
262
  else
@@ -553,6 +560,7 @@ closure FSview(table, rootpath, local view) {
553
560
  // available:
554
561
  // exclusive: true -- file must not already exist
555
562
  // md5: <hash> -- existing file content must match hash
563
+ // cacheControl: <string> -- set the cacheControl header (if supported by underlying storage)
556
564
 
557
565
  view.write = closure write(path, content, options, callback) {
558
566
  if !callback