@naanlang/naan 1.0.16 → 1.1.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 (41) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/index.js +4 -4
  4. package/dist/naan.min.js +6 -6
  5. package/frameworks/browser/https_request.nlg +23 -9
  6. package/frameworks/browser/sworker.js +26 -25
  7. package/frameworks/browser/terminals.nlg +56 -15
  8. package/frameworks/browser/ws_client.nlg +124 -0
  9. package/frameworks/client/apiclient.nlg +3 -2
  10. package/frameworks/client/psm_client.nlg +1 -1
  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 +30 -5
  16. package/frameworks/node/node.nlg +60 -2
  17. package/frameworks/node/psm_server.nlg +9 -5
  18. package/frameworks/node/worker.nlg +22 -9
  19. package/frameworks/node/ws_client.nlg +127 -0
  20. package/frameworks/project/build.nlg +2 -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 +59 -12
  25. package/frameworks/running/sourcecode.nlg +2 -2
  26. package/frameworks/running/taskexec.nlg +23 -24
  27. package/frameworks/storage/dbt_pouch.nlg +8 -6
  28. package/frameworks/storage/file_manager.nlg +6 -3
  29. package/frameworks/storage/psm.nlg +3 -2
  30. package/frameworks/storage/psm_dbtables.nlg +53 -41
  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 +6 -6
  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/dbt_aws.nlg +2 -2
  40. package/plugins/serviceAws/psm_aws.nlg +2 -2
  41. package/test/test_01_core.nlg +30 -4
@@ -1,11 +1,11 @@
1
1
  /*
2
- * https_browser.nlg
2
+ * https_request.nlg
3
3
  *
4
4
  * Https operations for the browser.
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021-2023 by Richard C. Zulch
8
+ * Copyright (c) 2021-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -24,6 +24,7 @@
24
24
  * encoding: <string> // "binary" to receive binary data as arrayBuffer
25
25
  * range: <string> // sets range header
26
26
  * headers: [`(key, value)] // add header(s) to the request, overriding defaults
27
+ * debug: <boolean> // log errors and status results
27
28
  * }
28
29
  *
29
30
  * The return is a result tuple comprising:
@@ -82,14 +83,27 @@ closure HttpsApiRequest(url, options,
82
83
  //
83
84
  // perform the fetch and return when complete
84
85
  //
85
- `(error, response) = await(window.fetch(urlq, fetchOptions))
86
- if error
87
- return (list(Error("HttpsApiRequest fetch failed:", error, url)))
86
+ loop { // loop for retries
87
+ `(error, response) = await(window.fetch(urlq, fetchOptions))
88
+ if error {
89
+ if options.debug
90
+ debuglog("HttpsApiRequest error:", url, "error:", error)
91
+ return (list(Error("HttpsApiRequest fetch failed:", error, url)))
92
+ }
93
+ if response.status < 200 || response.status >= 300 {
94
+ if options.debug
95
+ debuglog("HttpsApiRequest status:", url, response.statusCode)
96
+ error = Error("HttpsApiRequest status:", url, response.status, {
97
+ status: response.status
98
+ })
99
+ if options.retrier(error)
100
+ continue // optional retry/delay logic
101
+ else
102
+ return (list(error, false, { status: response.status }))
103
+ } else
104
+ break
105
+ }
88
106
  contentType = response.headers.get("content-type")
89
- if response.status < 200 || response.status >= 300
90
- return (list(Error("HttpsApiRequest status:", url, response.status, {
91
- status: response.status
92
- }), false, { status: response.status }))
93
107
  if contentType.startsWith("application/json") {
94
108
  `(error, content) = await(response.text())
95
109
  if !error
@@ -32,11 +32,11 @@
32
32
  *
33
33
  * column positioning: // // !
34
34
  *
35
- * Copyright (c) 2020-2023 by Richard C. Zulch
35
+ * Copyright (c) 2020-2024 by Richard C. Zulch
36
36
  *
37
37
  */
38
38
 
39
- var CurrentCacheName = "Naanlang-1.0.16+1";
39
+ var CurrentCacheName = "Naanlang-1.1.0+1";
40
40
 
