@naanlang/naan 1.0.16 → 1.2.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 (45) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +13 -11
  3. package/bin/index.js +4 -4
  4. package/dist/naan.min.js +14 -14
  5. package/frameworks/browser/https_request.nlg +26 -10
  6. package/frameworks/browser/sworker.js +26 -25
  7. package/frameworks/browser/terminals.nlg +118 -55
  8. package/frameworks/browser/ws_client.nlg +124 -0
  9. package/frameworks/client/apiclient.nlg +82 -116
  10. package/frameworks/client/psm_client.nlg +37 -16
  11. package/frameworks/client/relaycon.nlg +308 -0
  12. package/frameworks/common/common.nlg +1 -1
  13. package/frameworks/common/utils.nlg +40 -2
  14. package/frameworks/node/apiserver.nlg +342 -103
  15. package/frameworks/node/https_request.nlg +32 -6
  16. package/frameworks/node/node.nlg +60 -2
  17. package/frameworks/node/psm_server.nlg +11 -7
  18. package/frameworks/node/worker.nlg +22 -9
  19. package/frameworks/node/ws_client.nlg +127 -0
  20. package/frameworks/project/build.nlg +3 -2
  21. package/frameworks/project/projects.nlg +8 -2
  22. package/frameworks/running/debugnub.nlg +2 -2
  23. package/frameworks/running/debugutil.nlg +1 -1
  24. package/frameworks/running/executors.nlg +69 -17
  25. package/frameworks/running/sourcecode.nlg +2 -2
  26. package/frameworks/running/taskexec.nlg +25 -25
  27. package/frameworks/storage/dbt_pouch.nlg +8 -6
  28. package/frameworks/storage/file_manager.nlg +6 -3
  29. package/frameworks/storage/psm.nlg +5 -3
  30. package/frameworks/storage/psm_dbtables.nlg +75 -53
  31. package/frameworks/storage/resources.nlg +4 -2
  32. package/lib/browser/env_web.js +6 -6
  33. package/lib/browser/env_webworker.js +1 -1
  34. package/lib/core/naanlib.js +14 -14
  35. package/lib/env_node.js +97 -8
  36. package/lib/env_nodeworker.js +22 -4
  37. package/package.json +1 -1
  38. package/plugins/serviceAws/aws/lambda_rest_index.js +5 -1
  39. package/plugins/serviceAws/aws_dynamo.nlg +91 -32
  40. package/plugins/serviceAws/dbt_aws.nlg +5 -5
  41. package/plugins/serviceAws/psm_aws.nlg +2 -2
  42. package/test/harness.nlg +1 -1
  43. package/test/test_01_core.nlg +30 -4
  44. package/test/test_02_context.nlg +2 -2
  45. package/test/test_07_lingo.nlg +3 -0
