@naanlang/naan 1.0.0 → 1.0.3

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 (37) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +37 -8
  3. package/bin/index.js +148 -0
  4. package/dist/env_web.js +145 -16
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/sworker.js +309 -91
  7. package/frameworks/browser/terminals.nlg +22 -9
  8. package/frameworks/browser/workers.nlg +19 -11
  9. package/frameworks/client/apiclient.nlg +17 -4
  10. package/frameworks/client/psm_client.nlg +116 -53
  11. package/frameworks/common/common.nlg +12 -2
  12. package/frameworks/node/apiserver.nlg +25 -13
  13. package/frameworks/node/filesystem.nlg +173 -30
  14. package/frameworks/node/psm_server.nlg +95 -32
  15. package/frameworks/project/build.nlg +40 -23
  16. package/frameworks/project/projects.nlg +47 -26
  17. package/frameworks/running/debugnub.nlg +2 -5
  18. package/frameworks/storage/csv.nlg +120 -0
  19. package/frameworks/storage/dbt_pouch.nlg +41 -25
  20. package/frameworks/storage/psm.nlg +108 -28
  21. package/frameworks/storage/psm_dbtables.nlg +7 -3
  22. package/frameworks/storage/resources.nlg +30 -2
  23. package/lib/browser/env_web.js +147 -18
  24. package/lib/browser/env_webworker.js +1 -1
  25. package/lib/browser/require.js +1 -1
  26. package/lib/core/naanlib.js +5 -5
  27. package/lib/env_node.js +14 -2
  28. package/lib/node_repl.js +2 -2
  29. package/package.json +4 -1
  30. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +1 -1
  31. package/plugins/serviceAws/aws_dynamo.nlg +625 -141
  32. package/plugins/serviceAws/aws_dynextra.nlg +294 -0
  33. package/plugins/serviceAws/dbt_aws.nlg +31 -11
  34. package/plugins/serviceAws/psm_aws.nlg +42 -26
  35. package/plugins/serviceAws/serviceAws.nlg +1 -0
  36. package/plugins/serviceGitHub/psm_github.nlg +41 -25
  37. package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -20,9 +20,10 @@
20
20
  *
21
21
  */
22
22
 