41
41
 
42
42
  //
@@ -92,18 +92,18 @@ var terminated; // flag that this service worker
92
92
  var pubVersion = event.data.hereIsMyVersion;
93
93
  var sourceId = event.source.id; // client id of sender of message
94
94
  msgPorts[sourceId] = msgport;
95
- console.log("[1.0.16+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (pubVersion != "1.0.16+1") {
95
+ console.log("[1.1.0+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (pubVersion != "1.1.0+1") {
97
97
  if (upgradeMsgPorts) { // if not activated
98
- console.log("[1.0.16+1] update pending for", sourceId);
98
+ console.log("[1.1.0+1] update pending for", sourceId);
99
99
  upgradeMsgPorts[sourceId] = msgport;
100
100
  }
101
101
  else {
102
102
  msgport.postMessage({ // notify new version available
103
103
  id: "upgrade",
104
- version: "1.0.16+1"
104
+ version: "1.1.0+1"
105
105
  });
106
- console.log("[1.0.16+1] update sent for:", sourceId);
106
+ console.log("[1.1.0+1] update sent for:", sourceId);
107
107
  }
108
108
  }
109
109
  Reaper(); // clean up obsolete info
@@ -134,10 +134,10 @@ var terminated; // flag that this service worker
134
134
  msgport.onmessage = function(msg) {
135
135
  msg = msg.data;
136
136
  if (msg.id == "skipWaiting") { // try to activate this SW now
137
- console.log("[1.0.16+1] attempting skipWaiting");
137
+ console.log("[1.1.0+1] attempting skipWaiting");
138
138
  self.skipWaiting();
139
139
  } else if (msg.id == "terminate") { // termiante this SW now
140
- console.log("[1.0.16+1] terminated");
140
+ console.log("[1.1.0+1] terminated");
141
141
  terminate();
142
142
  } else if (msg.id == "abortURL") { // abort an I/O transaction
143
143
  var href = new URL(msg.url).href;
@@ -150,7 +150,7 @@ var terminated; // flag that this service worker
150
150
  } else if (msg.id == "response") // response from fetch request
151
151
  processResponse(msg);
152
152
  else if (msg.id == "text") // just log some text
153
- console.log("[1.0.16+1] msg received:", msg.text);
153
+ console.log("[1.1.0+1] msg received:", msg.text);
154
154
  };
155
155
 
156
156
  // send text to IDE log
@@ -161,7 +161,7 @@ var terminated; // flag that this service worker
161
161
  // don't clutter the log
162
162
  msgport.postMessage({
163
163
  id: "text",
164
- text: "port received by 1.0.16+1",
164
+ text: "port received by 1.1.0+1",
165
165
  });
166
166
  */
167
167
  if (--pending === 0)
@@ -178,7 +178,7 @@ var terminated; // flag that this service worker
178
178
  includeUncontrolled: true
179
179
  }).then(function(clientList) {
180
180
  clientList.every(function(client) {
181
- console.log("[1.0.16+1] requesting new msgport for", client.id);
181
+ console.log("[1.1.0+1] requesting new msgport for", client.id);
182
182
  ++pending;
183
183
  client.postMessage({ // tell client(s) we need this fetch source
184
184
  msg: "Naan_need_fetch_port",
@@ -224,12 +224,12 @@ function Reaper() {
224
224
  clients[clientList[clidex].id] = clientList[clidex];
225
225
  for (var sourceId in msgPorts)
226
226
  if (!clients[sourceId]) {
227
- console.log("[1.0.16+1] source gone:", sourceId);
227
+ console.log("[1.1.0+1] source gone:", sourceId);
228
228
  delete msgPorts[sourceId]; // no longer a source
229
229
  }
230
230
  for (var clientId in fetchPorts)
231
231
  if (!clients[clientId]) {
232
- console.log("[1.0.16+1] client gone:", clientId);
232
+ console.log("[1.1.0+1] client gone:", clientId);
233
233
  delete fetchPorts[clientId]; // no longer a client
234
234
  }
235
235
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -263,16 +263,16 @@ function ClearCaches() {
263
263
  return (Promise.all(
264
264
  cacheNames.map(function(cacheName) {
265
265
  if (cacheName != CurrentCacheName) {
266
- console.log('[1.0.16+1] deleting old cache:', cacheName);
266
+ console.log('[1.1.0+1] deleting old cache:', cacheName);
267
267
  return (caches.delete(cacheName));
268
268
  }
269
269
  })
270
270
  ));
271
271
  }).then(function() { // claim all clients
272
- console.log('[1.0.16+1] claiming clients');
272
+ console.log('[1.1.0+1] claiming clients');
273
273
  return (self.clients.claim());
274
274
  }).then(function() {
275
- console.log('[1.0.16+1] clients claimed');
275
+ console.log('[1.1.0+1] clients claimed');
276
276
  return (Promise.resolve(true));
277
277
  });
278
278
  return (promise);
@@ -315,7 +315,7 @@ function GetClientResponse(event, urlpath) {
315
315
  msgport.postMessage({
316
316
  id: "fetch",
317
317
  seq: seqno,
318
- version: "1.0.16+1",
318
+ version: "1.1.0+1",
319
319
  request: {
320
320
  method: event.request.method,
321
321
  url: event.request.url
@@ -363,7 +363,7 @@ function GetClientResponse(event, urlpath) {
363
363
  */
364
364
 
365
365
  self.addEventListener('install', function(event) {
366
- console.log("[1.0.16+1] install");
366
+ console.log("[1.1.0+1] install");
367
367
  self.skipWaiting();
368
368
  });
369
369
 
@@ -376,20 +376,20 @@ self.addEventListener('install', function(event) {
376
376
  */
377
377
 
378
378
  self.addEventListener('activate', function(event) {
379
- console.log("[1.0.16+1] activate");
379
+ console.log("[1.1.0+1] activate");
380
380
  self.clients.matchAll({ // for debugging, list controlled clients
381
381
  includeUncontrolled: true
382
382
  }).then(function(clientList) {
383
383
  var urls = clientList.map(function(client) {
384
384
  return (client.url);
385
385
  });
386
- console.log('[1.0.16+1] matching clients:', urls.join(', '));
386
+ console.log('[1.1.0+1] matching clients:', urls.join(', '));
387
387
  });
388
388
  var promise = ClearCaches().then(function() {
389
389
  for (var sourceId in upgradeMsgPorts) {
390
390
  upgradeMsgPorts[sourceId].postMessage({ // notify new version available
391
391
  id: "upgrade",
392
- version: "1.0.16+1"
392
+ version: "1.1.0+1"
393
393
  });
394
394
  }
395
395
  upgradeMsgPorts = false;
@@ -410,7 +410,7 @@ self.addEventListener('fetch', function(event) {
410
410
  event.respondWith(
411
411
  caches.match(event.request).then(function(response)
412
412
  {
413
- if (response)
413
+ if (response && response.status != 299)
414
414
  return (response);
415
415
  if (terminated)
416
416
  return (new Response(undefined, {
@@ -421,7 +421,7 @@ self.addEventListener('fetch', function(event) {
421
421
  var promise;
422
422
  var url = new URL(request.url);
423
423
  var nocache = request.method != "GET"
424
- || url.search.length !== 0 && url.searchParams.get("naanver") !== "b2535090f4e111466794eea676c7d7aa"
424
+ || url.search.length !== 0 && url.searchParams.get("naanver") !== "0edbbbd718a57553ff0a5c178f4dd389"
425
425
  || request.headers.get('range');
426
426
  if (url.pathname.startsWith("/run/")) {
427
427
  nocache = true;
@@ -449,12 +449,13 @@ self.addEventListener('fetch', function(event) {
449
449
  return (item !== abortItem);
450
450
  });
451
451
  if (abortItem.aborted) {
452
+ nocache = true; // don't cache the 299!
452
453
  return (new Response(undefined, {
453
454
  status: 299,
454
455
  statusText: "Request Cancelled"
455
456
  }));
456
457
  }
457
- console.log("[1.0.16+1] fetch failed", request.url, e);
458
+ console.log("[1.1.0+1] fetch failed", request.url, e);
458
459
  return (new Response(undefined, {
459
460
  status: 404,
460
461
  statusText: "Fetch Failed"
@@ -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
 
@@ -21,6 +21,7 @@
21
21
  closure TermHost(track, api, name, workerID, options,
22
22
  local target, nlregex, clientID, serverID)
23
23
  {
24
+ global(window)
24
25
  target = require("../running/executors.nlg").ExecutorBase(track, "Host", name)
25
26
  clientID = "DebugWorkers"
26
27
  serverID = "Workers"
@@ -29,22 +30,33 @@ closure TermHost(track, api, name, workerID, options,
29
30
  workerID = "myDebugWorker"
30
31
  target.workerID = workerID
31
32
  target.msgQueue = []
33
+ target.dispatcher = call(options.dispatcher, function(reply) { reply })
32
34
 
33
35
  // connect
34
36
  // Connect with the host if not already connected.
35
37
  closure connect(local pending) {
36
38
  if target.ws
37
39
  return
38
- target.ws = xnew(window.WebSocket,"ws://".concat(window.location.host)) // ### use api parameter
40
+ target.ws = xnew(window.WebSocket,"ws://${window.location.host}")
39
41
  pending = new(nonce)
40
-
42
+
41
43
  // onopen
42
44
  // The WebSocket connection is now open.
43
- target.ws.onopen = function onopen(e, local spawner) {
44
- pending.signal(list(false, { ok: true }))
45
- spawner = new(options.startup)
45
+ target.ws.onopen = function onopen(e, local info, spawner) {
46
+ info = {
47
+ name: name
48
+ workerID: workerID
49
+ }
50
+ if options.dispatcher
51
+ info.destID = api.instanceID // valid destination for relay
52
+ target.ws.send(window.JSON.stringify({
53
+ guid: api.guid
54
+ init: info
55
+ }))
56
+ spawner = new(options.startup) || { }
46
57
  spawner.name = name
47
58
  sendControl("spawn", spawner)
59
+ pending.signal(list(false, { ok: true }))
48
60
  }
49
61
 
50
62
  // onmessage
@@ -77,10 +89,29 @@ closure TermHost(track, api, name, workerID, options,
77
89
  if !target.written
78
90
  target.term.WriteLn("(reconnected)")
79
91
  }, 1000).run()
80
- else if message.respOp
81
- debuglog("unknown host response message:", message.respOp)
92
+ else if message.workerOp == "msgin" && message.payload.id == "targetin"
93
+ dispatcher(message) // request from Host
94
+ else if message.respOp || message.id
95
+ debuglog("unknown host response message:", message.respOp || message.id)
82
96
  }
83
-
97
+
98
+ // dispatcher
99
+ // Respond to a host request while preserving context to permit reply routing
100
+ closure dispatcher(message, local reply) {
101
+ reply = target.dispatcher(message.payload.data)
102
+ target.ws.send(window.JSON.stringify({
103
+ guid: api.guid
104
+ clientID: message.clientID
105
+ serverID: message.serverID
106
+ workerID: message.workerID
107
+ respOp: "msgout"
108
+ payload: {
109
+ id: "targetout",
110
+ data: reply
111
+ }
112
+ }))
113
+ }
114
+
84
115
  // onerror
85
116
  // An error occurred on the WebSocket connection.
86
117
  target.ws.onerror = function onerror(e) {
@@ -91,7 +122,8 @@ closure TermHost(track, api, name, workerID, options,
91
122
  // onclose
92
123
  // The WebSocket connection was closed.
93
124
  target.ws.onclose = function onclose(e) {
94
- debuglog("ws close:", e.code, e.reason)
125
+ if e.code != 1000 && e.code != 1001 && e.code != 1005 && e.code != 1006
126
+ debuglog("ws close:", e.code, e.reason)
95
127
  target.execClosed(Error("socket closed", e.code, e.reason))
96
128
  target.ws = false
97
129
  }
@@ -104,7 +136,7 @@ closure TermHost(track, api, name, workerID, options,
104
136
  //
105
137
  // New workers created/discovered on this host.
106
138
  //
107
- function hostWorkerUpdate(msg, local localID) {
139
+ function hostWorkerUpdate(msg, local localID, worker) {
108
140
 
109
141
  // findWorker
110
142
  //
@@ -123,9 +155,9 @@ closure TermHost(track, api, name, workerID, options,
123
155
  else if msg.id == "termination"
124
156
  track.delete(findWorker(msg.workerID))
125
157
  else {
126
- for localID in msg.workerIDs
158
+ for `(localID, worker) in msg.workers
127
159
  if !findWorker(localID)
128
- TermHost(track, api, localID, localID)
160
+ TermHost(track, api, worker.name, localID)
129
161
  }
130
162
  }
131
163
 
@@ -243,12 +275,15 @@ closure TermHost(track, api, name, workerID, options,
243
275
  // Post a message to the target.
244
276
  //
245
277
 
246
- target.PostMessage = function PostMessage(data) {
247
- connect()
278
+ target.PostMessage = function PostMessage(data, local result) {
279
+ result = connect()
280
+ if result.0
281
+ return (result)
248
282
  send({ // send data to worker
249
283
  id: "targetin",
250
284
  data: data
251
285
  })
286
+ list(false, { posted: true })
252
287
  }
253
288
 
254
289
  //
@@ -358,6 +393,7 @@ closure TermHost(track, api, name, workerID, options,
358
393
  closure TermLocal(track, name, workerID, options,
359
394
  local target, nlregex, listener)
360
395
  {
396
+ global(js, window)
361
397
  if workerID == "NaanIDE" {
362
398
  if !js.t
363
399
  return (list(Error("Cannot access Local IDE executor")))
@@ -500,6 +536,7 @@ closure TermLocal(track, name, workerID, options,
500
536
  id: "targetin",
501
537
  data: data
502
538
  })
539
+ list(false, { posted: true })
503
540
  }
504
541
 
505
542
  //
@@ -591,6 +628,7 @@ closure TermLocal(track, name, workerID, options,
591
628
  */
592
629
 
593
630
  closure findIDEtarget(track, name, workerID, naancont, local target) {
631
+ global()
594
632
  for target in track.enumlist()
595
633
  if target.name == name && target.workerID == workerID && (!target.naancont || target.naancont === naancont)
596
634
  return (target)
@@ -608,6 +646,7 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
608
646
  */
609
647
 
610
648
  closure termIDE(track, name, workerID, naancont, title, local target, connected) {
649
+ global()
611
650
  target = findIDEtarget(track, name, workerID, naancont)
612
651
  if target { // new naancont on existing target
613
652
  target.updateController(naancont)
@@ -640,6 +679,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
640
679
 
641
680
  target.PostMessage = function PostMessage(data) {
642
681
  naancont.DispatchMessage(data)
682
+ list(false, { posted: true })
643
683
  }
644
684
 
645
685
  // termAttach
@@ -770,6 +810,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
770
810
  */
771
811
 
772
812
  closure termInstallVirtualWatcher(track, naancont) {
813
+ global()
773
814
 
774
815
  function watchVsites(msg, local error, target) {
775
816
  if msg.op == "VsiteOpen" {
@@ -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,11 +2,11 @@
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-2023 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -25,6 +25,7 @@ closure MakeNideAPIclient(url, guid, local client) {
25
25
  client.url = url
26
26
  client.guid = guid
27
27
  client.notifyAfter = 0
28
+ client.instanceID = UUID() // our client's unique ID
28
29
 
29
30
  // clientRequest() - general request routine
30
31
 
@@ -114,7 +114,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
114
114
  // that a transient error will give a bad path, but that will be detected downstream.
115
115
 
116
116
  view.path = new(object, this)
117
- view.path[".undefined"] = function path args {
117
+ view.path@[".undefined"] = function path args {
118
118
  if !pathmod
119
119
  pathInit()
120
120
  xapply(pathmod, args.0, args.-1)