@@ -0,0 +1,124 @@
1
+ /*
2
+ * ws_client.nlg
3
+ *
4
+ * WebSocket client operations for the browser.
5
+ *
6
+ * column positioning: // // !
7
+ *
8
+ * Copyright (c) 2023 by Richard C. Zulch
9
+ *
10
+ */
11
+
12
+
13
+ /*
14
+ * WebSocket
15
+ *
16
+ * Create a WebSocket client connection object using the existing HTTPS connection.
17
+ *
18
+ * Options:
19
+ * {
20
+ * host: <string> // host:port
21
+ * onopen: <proc>(e) // connection has opened
22
+ * onmessage: <proc>(e, message) // message received
23
+ * onerror: <proc>(e) // error occurred
24
+ * onclose: <proc>(e) // connection has closed
25
+ * }
26
+ *
27
+ */
28
+
29
+ closure WebSocket(options, local wsock) {
30
+ global(window)
31
+ wsock = new(object, this)
32
+ options = new(options) || { }
33
+ if !options.host
34
+ options.host = window.location.host
35
+
36
+ // connect
37
+ //
38
+ // Connect with the host if not already connected, returning a result tuple.
39
+ //
40
+ wsock.connect = closure connect(local pending) {
41
+ if wsock.ws
42
+ return (list(false, { open: true }))
43
+ wsock.ws = xnew(window.WebSocket, "ws://${options.host}")
44
+ pending = new(nonce)
45
+
46
+ // onopen
47
+ // The WebSocket connection is now open.
48
+ wsock.ws.onopen = closure onopen(e, local spawner) {
49
+ pending.signal(list(false, { ok: true }))
50
+ options.onopen(e)
51
+ }
52
+
53
+ // onmessage
54
+ // A message was received from the worker via WebSockets.
55
+ wsock.ws.onmessage = closure onmessage(e, message) {
56
+ try {
57
+ message = new(JSONparse(e.data))
58
+ } catch {
59
+ debuglog("can't decode incoming ws message:", exception)
60
+ options.onerror(Error("can't decode incoming message:", exception))
61
+ return
62
+ }
63
+ options.onmessage(e, message)
64
+ }
65
+
66
+ // onerror
67
+ // An error occurred on the WebSocket connection.
68
+ wsock.ws.onerror = closure onerror(e, local error) {
69
+ error = Error("websocket failed", e)
70
+ options.onerror(e, error)
71
+ pending.signal(list(error)) // ensure connect completes
72
+ }
73
+
74
+ // onclose
75
+ // The WebSocket connection was closed.
76
+ wsock.ws.onclose = closure onclose(e) {
77
+ options.onclose(e, e.code, e.reason)
78
+ pending.signal(list(Error("connection closed"))) // ensure connect completes
79
+ wsock.ws = false
80
+ }
81
+
82
+ pending.wait()
83
+ }
84
+
85
+ // send
86
+ //
87
+ // Send a message dictionary.
88
+ //
89
+ wsock.send = function send(message) {
90
+ wsock.ws.send(JSONstringify(message))
91
+ }
92
+
93
+ // close
94
+ //
95
+ // Close the connection.
96
+ //
97
+ wsock.close = function close(reason) {
98
+ if !reason
99
+ reason = 1000 // normal close
100
+ wsock.ws.close(reason)
101
+ }
102
+
103
+ // finis
104
+
105
+ wsock
106
+ };
107
+
108
+
109
+ /*
110
+ * ws_clientInit
111
+ *
112
+ * Initialize WebSocket client operations for browsers.
113
+ *
114
+ */
115
+
116
+ function ws_clientInit(local manifest) {
117
+ manifest = `(WebSocket, ws_clientInit)
118
+
119
+ Naan.module.build(module.id, "ws_client", function(modobj, compobj) {
120
+ require("browser.nlg")
121
+ compobj.manifest = manifest
122
+ modobj.exports.WebSocket = WebSocket
123
+ })
124
+ }();
@@ -2,174 +2,140 @@
2
2
  * apiclient.nlg
3
3
  * Naanlib/frameworks/client
4
4
  *
5
- * Access to Nide API with HTTP client.
5
+ * Access to Nide API with browser HTTP client.
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2022 by Richard C. Zulch
9
+ * Copyright (c) 2017-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
13
13
 
14
14
  /*
15
- * MakeNideAPIclient
16
- *
17
- * Make a DAS API client object for browsers. This client API handles a single project at a
18
- * time, from load() to close().
15
+ * NideAPIclient
19
16
  *
17
+ * Make an API client object for browsers.
20
18
  *
21
19
  */
22
20
 
23
- closure MakeNideAPIclient(url, guid, local client) {
21
+ closure NideAPIclient(url, guid, apiRequester, local client) {
22
+ global()
24
23
  client = new(object, this)
24
+ if !url.match(RegExp("^https?:\/\/", "i"))
25
+ url = "http://${url}"
25
26
  client.url = url
26
27
  client.guid = guid
27
28
  client.notifyAfter = 0
29
+ client.instanceID = UUID() // our client's unique ID
28
30
 
29
- // clientRequest() - general request routine
30
-
31
- closure clientRequest(method, path, options, cbReq, local request) {
32
- request = xnew(js.w.XMLHttpRequest)
33
- request.addEventListener("load", function (event) {
34
- cbReq(false, request, event)
35
- })
36
-
37
- request.addEventListener("error", function reqFailed(event, local errtext) {
38
- if request.status == 0
39
- errtext = "connectivity"
40
- else
41
- errtext = "request failed"
42
- error = Error(errtext, {
43
- status: request.status
44
- method: method
45
- path: path
46
- })
47
- cbReq(error, request, event)
48
- })
31
+ // clientRequest() - append our guid to the headers
49
32
 
50
- request.open(method, path)
51
- if options.responseType
52
- request.responseType = options.responseType
53
- if options.contentType
54
- request.setRequestHeader("Content-Type", options.contentType)
33
+ closure clientRequest(url, options) {
34
+ options = merge(options)
55
35
  if guid
56
- request.setRequestHeader("x-naanlang-api-guid", guid)
57
- request.send(options.putdata)
36
+ options.headers = [list("x-naanlang-api-guid", guid)]
37
+ apiRequester(url, options)
58
38
  }
59
39
 
60
40
  //
61
41
  // docs()
62
42
  //
63
43
 
64
- client.docs = closure docs() {
65
- clientRequest("GET", url.concat("/"), false, function (error, req, event) {
66
- printline(req.responseText)
67
- })
44
+ client.docs = closure docs(local error, content, extra) {
45
+ `(error, content, extra) = clientRequest(url.concat("/"))
46
+ printline(content)
68
47
  }