23
- closure MakeNideAPIclient(url, local client) {
23
+ closure MakeNideAPIclient(url, guid, local client) {
24
24
  client = new(object, this)
25
25
  client.url = url
26
+ client.guid = guid
26
27
  client.notifyAfter = 0
27
28
 
28
29
  // clientRequest() - general request routine
@@ -51,6 +52,8 @@ closure MakeNideAPIclient(url, local client) {
51
52
  request.responseType = options.responseType
52
53
  if options.contentType
53
54
  request.setRequestHeader("Content-Type", options.contentType)
55
+ if guid
56
+ request.setRequestHeader("x-naanlang-api-guid", guid)
54
57
  request.send(options.putdata)
55
58
  }
56
59
 
@@ -117,7 +120,7 @@ closure MakeNideAPIclient(url, local client) {
117
120
  // Otherwise if communication failed then the result is:
118
121
  // callback(<error>).
119
122
 
120
- client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions) {
123
+ client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions, error) {
121
124
  query = EncodeQuery("/psm/?", options)
122
125
  if data
123
126
  method = "PUT"
@@ -131,6 +134,16 @@ closure MakeNideAPIclient(url, local client) {
131
134
  reqOptions.contentType = "application/octet-stream" // we are sending binary data
132
135
  else
133
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
134
147
  }
135
148
  clientRequest(method, url.concat(query), reqOptions, function (error, req, event, local data) {
136
149
  if !error && req.status != 200
@@ -172,7 +185,7 @@ closure MakeNideAPIclient(url, local client) {
172
185
 
173
186
  function apicInit(local manifest) {
174
187
 
175
- manifest = `(apic, apicInit)
188
+ manifest = `(MakeNideAPIclient, apicInit)
176
189
 
177
190
  Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
178
191
  Naan.module.require("./client.nlg")
@@ -123,7 +123,28 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
123
123
  view.writeFile = function writeFile(path, data, options, callback) {
124
124
  psmRemote(path, "writeFile", options, data, callback)
125
125
  }
126
-
126
+
127
+ // readlink
128
+ //
129
+ // Read the contents of a symbolic link at the specified path
130
+
131
+ view.readlink = function readlink(path, callback) {
132
+ psmRemote(path, "readlink", { }, false, callback)
133
+ }
134
+
135
+ // symlink
136
+ //
137
+ // Create a symbolic link path pointing to (containing the string) target
138
+
139
+ view.symlink = function symlink(target, path, options, callback) {
140
+ if options
141
+ options = new(options)
142
+ else
143
+ options = { }
144
+ options.target = target
145
+ psmRemote(path, "symlink", options, false, callback)
146
+ }
147
+
127
148
  // delete
128
149
  //
129
150
  // Delete a file or directory at the specified path
@@ -134,12 +155,45 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
134
155
 
135
156
  // info
136
157
  //
137
- // Get file info at the specified path
158
+ // Get file or directory info at the specified path
159
+
160
+ view.info = function info(path, options, callback) {
161
+ if !options
162
+ options = { }
163
+ psmRemote(path, "info", options, false, callback)
164
+ }
165
+
166
+ // setInfo
167
+ //
168
+ // Set file or directory info obtained from info() call.
169
+
170
+ view.setInfo = function setInfo(path, info, callback) {
171
+ psmRemote(path, "setInfo", {
172
+ encoding: "json"
173
+ }, info, callback)
174
+ }
175
+
176
+ // setOwnerGroup
177
+ //
178
+ // Set file or directory owner or group at the specified path
138
179
 
139
- view.info = function info(path, callback) {
140
- psmRemote(path, "info", { }, false, callback)
180
+ view.setOwnerGroup = function setOwnerGroup(path, uid, gid, callback) {
181
+ psmRemote(path, "setOwnerGroup", {
182
+ uid: uid
183
+ gid: gid
184
+ }, false, callback)
141
185
  }
142
186
 
187
+ // setMode
188
+ //
189
+ // Set file or directory mode at the specified path
190
+
191
+ view.setMode = function setMode(path, mode, callback) {
192
+ psmRemote(path, "setMode", {
193
+ mode: mode
194
+ }, false, callback)
195
+ }
196
+
143
197
  // setTimes
144
198
  //
145
199
  // Set file modify and access times at the specified path
@@ -157,7 +211,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
157
211
 
158
212
  view.dirList = function dirList(path, options, callback) {
159
213
  psmRemote(path, "dirList", {
160
- head: options.head
214
+ stat: options.stat
161
215
  }, false, callback)
162
216
  }
163
217
 
@@ -167,7 +221,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
167
221
 
168
222
  view.readir = function readir(path, callback) {
169
223
  psmRemote(path, "dirList", {
170
- head: true
224
+ stat: true
171
225
  }, false, callback)
172
226
  }
173
227
 
@@ -209,7 +263,9 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
209
263
  // delete the source after the copy.
210
264
 
211
265
  view.copy = closure copy(srcpath, dest, options, callback, local parameters) {
212
- if !options
266
+ if options
267
+ options = new(options)
268
+ else
213
269
  options = { }
214
270
  options.destination = JSpath.resolve(rootpath, dest)
215
271
  psmRemote(srcpath, "copy", options, false, callback)
@@ -325,7 +381,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
325
381
  connector.psm = psm
326
382
  connector.api = nicc.api
327
383
  connector.vault = psm.vault(connClassID)
328
- connector.label = "Naan IDE Server"
384
+ connector.label = "Naan-Server"
329
385
  watch = psm.watchable(connector)
330
386
 
331
387
  // uifields
@@ -337,57 +393,44 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
337
393
  placeholder: "http://host.com/endpoint"
338
394
  key: "urlName" },
339
395
  {
340
- label: "Auth Name"
341
- placeholder: "account name"
342
- key: "authName" },
343
- {
344
- label: "Auth Password"
345
- placeholder: "account password"
346
- key: "authPW"
347
- type: "password" },
396
+ label: "Auth Secret"
397
+ placeholder: "guid"
398
+ key: "authSecret" }
348
399
  ]
349
400
  }
350
-
351
- // hashResourceID
352
- //
353
- // Make a stable hash based on the resource credentials that we can persist or share between
354
- // different domains accessing the same resource.
355
- connector.hashResourceID = function hashResourceID(resource, local ident) {
356
- ident = resource.urlName
357
- if resource.label
358
- ident = ident.concat(resource.label)
359
- HashSHA256(ident)
360
- }
361
401
 
362
402
  // findResource
363
403
  //
364
- // Find a resource with the specified contents, returning the resID or false. The loose criteria
365
- // is: if added, would the specified contents be redundant to an existing resource?
404
+ // Find a resource with the specified contents, returning the resID or false.
366
405
  connector.findResource = function findResource(resource, local resID, creds) {
367
406
  for resID in listResources().1 {
368
407
  creds = access(resID).1
369
- if resource.urlName == creds.urlName
370
- && (!resource.label || resource.label == creds.label)
371
- && resource.authName == creds.authName
408
+ if ((!resource.urlName || resource.urlName == creds.urlName)
409
+ && (!resource.label || resource.label == creds.label))
372
410
  return (resID)
373
411
  }
374
412
  false
375
413
  }
376
414
 
377
- // addResource
415
+
416
+ // writeResource
378
417
  //
379
- // Add credentials for a named resource. The resource is a dictionary comprising these keys:
418
+ // Add or update credentials for a named resource. If no resID is specified this adds a new
419
+ // resource; otherwise it updates an existing one. The resource is a dictionary comprising these
420
+ // keys:
380
421
  // label - [optional] label we use for this resource
381
422
  // urlName - URL (e.g. "http://host.com/node/fsapi")
382
- // authName - [optional] the authentication name for a URL
383
- // authPW - [optional] the autentication password, required if authname is provided
423
+ // authSecret - access-control token for this URL
384
424
  // hidden - [optional] don't enumerate to user
385
425
  // locked - [optional] don't allow user delete
386
426
  //
387
- // The return value is (error, resID) tuple. Error, if non-false, is a dictionary of field
427
+ // The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
388
428
  // keys and error diagnostic strings.
389
- connector.addResource = function addResource(resource, local creds, errors, error, resID, data) {
429
+ connector.writeResource = function writeResource(resource, resID,
430
+ local creds, errors, error, data, change) {
431
+ // badField - test for a bad string
390
432
  function badField(str) { !string(str) || str.trim().length == 0 }
433
+
391
434
  creds = { urlName: resource.urlName }
392
435
  errors = { }
393
436
  if resource.label
@@ -396,13 +439,13 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
396
439
  creds.label = resource.urlName
397
440
  if badField(creds.label)
398
441
  errors.label = "invalid label"
442
+ else if !resID && findResource({ label: creds.label })
443
+ errors.label = "duplicate name"
399
444
  if badField(resource.urlName)
400
445
  errors.urlName = "required field"
401
- if !badField(resource.authName) {
402
- creds.authName = resource.authName
403
- creds.authPW = resource.authPW
404
- if badField(resource.authPW)
405
- errors.authPW = "required if Auth Name specified" }
446
+ if badField(resource.authSecret)
447
+ errors.authSecret = "required field"
448
+ creds.authSecret = resource.authSecret
406
449
  if length(errors) > 0
407
450
  error = errors
408
451
  else {
@@ -410,14 +453,36 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
410
453
  creds.hidden = resource.hidden
411
454
  if resource.locked
412
455
  creds.locked = resource.locked
413
- resID = hashResourceID(resource)
414
- `(error, data) = connector.vault.addResource(resID, creds)
456
+ if resID {
457
+ `(error, data) = connector.vault.updateResource(resID, creds)
458
+ change = { changed: [resID] }
459
+ } else {
460
+ resID = UUID()
461
+ `(error, data) = connector.vault.addResource(resID, creds)
462
+ change = { added: [resID] }
463
+ }
415
464
  if data {
416
- watch.notify(connClassID, { added: [resID] })
417
- data = resID } }
465
+ watch.notify(connClassID, change)
466
+ data = resID
467
+ }
468
+ }
418
469
  list(error, data)
419
470
  }
420
-
471
+
472
+ // addResource
473
+ //
474
+ // Add a new resource, returning a standard error tuple per writeResource.
475
+ connector.addResource = function addResource(resource) {
476
+ writeResource(resource)
477
+ }
478
+
479
+ // updateResource
480
+ //
481
+ // Update an existing resource, returning a standard error tuple per writeResource.
482
+ connector.updateResource = function updateResource(resID, resource) {
483
+ writeResource(resource, resID)
484
+ }
485
+
421
486
  // getResource
422
487
  //
423
488
  // Get credentials for a named resource, matching the dictionary semantics of addResource. The
@@ -432,10 +497,8 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
432
497
  }
433
498
  if string(creds.urlName)
434
499
  resource.urlName = creds.urlName
435
- if string(creds.authName)
436
- resource.authName = creds.authName
437
- if string(creds.authPW)
438
- resource.authPW = creds.authPW
500
+ if string(creds.authSecret)
501
+ resource.authSecret = creds.authSecret
439
502
  list(false, resource)
440
503
  }
441
504
 
@@ -510,7 +573,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
510
573
  if creds == "HostFS"
511
574
  api = connector.api
512
575
  else
513
- `(error, api) = MakeNideAPIclient(creds.urlName) // API to remote NodeJS filesystem
576
+ api = MakeNideAPIclient(creds.urlName, creds.authSecret) // API to remote NodeJS filesystem
514
577
  }
515
578
  if error
516
579
  list(error)
@@ -256,6 +256,8 @@ TextEncoder = false;
256
256
  TextDecoder = false;
257
257
  EncodeURIComponent = false;
258
258
  DecodeURIComponent = false;
259
+ EncodeBase64 = false;
260
+ DecodeBase64 = false;
259
261
  JSONstringify = false;
260
262
  JSONparse = false;
261
263
 
@@ -289,6 +291,10 @@ function comrInit(local manifest) {
289
291
  EncodeURIComponent = wsg.encodeURIComponent
290
292
  DecodeURIComponent = wsg.decodeURIComponent
291
293
  //
294
+ // everywhere: Base65
295
+ EncodeBase64 = wsg.btoa
296
+ DecodeBase64 = wsg.atob
297
+ //
292
298
  // everywhere: JSON
293
299
  JSONstringify = wsg.JSON.stringify
294
300
  JSONparse = wsg.JSON.parse
@@ -338,7 +344,7 @@ function comrInit(local manifest) {
338
344
  //
339
345
  DecodeBase64url = function decodeBase64url(base64url, local padding) {
340
346
  base64url = base64url.replace(RegExp("-", "g"), '+').replace(RegExp("_", "g"), '/')
341
- wsg.atob(base64url)
347
+ DecodeBase64(base64url)
342
348
  }
343
349
  //
344
350
  // For browser windows and web workers
@@ -358,7 +364,7 @@ function comrInit(local manifest) {
358
364
  //
359
365
  // HashMD5 in base64
360
366
  HashMD5_base64 = function browserMD5_base64(data) {
361
- wsg.btoa(browserMD5(data, true)) }
367
+ EncodeBase64(browserMD5(data, true)) }
362
368
  //
363
369
  // browserDigest - only in secure contexts (localhost and HTTPS)
364
370
  //
@@ -431,6 +437,8 @@ function comrInit(local manifest) {
431
437
  HashMD5 =
432
438
  HashMD5_base64 =
433
439
  HashSHA1 =
440
+ HashSHA256 =
441
+ HashSHA256_base64 =
434
442
  RandomBytes = function() { throw(Error("crypto functions unavailable")) }
435
443
  }
436
444
  }
@@ -442,6 +450,8 @@ function comrInit(local manifest) {
442
450
  modobj.exports.TextDecoder = TextDecoder
443
451
  modobj.exports.EncodeURIComponent = EncodeURIComponent
444
452
  modobj.exports.DecodeURIComponent = DecodeURIComponent
453
+ modobj.exports.EncodeBase64 = EncodeBase64
454
+ modobj.exports.DecodeBase64 = DecodeBase64
445
455
  modobj.exports.JSONstringify = JSONstringify
446
456
  modobj.exports.JSONparse = JSONparse
447
457
  modobj.exports.JsonStringify = JsonStringify
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -36,6 +36,7 @@ closure MakeAPI(port, options, callback, local chroot, server) {
36
36
  server.psm = require("./psm_server.nlg").MakeServerPSM(server)
37
37
  server.root = chroot
38
38
  server.app = express()
39
+ server.guid = options.guid
39
40
  server.notifyq = []
40
41
  server.responseq = []
41
42
  server.projects = { }
@@ -99,8 +100,9 @@ closure MakeAPI(port, options, callback, local chroot, server) {
99
100
  // body parsing
100
101
  //
101
102
 
102
- server.app.use(bodyParser.text({limit: "20mb"}))
103
- server.app.use(bodyParser.raw({limit: "20mb"}))
103
+ server.app.use(bodyParser.json({limit: "5mb"}))
104
+ server.app.use(bodyParser.text({limit: "25mb"}))
105
+ server.app.use(bodyParser.raw({limit: "25mb"}))
104
106
 
105
107
  //
106
108
  // Express.js endpoints
@@ -121,15 +123,18 @@ closure MakeAPI(port, options, callback, local chroot, server) {
121
123
  // "/"
122
124
 
123
125
  server.app.get("/", function(req, res) {
124
- res.send("<u>NaanIDE API</u><br>\r\n"
126
+ res.send("<u>naan-server API</u><br>\r\n"
125
127
  " /readfile?path=<i>filepath</i><br>\r\n"
126
128
  " /status?timeout=<i>msec</i>&after=<i>stamp</i><br>\r\n")
127
129
  })
128
130
 
129
131
  // "/readfile"
130
132
 
131
- server.app.get("/readfile", closure(req, res) {
132
- if not string(req.query.path)
133
+ server.app.get("/readfile", closure(req, res, local guid) {
134
+ guid = req.get("x-naanlang-api-guid")
135
+ if server.guid && guid != server.guid
136
+ res.status(403).send("unauthorized")
137
+ else if not string(req.query.path)
133
138
  res.status(400).send("Invalid filename")
134
139
  else if RegExp("^\\.\\.[/\\\\]|[/\\\\]\\.\\.$|[/\\\\]\\.\\.[/\\\\]").test(req.query.path)
135
140
  res.status(403).send("\"Parent path references forbidden\"")
@@ -156,7 +161,10 @@ closure MakeAPI(port, options, callback, local chroot, server) {
156
161
 
157
162
  // "/status"
158
163
 
159
- server.app.get("/status", closure(req,res, local timeoutms, after, responder) {
164
+ server.app.get("/status", closure(req, res, local guid, timeoutms, after, responder) {
165
+ guid = req.get("x-naanlang-api-guid")
166
+ if server.guid && guid != server.guid
167
+ res.status(403).send("unauthorized")
160
168
  timeoutms = toint(req.query.timeout)
161
169
  after = toint(req.query.after)
162
170
  if not integer(timeoutms) or timeoutms > 10000
@@ -187,8 +195,11 @@ closure MakeAPI(port, options, callback, local chroot, server) {
187
195
 
188
196
  // "/psm"
189
197
 
190
- server.app.all("/psm", closure(req, res) {
191
- if !server.hostfs
198
+ server.app.all("/psm", closure(req, res, local guid) {
199
+ guid = req.get("x-naanlang-api-guid")
200
+ if server.guid && guid != server.guid
201
+ res.status(403).send("unauthorized")
202
+ else if !server.hostfs
192
203
  res.status(503).send("host filesystem not available")
193
204
  else
194
205
  server.hostfs.api(req, res)
@@ -227,7 +238,6 @@ closure MakeAPI(port, options, callback, local chroot, server) {
227
238
  server.ws = WebSocketServer(server)
228
239
  server.port = port
229
240
  callback(false, port, server) // notify of success
230
- debuglog("server open on port", port)
231
241
  result = server.psm.conns.NodeFS.connect("Host", "NideFS", list("")) // ### this should go through resource tracker
232
242
  if result.0
233
243
  ErrorDebuglog(Error("can't access HostFS", result.0))
@@ -264,7 +274,7 @@ closure WebSocketServer(server, local websocks) {
264
274
  // Event handlers
265
275
 
266
276
  websocks.wss.on("connection", function(socket, request) {
267
- websocks.conns.push(WesCon(websocks, socket, request))
277
+ websocks.conns.push(WesCon(server, websocks, socket, request))
268
278
  })
269
279
 
270
280
  server.wss.on("error", function(error) {
@@ -303,7 +313,7 @@ closure WebSocketServer(server, local websocks) {
303
313
  *
304
314
  */
305
315
 
306
- closure WesCon(websocks, socket, request, local wescon) {
316
+ closure WesCon(server, websocks, socket, request, local wescon) {
307
317
  wescon = new(object, this)
308
318
  wescon.socket = socket
309
319
  wescon.serverIDs = []
@@ -315,7 +325,9 @@ closure WesCon(websocks, socket, request, local wescon) {
315
325
  socket.on("message", function onmessage(text, local message, error) {
316
326
  try {
317
327
  message = new(js.g.JSON.parse(text))
318
- if message.clientID && websocks.subsystems[message.serverID] {
328
+ if server.guid && message.guid != server.guid
329
+ error = Error("unauthorized")
330
+ else if message.clientID && websocks.subsystems[message.serverID] {
319
331
  wescon.serverIDs.push(message.serverID) // ### track subsystems for closing
320
332
  websocks.subsystems[message.serverID].wsReceive(message, wescon)
321
333
  }