@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
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021 by Richard C. Zulch
8
+ * Copyright (c) 2021-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -59,7 +59,7 @@ closure CodeManager(options, local coman, cache)
59
59
  coman.procComponent = function procComponent(procdef, local sym, mod, modname, compname, component) {
60
60
  procdef = baseproc(rootproc(procdef)) // only work with the bases, not instances
61
61
  sym = procdef.1
62
- mod = module.owner.findModule(sym.namespace)
62
+ mod = module.owner.find(function(mod){ mod.namespace === sym.namespace })
63
63
  for `(compname, component) in mod.components
64
64
  if member(sym, component.manifest)
65
65
  return (CodeComposite(coman, component.manifest, mod.id, compname))
@@ -5,13 +5,13 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021 by Richard C. Zulch
8
+ * Copyright (c) 2021-2024 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
12
12
 
13
13
  /*
14
- * taskDispatcher
14
+ * TaskDispatcher
15
15
  *
16
16
  * Create a dispatcher for remote evaluation. The replier argument is a function that accepts
17
17
  * outgoing messages. The return value is a closure that processes incomming messages. This code is
@@ -19,11 +19,12 @@
19
19
  *
20
20
  */
21
21
 
22
- closure taskDispatcher(replier, local contexts, gohome, util) {
22
+ closure TaskDispatcher(replier, local contexts, gohome, util) {
23
+ global()
23
24
  contexts = { }
24
25
  gohome = makeActivator()
25
26
  util = modlist().Util.exports
26
- closure(data, local reply, restorens, expr, roots, proc, args, result) {
27
+ closure(data, local reply, restorens, context, expr, roots, proc, args, result) {
27
28
  reply = {
28
29
  xmsg: "error"
29
30
  xid: data.xid
@@ -68,24 +69,22 @@ closure taskDispatcher(replier, local contexts, gohome, util) {
68
69
  })
69
70
  expr = caar(expr)
70
71
  reply.error = undefined
71
- if expr {
72
- expr = evalactive(expr)
73
- if data.get
74
- roots = new(data.get).map(function(sym){ // new() to get symbol from compress
75
- compress(sym)
76
- })
77
- if roots
78
- roots = cons(`expr, totuple(roots))
79
- else
80
- roots = `(expr)
81
- reply.result = util.pkgSave(`expr, roots, {
82
- nsignore: true // don't report namespace errors
83
- comments: true }) // include comments
84
- if context.namespace !== nsactive().0
85
- { // evaluation changed namespace
86
- context.namespace = nsactive().0
87
- context.activator = makeActivator()
88
- }
72
+ expr = evalactive(expr)
73
+ if data.get
74
+ roots = new(data.get).map(function(sym){ // new() to get symbol from compress
75
+ compress(sym)
76
+ })
77
+ if roots
78
+ roots = cons(`expr, totuple(roots))
79
+ else
80
+ roots = `(expr)
81
+ reply.result = util.pkgSave(`expr, roots, {
82
+ nsignore: true // don't report namespace errors
83
+ comments: true }) // include comments
84
+ if context.namespace !== nsactive().0
85
+ { // evaluation changed namespace
86
+ context.namespace = nsactive().0
87
+ context.activator = makeActivator()
89
88
  }
90
89
  }
91
90
  }
@@ -148,12 +147,12 @@ closure taskDispatcher(replier, local contexts, gohome, util) {
148
147
  closure TaskExecutor() {
149
148
 
150
149
  // taskInstall
151
- // Install the taskDispatcher, and reload on state load.
150
+ // Install the TaskDispatcher, and reload on state load.
152
151
  //
153
152
  function taskInstall() {
154
153
  if !js.t.OnMessage
155
154
  return (false) // false if not a worker
156
- js.t.OnMessage(taskDispatcher(js.t.ReplyMessage))
155
+ js.t.OnMessage(TaskDispatcher(js.t.ReplyMessage))
157
156
  }()
158
157
  LoadNotify(taskInstall)
159
158
 
@@ -185,11 +184,12 @@ function TaskOnMessageHook(hook) {
185
184
 
186
185
  function taxInit(local manifest) {
187
186
 
188
- manifest = `(taskDispatcher, TaskExecutor, TaskOnMessageHook, taxInit)
187
+ manifest = `(TaskDispatcher, TaskExecutor, TaskOnMessageHook, taxInit)
189
188
 
190
189
  Naan.module.build(module.id, "taskexec", function(modobj, compobj) {
191
190
  require("./running.nlg").LiveImport()
192
191
  compobj.manifest = manifest
192
+ modobj.exports.TaskDispatcher = TaskDispatcher
193
193
  modobj.exports.TaskExecutor = TaskExecutor
194
194
  modobj.exports.TaskOnMessageHook = TaskOnMessageHook
195
195
  })
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * column positioning: // // !
9
9
  *
10
- * Copyright (c) 2017-2023 by Richard C. Zulch
10
+ * Copyright (c) 2017-2024 by Richard C. Zulch
11
11
  *
12
12
  */
13
13
 
@@ -35,6 +35,7 @@
35
35
  */
36
36
 
37
37
  closure dapoTable(database, path, local table, prefix) {
38
+ global()
38
39
  table = new(object, this)
39
40
  table.type = database.type
40
41
  table.db = database
@@ -277,13 +278,11 @@ closure dapoTable(database, path, local table, prefix) {
277
278
  // rows : [
278
279
  // {
279
280
  // key : "john",
280
- // _modified : [Date Sat Apr 04 2020 17:05:38 GMT-0700 (Pacific Daylight Time)]
281
281
  // _md5 : "55ff07fb005494c4bd42322d4845e858"
282
282
  // _length : 156043
283
283
  // [ doc | error: <document from "get" or "head"> | <error from get|head> ] },
284
284
  // {
285
285
  // key : "jill",
286
- // _modified : [Date Sat Apr 22 2020 17:05:38 GMT-0700 (Pacific Daylight Time)]
287
286
  // _md5 : "f53f07fb00d42322dc4bd42322d4845e"
288
287
  // _length : 155
289
288
  // [ doc : <document from "get" or "head"> ] } ] }
@@ -335,7 +334,7 @@ closure dapoTable(database, path, local table, prefix) {
335
334
  _id: obj.id
336
335
  _rev: obj.value.rev
337
336
  }
338
- if obj.doc {
337
+ if obj.doc { // docs were requested
339
338
  if obj.doc.content {
340
339
  item._md5 = HashMD5(obj.doc.content)
341
340
  item._length = length(obj.doc.content) }
@@ -373,6 +372,7 @@ closure dapoTable(database, path, local table, prefix) {
373
372
  */
374
373
 
375
374
  closure PouchDB(urlname, local database, options) {
375
+ global(jspath, pouchdb)
376
376
  options = {
377
377
  revs_limit: 1 // number of revs to maintain for replication
378
378
  auto_compaction: false } // don't compcat on every change
@@ -480,7 +480,8 @@ closure PouchDB(urlname, local database, options) {
480
480
  *
481
481
  */
482
482
 
483
- closure ListBrowserDBs(local pending, output) {
483
+ closure ListBrowserDBs(local pending, output, item) {
484
+ global(indexedDB)
484
485
  if !indexedDB
485
486
  return (list(Error("browser database unavailable")))
486
487
  if !indexedDB.databases
@@ -507,6 +508,7 @@ closure ListBrowserDBs(local pending, output) {
507
508
  */
508
509
 
509
510
  closure DbConnector(psm, local connClassID, connector, watch) {
511
+ global()
510
512
  connClassID = "PouchDB"
511
513
  connector = new(object, this)
512
514
  connector.psm = psm
@@ -572,7 +574,7 @@ closure DbConnector(psm, local connClassID, connector, watch) {
572
574
  // The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
573
575
  // keys and error diagnostic strings.
574
576
  connector.writeResource = function writeResource(resource, resID,
575
- local errors, creds, original, error, data) {
577
+ local errors, creds, original, error, data, change) {
576
578
  // badField - test for a bad string
577
579
  function badField(str) { !string(str) || str.trim().length == 0 }
578
580
 
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2022 by Richard C. Zulch
9
+ * Copyright (c) 2020-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -22,6 +22,7 @@
22
22
  */
23
23
 
24
24
  closure FileManager(fs, options, local files, watch, opqueue, willgit, hadgit) {
25
+ global(commonWatching)
25
26
  files = new(object, this)
26
27
  files.fs = fs
27
28
  files.nodes = [] // array of nodes, one per file or folder
@@ -159,7 +160,7 @@ closure FileManager(fs, options, local files, watch, opqueue, willgit, hadgit) {
159
160
  //
160
161
  // Return the parent path of the specified node, or "" for the root.
161
162
  //
162
- function parentPath(node) {
163
+ function parentPath(node, local path) {
163
164
  path = node.path
164
165
  if path.slice(-1) == files.pathsep
165
166
  path = path.slice(0,-1)
@@ -415,7 +416,8 @@ closure FileManager(fs, options, local files, watch, opqueue, willgit, hadgit) {
415
416
  return // no git checking available
416
417
  opBeginOrReschedule(closure(callback) {
417
418
  willgit = true
418
- files.fs.gitStatus(false, function (error, statlist, local changed, delgit, unknownFiles, unknownFileStatus, changes, classID) {
419
+ files.fs.gitStatus(false, function (error, statlist,
420
+ local changed, delgit, unknownFiles, unknownFileStatus, changes, classID, item) {
419
421
  if error {
420
422
  files.gitvalid = false
421
423
  return (callback(error))
@@ -649,6 +651,7 @@ closure FileManager(fs, options, local files, watch, opqueue, willgit, hadgit) {
649
651
  */
650
652
 
651
653
  closure fimaStore(fm, node, local store) {
654
+ global()
652
655
  store = new(object, this)
653
656
  store.fm = fm
654
657
  store.fs = fm.fs
@@ -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
 
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  closure MakePSMutil(fs, local util) {
23
+ global()
23
24
  util = new(object, this)
24
25
  util.fs = fs
25
26
 
@@ -56,7 +57,8 @@ closure MakePSMutil(fs, local util) {
56
57
  //
57
58
  // Update a directory within an existing tree data structure. The result is a standard tuple with
58
59
  // (error, changes) where the changes are added/deleted/changed. This assumes that children changed
59
- // if their info is different
60
+ // if their info is different. If a child directory is found that does not yet have children, then
61
+ // the
60
62
 
61
63
  util.treeDirUpdate = closure treeDirUpdate(treepath, dirpath, tree, callback, local error, inpath, treedir, files) {
62
64
  if !callback
@@ -309,7 +311,7 @@ closure MakePSMutil(fs, local util) {
309
311
  //
310
312
  if options.erase {
311
313
  asyncArray(keys(folders).toarray, 10, closure eraser(destpath
312
- local error, outfolder, folder, namesin, removes, delopt) {
314
+ local error, outfolder, folder, namesin, node, removes, delopt) {
313
315
  `(error, outfolder) = outfs.dirList(destpath) // existing files in our destination
314
316
  if error
315
317
  return
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2023 by Richard C. Zulch
9
+ * Copyright (c) 2020-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -23,7 +23,7 @@
23
23
  * - directory names are non-empty strings with the sole "/" at the end
24
24
  * - the names ".", "..", and anything containing ":" are forbidden
25
25
  * - every directory has (or should have) an associated database record for itself
26
- * - files lacking a valid directory parent are "orphans" listed during tree enumeration
26
+ * - when these records are missing, trivial parents are created during tree enumeration
27
27
  * - pathnames are relative to the root of the view an d consist of a string with:
28
28
  * - zero or more directory names joined by "/", then
29
29
  * - either "", "/" for a directory, or "/" with a filename
@@ -37,6 +37,7 @@
37
37
  */
38
38
 
39
39
  closure FSview(table, rootpath, local view) {
40
+ global(JSpath)
40
41
  if !table
41
42
  return (list(Error("invalid arguments")))
42
43
  view = new(object, this)
@@ -44,6 +45,14 @@ closure FSview(table, rootpath, local view) {
44
45
  view.rootpath = rootpath
45
46
  view.path = JSpath.posix // for view.path.join(), etc.
46
47
 
48
+ // dirPath
49
+ //
50
+ // True iff the path is a dirpath. If the path is empty this checks the rootpath.
51
+
52
+ function dirPath(path) {
53
+ path.slice(-1) == "/" || path == "" && rootpath.slice(-1) == "/"
54
+ }
55
+
47
56
  // dirName
48
57
  //
49
58
  // Return the directory portion of the specified path.
@@ -83,7 +92,7 @@ closure FSview(table, rootpath, local view) {
83
92
  if isPath {
84
93
  if name == ""
85
94
  return (true)
86
- else if name.substring(0, 1) == "/" || path.indexOf("//") >= 0
95
+ else if name.substring(0, 1) == "/" || name.indexOf("//") >= 0
87
96
  return (false)
88
97
  else
89
98
  name = baseName(name) }
@@ -98,9 +107,7 @@ closure FSview(table, rootpath, local view) {
98
107
  if !string(path) {
99
108
  if !msg
100
109
  msg = "invalid path"
101
- if !string(path)
102
- path = "<".concat(typeof(path), ">") // e.g. "<xobject>"
103
- asyncResult(callback, Error(msg, typeof(content), {
110
+ asyncResult(callback, Error(msg, "invalid path ${typeof(path)}", {
104
111
  status: 400
105
112
  name: "invalid_arguments"
106
113
  path: path
@@ -110,8 +117,10 @@ closure FSview(table, rootpath, local view) {
110
117
 
111
118
  // checkPathName
112
119
  //
113
- // Return a `(path, name) tuple for a directory, or false if callback executed due to error.
114
-
120
+ // Return a `(path, name) tuple for a directory, or false if callback executed due to error. The
121
+ // name is the empty string if the path is empty. Path "/" has name "/". Otherwise the name is
122
+ // the last segment of the path, omitting the trailing slash.
123
+ //
115
124
  function checkPathName(path, callback, local name, slashdex) {
116
125
  if path == ""
117
126
  name = ""
@@ -162,7 +171,7 @@ closure FSview(table, rootpath, local view) {
162
171
  else
163
172
  proc = view.table.get
164
173
  `(error, data) = proc(path)
165
- if error && error.status == 404 && path.slice(-1) != "/"
174
+ if error && error.status == 404 && !dirPath(path)
166
175
  `(error, data) = proc(path.concat("/"))
167
176
  callback(error, data)
168
177
  }
@@ -199,7 +208,7 @@ closure FSview(table, rootpath, local view) {
199
208
  birthtime: nowstr
200
209
  }
201
210
  }
202
- if path.slice(-1) == "/"
211
+ if dirPath(path)
203
212
  metadata.finfo.type = "directory"
204
213
  else
205
214
  metadata.finfo.type = "file"
@@ -232,7 +241,7 @@ closure FSview(table, rootpath, local view) {
232
241
  else
233
242
  metadata.finfo.encoding = "utf8" } // default string encoding
234
243
  else if content && !xobject(content)
235
- return (callback(Error("unsupported content type", typeof(content), {
244
+ return (callback(Error("unsupported content type ${typeof(content)}", {
236
245
  status: 400
237
246
  name: "invalid_arguments"
238
247
  path: path
@@ -267,7 +276,7 @@ closure FSview(table, rootpath, local view) {
267
276
  return (asyncResult(callback, false, { created: false })) // root always already exists
268
277
  if checkPath(path, callback)
269
278
  return
270
- if path.slice(-1) != "/"
279
+ if !dirPath(path)
271
280
  path = path.concat("/")
272
281
  view.table.get(path, function(error, data, local pathdirs, buildpath, item) {
273
282
  if !error
@@ -285,7 +294,7 @@ closure FSview(table, rootpath, local view) {
285
294
  name: "conflict"
286
295
  path: buildpath
287
296
  })))
288
- if buildpath.slice(-1) != "/"
297
+ if !dirPath(buildpath)
289
298
  buildpath = buildpath.concat("/")
290
299
  `(error, data) = view.table.head(buildpath) // get desired directory
291
300
  if error.status == 404
@@ -310,7 +319,7 @@ closure FSview(table, rootpath, local view) {
310
319
  if dest.indexOf("/") < 0
311
320
  newpath = dirName(srcpath).concat(dest) // renaming within same directory
312
321
  else {
313
- if dest.slice(-1) == "/" // moving to new directory location:
322
+ if dirPath(dest) // moving to new directory location:
314
323
  newpath = dest.concat(baseName(srcpath)) // new path is new pathname
315
324
  else { // moving and renaming:
316
325
  newpath = dest // new path is new pathname
@@ -352,10 +361,17 @@ closure FSview(table, rootpath, local view) {
352
361
 
353
362
  // copyDirectory
354
363
  //
355
- // Copy a directory by its source path, with optional move.
356
-
357
- closure copyDirectory(srcpath, dest, options, local error, nodes, data, destpath, errors, docpath, newpath, srcroot) {
358
- `(error, nodes) = view.table.records({ prefix: srcpath })
364
+ // Copy a directory by its source path, with optional move. The
365
+ // destination is:
366
+ // - a new name in same parent if it has no embedded slashes, or
367
+ // - the same name in a new location if it ends in a slash, otherwise
368
+ // - a new pathname for the source.
369
+ //
370
+ closure copyDirectory(srcpath, dest, options,
371
+ local error, nodes, data, destpath, errors, docpath, newpath, srcroot, item) {
372
+ if !dirPath(srcpath)
373
+ srcpath = srcpath.concat("/") // normalize source as directory
374
+ `(error, nodes) = view.table.records({ prefix: srcpath }) // each item has key/_id/_rev, not doc
359
375
  if error
360
376
  return (list(Error("copyDirectory: can't enumerate source directory", {
361
377
  source: srcpath
@@ -372,13 +388,13 @@ closure FSview(table, rootpath, local view) {
372
388
  destpath = dirName(srcpath).concat(dest) // new directory name in same parent directory
373
389
  dest = dirName(srcpath) } // dest is new parent pathname
374
390
  else {
375
- if dest.slice(-1) == "/"
391
+ if dirPath(dest)
376
392
  destpath = dest.concat(baseName(srcpath)) // same name; new location
377
393
  else {
378
394
  destpath = dest // new full pathname
379
395
  dest = dirName(dest) } // dest is new parent pathname
380
396
  }
381
- if destpath != "" && destpath.slice(-1) != "/"
397
+ if destpath != "" && !dirPath(destpath)
382
398
  destpath = destpath.concat("/") // get actual path of new directory
383
399
  //
384
400
  // Change srcpath to destpath for all records. Note that dest is the name of our new parent
@@ -434,7 +450,7 @@ closure FSview(table, rootpath, local view) {
434
450
  if !srcroot
435
451
  srcroot = item // delete source root later
436
452
  else {
437
- `(error, data) = view.table.delete(item.doc) // delete original
453
+ `(error, data) = view.table.delete(item) // delete original
438
454
  if error
439
455
  errors.push(Error("copyDirectory: can't remove original", {
440
456
  source: docpath
@@ -448,7 +464,7 @@ closure FSview(table, rootpath, local view) {
448
464
  }, errors))
449
465
  else {
450
466
  if srcroot {
451
- `(error, data) = view.table.delete(srcroot.doc) // delete original root
467
+ `(error, data) = view.table.delete(srcroot) // delete original root
452
468
  if error
453
469
  return (list(Error("copyDirectory: can't remove original root", {
454
470
  source: srcpath
@@ -481,13 +497,13 @@ closure FSview(table, rootpath, local view) {
481
497
  finfo.length = data.length
482
498
  else if data._length
483
499
  finfo.length = data._length
484
- if finfo.length
500
+ if dirPath(path)
501
+ finfo.type = "directory"
502
+ else if finfo.length
485
503
  finfo.type = "file" }
486
504
  else if path == "" {
487
505
  error = false // we were asked for info on superoot
488
506
  finfo = {
489
- md5: HashMD5("")
490
- length: 0
491
507
  type: "directory"
492
508
  } }
493
509
  callback(error, finfo)
@@ -545,7 +561,7 @@ closure FSview(table, rootpath, local view) {
545
561
  return
546
562
  if !content
547
563
  return (asyncResult(callback, Error("content required")))
548
- if path.slice(-1) == "/"
564
+ if dirPath(path)
549
565
  return (asyncResult(callback, Error("cannot write to a directory", { path: path })))
550
566
  if !validName(path, true)
551
567
  return (asyncResult(callback, Error("invalid filename", { path: path })))
@@ -573,12 +589,11 @@ closure FSview(table, rootpath, local view) {
573
589
  // Copy / move / rename a file or directory. The source is the full path of the item. The
574
590
  // destination is:
575
591
  // - a new name if it has no embedded slashes, or
576
- // - a new location if it ends in a slash, otherwise
577
- // - a new pathname for the source if it does not end in a slash.
592
+ // - the same name in a new location if it ends in a slash, otherwise
593
+ // - a new pathname for the source.
578
594
  // This works by creating a new file or directory of files in the new location and then
579
- // optionally deleting the files in the existing location. If options.move is true then we
580
- // delete the source after the copy.
581
-
595
+ // deleting the files in the existing location after the copy if options.move is true.
596
+ //
582
597
  view.copy = closure copy(srcpath, dest, options, callback) {
583
598
  if !callback
584
599
  return (syncAdapter(copy, srcpath, dest, options))
@@ -594,7 +609,7 @@ closure FSview(table, rootpath, local view) {
594
609
  source: srcpath
595
610
  destination: dest
596
611
  }, error)))
597
- else if srcpath.slice(-1) == "/"
612
+ else if data.key.slice(-1) == "/"
598
613
  result = copyDirectory(srcpath, dest, options) // hard case: directory with possibly many children
599
614
  else
600
615
  result = copyFile(data, dest, options) // easy case: single file
@@ -624,7 +639,7 @@ closure FSview(table, rootpath, local view) {
624
639
  name: "forbidden"
625
640
  path: path
626
641
  })))
627
- if path.slice(-1) != "/"
642
+ if !dirPath(path)
628
643
  path = path.concat("/") // avoid deleting longer filenames with same parent and prefix
629
644
  `(error, data) = dbDirRecords(path, true)
630
645
  if error
@@ -637,13 +652,13 @@ closure FSview(table, rootpath, local view) {
637
652
  basedir = item // delete base directory at end
638
653
  continue
639
654
  }
640
- `(error, data) = view.table.delete(item.doc)
655
+ `(error, data) = view.table.delete(item)
641
656
  if error
642
657
  errors.push(error) }
643
658
  if errors.length != 0
644
659
  error = Error("some directory children not deleted", errors)
645
660
  else {
646
- `(error, data) = view.table.delete(basedir.doc)
661
+ `(error, data) = view.table.delete(basedir)
647
662
  if error
648
663
  error = Error("delete directory failed", {
649
664
  path: path
@@ -659,7 +674,7 @@ closure FSview(table, rootpath, local view) {
659
674
  //
660
675
  // Read a directory at the specified path
661
676
  // ### options currently ignored, for better or woise
662
-
677
+ //
663
678
  view.dirList = closure dirList(path, options, callback, local name, dirdict) {
664
679
  if !callback
665
680
  return (syncAdapter(readir, path))
@@ -667,7 +682,7 @@ closure FSview(table, rootpath, local view) {
667
682
  if !path
668
683
  return // already executed callback with error
669
684
  dirdict = {
670
- name: name.slice(0,-1)
685
+ name: name
671
686
  path: path
672
687
  data: {
673
688
  platform: table.type
@@ -700,10 +715,8 @@ closure FSview(table, rootpath, local view) {
700
715
  finfo.type = "directory"
701
716
  } else if !finfo.type
702
717
  finfo.type = "file" // everything is a file in UNIX
703
- if item.doc._md5
704
- finfo.md5 = item.doc._md5
705
- if item.doc._length
706
- finfo.length = item.doc._length
718
+ finfo.md5 = item.doc._md5 || undefined
719
+ finfo.length = item.doc._length || item.doc.length || undefined
707
720
  if item.key.length > offset // not the directory itself
708
721
  dirdict.children.push({
709
722
  name: name
@@ -723,7 +736,7 @@ closure FSview(table, rootpath, local view) {
723
736
  // tree
724
737
  //
725
738
  // Return a tree for the specified path. The path is relative to the root of the view.
726
-
739
+ //
727
740
  view.tree = closure tree(path, options, callback, local name, treedict, item, offset) {
728
741
  if !callback
729
742
  return (syncAdapter(tree, path, options))
@@ -734,8 +747,10 @@ closure FSview(table, rootpath, local view) {
734
747
  prefix: path
735
748
  deep: true
736
749
  }
750
+ if name == "" && rootpath != ""
751
+ name = rootpath.split("/").slice(0,-1).pop() || ""
737
752
  treedict = {
738
- name: name.slice(0,-1)
753
+ name: name
739
754
  data: {
740
755
  platform: table.type
741
756
  semantics: "dbfs"
@@ -748,7 +763,8 @@ closure FSview(table, rootpath, local view) {
748
763
  for item in path.split("/").slice(0,-1)
749
764
  treedict.parents.push(item)
750
765
  offset = path.length // offset to path's child names (after any slash)
751
- view.table.records(options, function(error, data, local index, leaf, parent, slashdex) {
766
+ view.table.records(options, function(error, data,
767
+ local index, leaf, parent, child, slashdex, parents) {
752
768
  if error
753
769
  return (callback(error))
754
770
  if data.rows.length == 0 && path != ""
@@ -775,22 +791,28 @@ closure FSview(table, rootpath, local view) {
775
791
  else { // named or non-root directory
776
792
  slashdex = name.lastIndexOf("/", name.length-2) // penultimate slash
777
793
  parent = name.substring(0, slashdex+1) // includes parent's ending slash
778
- leaf = name.slice(slashdex+1, -1) } // omits child's ending slash
779
- child = index[name] = {
794
+ leaf = name.slice(slashdex+1, -1) } // omits child's ending slash
795
+ child = index[name] = {
780
796
  name: leaf
781
797
  children: [] } }
782
798
  else { // file child not in root
783
799
  parent = name.substring(0, slashdex+1)
784
800
  leaf = name.slice(slashdex+1)
785
801
  child = { name: leaf } }
786
- if index[parent]
787
- index[parent].children.push(child)
788
- else { // orphaned child doesn't have <parent>/ record
789
- if !treedict.orphans
790
- treedict.orphans = []
791
- child.key = item.key
792
- treedict.orphans.push(child)
802
+ if !index[parent] { // doesn't have immediate <parent>/ record
803
+ parents = parent.split("/").slice(0,-1)
804
+ parent = item = ""
805
+ for leaf in parents {
806
+ item = item.concat(leaf, "/")
807
+ if !index[item]
808
+ index[parent].children.push(
809
+ index[item] = {
810
+ name: leaf
811
+ children: [] })
812
+ parent = item
813
+ }
793
814
  }
815
+ index[parent].children.push(child)
794
816
  }
795
817
  callback(false, treedict)
796
818
  })
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * column positioning: // // !
13
13
  *
14
- * Copyright (c) 2019-2022 by Richard C. Zulch
14
+ * Copyright (c) 2019-2024 by Richard C. Zulch
15
15
  *
16
16
  */
17
17
 
@@ -28,6 +28,7 @@
28
28
  */
29
29
 
30
30
  closure rsrcVault(db, tablename, local vault) {
31
+ global()
31
32
  vault = new(object, this)
32
33
 
33
34
  // addResource
@@ -72,7 +73,7 @@ closure rsrcVault(db, tablename, local vault) {
72
73
  //
73
74
  // Return credentials for access to the resource.
74
75
 
75
- vault.accessResource = function accessResource(resID) {
76
+ vault.accessResource = function accessResource(resID, local error, data) {
76
77
  `(error, data) = vault.table.get(resID)
77
78
  if error
78
79
  list(error)
@@ -134,6 +135,7 @@ closure rsrcVault(db, tablename, local vault) {
134
135
  */
135
136
 
136
137
  closure ResourceManager(dbname, prefix, local reman, watch, result) {
138
+ global(commonWatching)
137
139
  reman = new(object, this)
138
140
  watch = commonWatching.Watchable()
139
141
  reman.watch = watch.watch