69
48
 
70
49
  //
71
50
  // status(callback, timeoutMS)
72
51
  //
73
52
 
74
- client.status = closure status(cbStatus, timeoutms, local query, options, queued) {
75
- options = new(dictionary)
76
- options.timeout = timeoutms
77
- options.after = client.notifyAfter
78
- query = EncodeQuery("/status/?", options)
79
- clientRequest("GET", url.concat(query), false, function (error, req, event) {
80
- if (error || req.status != 200)
81
- cbStatus()
82
- else if (length(req.responseText) == 0)
83
- cbStatus([])
84
- else {
85
- queued = new(JSONparse(req.responseText))
86
- queued.forEach(function(item,index,array) {
87
- if client.notifyAfter < item.stamp
88
- client.notifyAfter = item.stamp
89
- })
90
- cbStatus(queued)
91
- }
53
+ client.status = closure status(cbStatus, timeoutms, local query, error, content, extra, queued) {
54
+ query = EncodeQuery("/status/?", {
55
+ timeout: timeoutms
56
+ after: client.notifyAfter
92
57
  })
58
+ `(error, content, extra) = clientRequest(url.concat(query))
59
+ if (error || extra.status != 200)
60
+ cbStatus()
61
+ else if (length(content) == 0)
62
+ cbStatus([])
63
+ else {
64
+ queued = new(content)
65
+ queued.forEach(function(item, index, array) {
66
+ if client.notifyAfter < item.stamp
67
+ client.notifyAfter = item.stamp
68
+ })
69
+ cbStatus(queued)
70
+ }
93
71
  }
94
72
 
95
73
  //
96
74
  // get
97
75
  //
98
76
 
99
- client.get = closure get(path, cbRead) {
100
- options.path = filepath
101
- clientRequest("GET", url.concat("/", path), false, cbRead)
77
+ client.get = closure get(path, options, local error, content, extra) {
78
+ `(error, content, extra) = clientRequest(url.concat("/", path))
79
+ if !error && extra.status != 200
80
+ error = Error("readFile failed: ".concat(extra.status), {
81
+ status: extra.status
82
+ })
83
+ if error
84
+ list(error)
85
+ else
86
+ list(error, content, extra)
102
87
  }
103
88
 
104
89
  //
105
- // readFile(filepath, cbRead)
90
+ // readFile(filepath, options)
106
91
  //
107
92
 
108
- client.readFile = closure readFile(filepath, cbRead, local query, options) {
109
- options = new(dictionary)
110
- options.path = filepath
111
- query = EncodeQuery("/readfile/?", options)
112
- clientRequest("GET", url.concat(query), false, cbRead)
93
+ client.readFile = closure readFile(filepath, options, local query, error, content, extra) {
94
+ query = EncodeQuery("/readfile/?", {
95
+ path: filepath
96
+ })
97
+ `(error, content, extra) = clientRequest(url.concat(query), options)
98
+ if !error && extra.status != 200
99
+ error = Error("readFile failed: ".concat(extra.status), {
100
+ status: extra.status
101
+ })
102
+ if error
103
+ list(error)
104
+ else
105
+ list(error, content, extra)
113
106
  }
114
107
 
115
108
  //
116
109
  // psmRemote(options, data, callback)
117
110
  //
118
- // Perform an operation and call back when done. If the communication works then the result is:
119
- // callback(false, (remote_error, remote_data)).
120
- // Otherwise if communication failed then the result is:
121
- // callback(<error>).
111
+ // Perform an operation and call back when done.
122
112
 
123
- client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions, error) {
113
+ client.psmRemote = closure psmRemote(options, data, callback,
114
+ local query, reqOptions, error, content, extra) {
124
115
  query = EncodeQuery("/psm/?", options)
125
- if data
126
- method = "PUT"
127
- else
128
- method = "GET"
129
116
  reqOptions = {
130
117
  putdata: data
131
118
  }
132
- if options.encoding == "binary" {
133
- if data
134
- reqOptions.contentType = "application/octet-stream" // we are sending binary data
135
- else
136
- reqOptions.responseType = "arraybuffer" // we are requesting binary data
137
- } else if options.encoding == "json" {
138
- if data {
139
- reqOptions.contentType = "application/json;charset=UTF-8" // we are sending JSON
140
- `(error, data) = JsonStringify(data)
141
- if error
142
- return (callback(Error("psmRemote failed:", error)))
143
- reqOptions.putdata = data
144
- }
145
- else
146
- reqOptions.responseType = "application/json" // we are requesting JSON
147
- }
148
- clientRequest(method, url.concat(query), reqOptions, function (error, req, event, local data) {
149
- if !error && req.status != 200
150
- error = Error("NodeFS request failed: ".concat(req.status), {
151
- status: req.status
152
- method: method
153
- query: query
154
- })
119
+ if !data
120
+ reqOptions.encoding = options.encoding // expected response format
121
+ else if options.encoding == "binary"
122
+ reqOptions.contentType = "application/octet-stream" // we are sending binary data
123
+ else if options.encoding == "json" {
124
+ reqOptions.contentType = "application/json;charset=UTF-8" // we are sending JSON
125
+ `(error, data) = JsonStringify(data)
155
126
  if error
156
- callback(error)
157
- else {
158
- if req.getResponseHeader("content-type").startsWith("application/json")
159
- try { // attempt to decode JSON response
160
- data = new(JSONparse(req.responseText))
161
- if array(data) {
162
- data = totuple(data)
163
- error = data.0
164
- data = data.1 }
165
- } catch {
166
- if true
167
- data = req.responseText // just return text on failure
168
- }
169
- else
170
- data = req.response // e.g. when reading binary content
171
- callback(error, data) }
172
- })
127
+ return (callback(Error("psmRemote encoding failed:", error)))
128
+ reqOptions.putdata = data
129
+ }
130
+ `(error, content, extra) = clientRequest(url.concat(query), reqOptions)
131
+ if !error && extra.status != 200
132
+ error = Error("NodeFS request failed: ".concat(extra.status), {
133
+ status: extra.status
134
+ })
135
+ if error
136
+ callback(error)
137
+ else
138
+ callback(error, content)
173
139
  }
174
140
 
175
141
  client
@@ -185,12 +151,12 @@ closure MakeNideAPIclient(url, guid, local client) {
185
151
 
186
152
  function apicInit(local manifest) {
187
153
 
188
- manifest = `(MakeNideAPIclient, apicInit)
154
+ manifest = `(NideAPIclient, apicInit)
189
155
 
190
156
  Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
191
157
  Naan.module.require("./client.nlg")
192
158
  compobj.manifest = manifest
193
- modobj.exports.MakeNideAPIclient = MakeNideAPIclient
159
+ modobj.exports.NideAPIclient = NideAPIclient
194
160
  })
195
161
 
196
162
  } ();
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2022 by Richard C. Zulch
9
+ * Copyright (c) 2019-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  closure psmcFsView(api, rootpath, local view, pathmod) {
22
+ global(JSpath)
22
23
  view = new(object, this)
23
24
  view.api = api
24
25
  view.rootpath = rootpath
@@ -114,7 +115,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
114
115
  // that a transient error will give a bad path, but that will be detected downstream.
115
116
 
116
117
  view.path = new(object, this)
117
- view.path[".undefined"] = function path args {
118
+ view.path@[".undefined"] = function path args {
118
119
  if !pathmod
119
120
  pathInit()
120
121
  xapply(pathmod, args.0, args.-1)
@@ -154,6 +155,8 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
154
155
  // Read a file at the specified path
155
156
 
156
157
  view.readFile = function readFile(path, options, callback) {
158
+ if !path
159
+ throw("psmcFsView.readFile: path is false")
157
160
  psmRemote(path, "readFile", options, false, callback)
158
161
  }
159
162
 
@@ -354,7 +357,9 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
354
357
  // Tell the shell to open the specified file.
355
358
 
356
359
  view.shellOpen = function shellOpen(path, args, callback) {
357
- psmRemote(path, "shellOpen", { args: args }, false, callback)
360
+ if args
361
+ args = { args: args }
362
+ psmRemote(path, "shellOpen", args, false, callback)
358
363
  }
359
364
 
360
365
  // grepLines
@@ -424,11 +429,14 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
424
429
  *
425
430
  */
426
431
 
427
- closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
432
+ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
433
+ global()
428
434
  connClassID = "NodeFS"
429
435
  connector = new(object, this)
430
436
  connector.psm = psm
431
- connector.api = nicc.api
437
+ connector.apis = {
438
+ HostFS: api
439
+ }
432
440
  connector.vault = psm.vault(connClassID)
433
441
  connector.label = "Naan-Server"
434
442
  watch = psm.watchable(connector)
@@ -563,7 +571,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
563
571
  // listResources
564
572
  //
565
573
  // List saved resources in our database
566
- connector.listResources = function listResources(local error, original) {
574
+ connector.listResources = function listResources(local error, output, original) {
567
575
  `(error, output) = connector.vault.listResources()
568
576
  if error
569
577
  return (list(error)) // can't access our saved resources
@@ -579,14 +587,14 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
579
587
  connector.info = function info(resID, local info, error, creds) {
580
588
  info = {
581
589
  classID: connClassID
582
- type: "remote filesystem"
583
- services: ["NideFS", "NideHostAPI"]
590
+ type: "remote server"
591
+ services: ["NideFS", "NideHostAPI", "NideTerminal"]
584
592
  }
585
593
  if resID == "Host" {
586
594
  info.name = "Host"
587
595
  info.type = "host filesystem"
588
596
  info.locked = true
589
- info.where = nicc.api.url
597
+ info.where = api.url
590
598
  info.access = "HostFS"
591
599
  } else {
592
600
  `(error, creds) = connector.vault.accessResource(resID)
@@ -620,14 +628,26 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
620
628
  `(error, creds) = connector.access(resID)
621
629
  if !error {
622
630
  if creds == "HostFS"
623
- api = connector.api
624
- else
625
- api = MakeNideAPIclient(creds.urlName, creds.authSecret) // API to remote NodeJS filesystem
631
+ api = connector.apis[creds]
632
+ else {
633
+ api = connector.apis[creds.urlName]
634
+ if !api // get API to remote server
635
+ connector.apis[creds.urlName] = api = NideAPIclient(creds.urlName, creds.authSecret)
636
+ }
626
637
  }
627
638
  if error
628
639
  list(error)
629
- else if service == "NideExecutor"
630
- require("../browser/terminals.nlg").TermHost(nicc.track, api, args.0, args.1) // host executor, args: (name, workerID)
640
+ else if service == "NideTerminal" {
641
+ if !App.nide.track
642
+ return (list(Error("NideTerminal not supported in this environment")))
643
+ list(false, App.nide.track.spawn(App.nide.registerRemote(api),
644
+ args.0, // terminal display name
645
+ args.1)) // options:
646
+ // {
647
+ // workerID: "NideServer" -- for main thread; other strings for worker threads
648
+ // type: "My Remotes" -- display name of terminal group; e.g. URL
649
+ // }
650
+ }
631
651
  else if service == "NideHostAPI"
632
652
  list(false, api) // connect to the Host API
633
653
  else if service == "NideFS" {
@@ -653,11 +673,12 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
653
673
  *
654
674
  */
655
675
 
656
- function psmcMakeClientPSM(nicc, dbname, local importPSM) {
676
+ function psmcMakeClientPSM(api, dbname, local importPSM) {
677
+ global(App)
657
678
  importPSM = require("../storage/psm.nlg")
658
679
  App.psm = importPSM.MakePSM(dbname).1
659
680
  if App.psm {
660
- psmcFsConnector(App.psm, nicc) // HostFS access
681
+ psmcFsConnector(App.psm, api) // HostFS access
661
682
  require("../storage/dbt_pouch.nlg").DbConnector(App.psm) // Pouch databases as filesystems
662
683
  }
663
684
  App.psm