@naanlang/naan 1.3.2 → 1.4.1

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 (56) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/index.js +25 -15
  4. package/dist/naan.min.js +5 -5
  5. package/frameworks/browser/sworker.js +23 -23
  6. package/frameworks/browser/terminals.nlg +26 -11
  7. package/frameworks/browser/ws_client.nlg +1 -1
  8. package/frameworks/client/psm_client.nlg +3 -1
  9. package/frameworks/client/relaycon.nlg +3 -2
  10. package/frameworks/common/common.nlg +5 -2
  11. package/frameworks/common/preferences.nlg +1 -1
  12. package/frameworks/node/apiserver.nlg +3 -3
  13. package/frameworks/node/filesystem.nlg +5 -4
  14. package/frameworks/node/psm_server.nlg +1 -1
  15. package/frameworks/node/shell.nlg +134 -0
  16. package/frameworks/node/worker.nlg +104 -46
  17. package/frameworks/project/build.nlg +1 -2
  18. package/frameworks/project/jszip.js +0 -0
  19. package/frameworks/project/projects.nlg +1 -1
  20. package/frameworks/running/debugnub.nlg +102 -12
  21. package/frameworks/running/debugutil.nlg +30 -13
  22. package/frameworks/running/executors.nlg +64 -28
  23. package/frameworks/running/running.nlg +85 -3
  24. package/frameworks/running/sourcecode.nlg +3 -1
  25. package/frameworks/running/taskexec.nlg +24 -4
  26. package/frameworks/storage/dbt_pouch.nlg +30 -17
  27. package/frameworks/storage/file_manager.nlg +1 -1
  28. package/frameworks/storage/psm.nlg +11 -1
  29. package/frameworks/storage/psm_dbtables.nlg +21 -22
  30. package/frameworks/storage/resources.nlg +12 -4
  31. package/frameworks/storage/storage.nlg +2 -2
  32. package/lib/browser/env_web.js +6 -6
  33. package/lib/browser/env_webworker.js +2 -0
  34. package/lib/core/naanlib.js +5 -5
  35. package/lib/env_nodeworker.js +2 -0
  36. package/package.json +1 -1
  37. package/plugins/gitClient/gitClient.nlg +3 -3
  38. package/plugins/gitClient/gitutils.nlg +2 -2
  39. package/plugins/nodeLib/node_https.nlg +2 -2
  40. package/plugins/openSearch/openSearch.nlg +2 -2
  41. package/plugins/serviceAws/lambda_naan_control.nlg +3 -3
  42. package/plugins/serviceAws/psm_aws.nlg +4 -2
  43. package/plugins/serviceAws/serviceAws.nlg +1 -1
  44. package/plugins/serviceGC/gc_api.nlg +5 -5
  45. package/plugins/serviceGitHub/gitops.nlg +4 -3
  46. package/plugins/serviceGitHub/psm_github.nlg +1 -1
  47. package/plugins/serviceGitHub/serviceGitHub.nlg +1 -1
  48. package/plugins/serviceGitLab/gitops.nlg +2 -1
  49. package/plugins/serviceGitLab/psm_gitlab.nlg +2 -2
  50. package/plugins/serviceGitLab/serviceGitLab.nlg +1 -1
  51. package/plugins/serviceYC/yc_vm.nlg +1 -1
  52. package/test/harness.nlg +9 -9
  53. package/test/test_01_core.nlg +24 -2
  54. package/test/test_02_context.nlg +2 -2
  55. package/test/test_07_lingo.nlg +3 -3
  56. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -24,19 +24,20 @@ closure ExecutorBase(track, type, name, local exec) {
24
24
  exec.name = name
25
25
 
26
26
  // execReset
27
+ //
27
28
  // Initialize remote communications for a new or restarted instance.
28
-
29
+ //
29
30
  exec.execReset = function execReset() {
30
31
  exec.pending = { }
31
32
  exec.nextSendID = toint(RandomBytes(3)) // keep our sequence code unique for shared servers
32
- exec.nextContextID = 1
33
33
  }
34
34
  execReset()
35
35
 
36
36
  // execSend
37
+ //
37
38
  // Send a message to the running instance and return a nonce to wait on. If data is not a
38
39
  // dictionary then it is ignored.
39
-
40
+ //
40
41
  exec.execSend = closure execSend(msg, data, local pending, error) {
41
42
  if !dictionary(data)
42
43
  data = { }
@@ -52,8 +53,9 @@ closure ExecutorBase(track, type, name, local exec) {
52
53
  }
53
54
 
54
55
  // execReceived
56
+ //
55
57
  // The specified message was received from the instance, so signal it.
56
-
58
+ //
57
59
  exec.execReceived = closure execReceived(data, local pending) {
58
60
  pending = exec.pending[data.xid]
59
61
  if !pending { // not waiting on message
@@ -68,15 +70,17 @@ closure ExecutorBase(track, type, name, local exec) {
68
70
  }
69
71
 
70
72
  // execSetStatus
73
+ //
71
74
  // Received a status update from the instance.
72
-
75
+ //
73
76
  exec.execSetStatus = function execSetStatus(status) {
74
77
  exec.xstatus = status
75
78
  }
76
79
 
77
80
  // execFailed
81
+ //
78
82
  // Communication has failed.
79
-
83
+ //
80
84
  exec.execFailed = closure execFailed(error, local xid, pending) {
81
85
  exec.pending.error = error
82
86
  for `(xid, pending) in exec.pending
@@ -84,8 +88,10 @@ closure ExecutorBase(track, type, name, local exec) {
84
88
  }
85
89
 
86
90
  // execClosed
91
+ //
92
+ //
87
93
  // Note that the connection has closed, so signal all the waiters.
88
-
94
+ //
89
95
  exec.execClosed = closure execClosed(error, local xid, pending) {
90
96
  for `(xid, pending) in exec.pending {
91
97
  pending.signal(list(error))
@@ -94,10 +100,13 @@ closure ExecutorBase(track, type, name, local exec) {
94
100
  }
95
101
 
96
102
  // context
97
- // Return a context object for the instance, or an error, in a result tuple. Currently this does
98
- // not ever reclaim old contexts on the instance end of things, but it should. ###
99
-
100
- exec.context = closure context(local error, retries, ms, delta, excon) {
103
+ //
104
+ // Return a context object for the instance, or an error, in a result tuple. The optional state
105
+ // dictionary argument initializes the context with the state of a previous one to facilitate
106
+ // persistent reconnection. Currently this does not ever reclaim old contexts on the instance
107
+ // end of things, but it should. ###
108
+ //
109
+ exec.context = closure context(state, local error, retries, ms, delta, excon) {
101
110
  retries = 0
102
111
  ms = milliseconds()
103
112
  delta = 1
@@ -114,13 +123,14 @@ closure ExecutorBase(track, type, name, local exec) {
114
123
  sleep(delta)
115
124
  if delta < 100
116
125
  delta *= 2 }
117
- excon = ExecutorContext(exec, exec.nextContextID++)
126
+ excon = ExecutorContext(exec, state)
118
127
  excon.register()
119
128
  }
120
129
 
121
130
  // attention
131
+ //
122
132
  // Focus the UI on this instance because something needs attention.
123
-
133
+ //
124
134
  exec.attention = function attention() {
125
135
  track.attention(exec)
126
136
  }
@@ -141,20 +151,37 @@ closure ExecutorBase(track, type, name, local exec) {
141
151
  * ability to track namespace changes as the result of evaluating an expression. They do not override
142
152
  * normal namespace protections. The contexts exposed here preserve the active namespace after the
143
153
  * expression is evaluated.
154
+ * The optional state argument preserves the context state so that it can be restored for
155
+ * resuming persistent connections.
144
156
  *
145
157
  */
146
158
 
147
- closure ExecutorContext(exec, contextID, local context, util) {
159
+ closure ExecutorContext(exec, state, local context, coder, util) {
148
160
  global(apply)
149
161
  context = new(object, this)
162
+ if !state || !state.contextID
163
+ state = {
164
+ contextID: UUID()
165
+ }
166
+ context.state = state
167
+ coder = packageCoder(evalf)
150
168
  util = modlist().Util.exports
151
169
 
170
+ // getState
171
+ //
172
+ // Get context state for future initialization
173
+ //
174
+ context.getState = function getState() {
175
+ context.state
176
+ }
177
+
152
178
  // register
179
+ //
153
180
  // Register or re-register our context with the worker.
154
-
181
+ //
155
182
  context.register = closure register(local pending, result) {
156
183
  pending = exec.execSend("register", {
157
- contextID: contextID
184
+ contextID: state.contextID
158
185
  })
159
186
  result = pending.wait()
160
187
  if result.1
@@ -164,10 +191,11 @@ closure ExecutorContext(exec, contextID, local context, util) {
164
191
  }
165
192
 
166
193
  // eval
194
+ //
167
195
  // Evaluate the specified expression in the current worker context. The optional put argument
168
196
  // if non-false is a tuple of symbols that should be transfered to the remote end, while get is
169
197
  // an optional tuple of symbols to retrieve after evaluation.
170
-
198
+ //
171
199
  context.eval = closure evalf(expr, put, get, local roots, pkg, data, pending, result) {
172
200
  if tuple(put)
173
201
  roots = cons(`expr, put)
@@ -175,9 +203,11 @@ closure ExecutorContext(exec, contextID, local context, util) {
175
203
  roots = `(expr)
176
204
  pkg = util.pkgSave(`expr, roots, {
177
205
  nsignore: true
178
- comments: true })
206
+ comments: true
207
+ objectEncoder: coder.encodeObject
208
+ })
179
209
  data = {
180
- contextID: contextID
210
+ contextID: state.contextID
181
211
  expr: pkg }
182
212
  if tuple(get)
183
213
  data.get = get
@@ -190,22 +220,25 @@ closure ExecutorContext(exec, contextID, local context, util) {
190
220
  localroots: false
191
221
  intern: false
192
222
  execute: false
223
+ objectDecoder: coder.decodeObject
193
224
  })))
194
225
  else
195
226
  result
196
227
  }
197
-
228
+
198
229
  // evalq
230
+ //
199
231
  // Evaluate the specified QUOTED expression in the current worker context.
200
-
232
+ //
201
233
  context.evalq = macro evalq(expr, put, get) {
202
234
  evalf(expr, eval(put), eval(get))
203
235
  }
204
-
236
+
205
237
  // apply
238
+ //
206
239
  // Apply list of arguments to the specified procure evaluated remotely. If the procedure is a
207
240
  // not a tuple then this uses the tuple definition if there is one.
208
-
241
+ //
209
242
  context.apply = closure applyf(proc, args, put, get) {
210
243
  if !tuple(proc) && tuple(proc.proc)
211
244
  proc = proc.proc
@@ -213,9 +246,10 @@ closure ExecutorContext(exec, contextID, local context, util) {
213
246
  }
214
247
 
215
248
  // call
249
+ //
216
250
  // Call the specified function remotely using local arguments:
217
251
  // context.call(proc, args...)
218
-
252
+ //
219
253
  context.call = macro callf args {
220
254
  closure (proc, arglist, local eargs) {
221
255
  while arglist
@@ -225,13 +259,14 @@ closure ExecutorContext(exec, contextID, local context, util) {
225
259
  }
226
260
 
227
261
  // rpc
262
+ //
228
263
  // Execute a remote procedure call without package translation, to reduce overhead. This will not
229
264
  // properly transfer Naan-specific types like symbols, and the results will be native JavaScript.
230
265
  // The procedure must be specified as a symbol or string.
231
-
266
+ //
232
267
  context.rpc = closure rpc(proc, args, local data, pending, result) {
233
268
  data = {
234
- contextID: contextID
269
+ contextID: state.contextID
235
270
  proc: proc
236
271
  args: args }
237
272
  pending = exec.execSend("rpc", data)
@@ -647,7 +682,7 @@ closure ExecutorTracker(local xtrak) {
647
682
  */
648
683
 
649
684
  closure execLambda(track, url, name, workerID, local target, nlregex, clientID, serverID) {
650
- global()
685
+ global(js)
651
686
  target = ExecutorBase(track, "Lambda", name)
652
687
  clientID = "DebugWorkers"
653
688
  serverID = "Workers"
@@ -896,7 +931,8 @@ closure execLambda(track, url, name, workerID, local target, nlregex, clientID,
896
931
  */
897
932
 
898
933
  function execInit(local manifest) {
899
- manifest = `(ExecutorBase, ExecutorContext, ExecutorParallel, ExecutorTracker, execLambda, execInit)
934
+ manifest = `(ExecutorBase, ExecutorContext, ExecutorParallel, ExecutorTracker, execLambda,
935
+ execInit)
900
936
 
901
937
  Naan.module.build(module.id, "executors", function(modobj, compobj) {
902
938
  require("./running.nlg")
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2021-2023 by Richard C. Zulch
9
+ * Copyright (c) 2021-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  function rootproc(procdef, local parent) {
22
+ global()
22
23
  loop { // can only find out proc
23
24
  parent = procdef.1@\.parent
24
25
  if !parent
@@ -38,6 +39,7 @@ function rootproc(procdef, local parent) {
38
39
  */
39
40
 
40
41
  function baseproc(procdef) {
42
+ global()
41
43
  if procdef.1@\.base.proc
42
44
  { }
43
45
  else
@@ -58,6 +60,7 @@ function baseproc(procdef) {
58
60
  */
59
61
 
60
62
  function pathmatch(parent, path, local segment, nth, child) {
63
+ global()
61
64
  if empty(path)
62
65
  return (parent)
63
66
  segment = pop(path)
@@ -83,6 +86,7 @@ function pathmatch(parent, path, local segment, nth, child) {
83
86
  */
84
87
 
85
88
  function procpath(procdef, local path, segment, parent, peers, nth, peer) {
89
+ global()
86
90
  loop {
87
91
  segment = procdef.1
88
92
  parent = segment@\.parent
@@ -114,6 +118,7 @@ function procpath(procdef, local path, segment, parent, peers, nth, peer) {
114
118
  */
115
119
 
116
120
  function procname(name, local parent) {
121
+ global()
117
122
  parent = name@\.parent
118
123
  name = tostring(name)
119
124
  if parent
@@ -136,6 +141,7 @@ function procname(name, local parent) {
136
141
  */
137
142
 
138
143
  function procstring(procdef, options, local name, output, parm) {
144
+ global()
139
145
  if options.fullname || !defined(options, `fullname)
140
146
  name = procname(procdef.1) // default is full name
141
147
  else
@@ -156,6 +162,82 @@ function procstring(procdef, options, local name, output, parm) {
156
162
  }
157
163
 
158
164
 
165
+ /*
166
+ * packageCoder
167
+ *
168
+ * Encode and decode objects for the NaaN packager to establish proxies for remote access.
169
+ *
170
+ */
171
+
172
+ closure packageCoder(evalf, local coder) {
173
+ global()
174
+ coder = new(object, this)
175
+ coder.wmapEncoding = new(weakmap)
176
+ coder.mapDecoding = { }
177
+
178
+ // encodeObject
179
+ //
180
+ // Encode an object into the ident dictionary so that the remote end can create a proxy.
181
+ //
182
+ coder.encodeObject = closure encodeObject(obj, local objectID) {
183
+ if coder.wmapEncoding[obj]
184
+ { }
185
+ else {
186
+ objectID = UUID()
187
+ coder.mapDecoding[objectID] = obj
188
+ coder.wmapEncoding[obj] = {
189
+ name: obj@\.closure || undefined
190
+ objectID: objectID
191
+ }
192
+ }
193
+ }
194
+
195
+ // decodeObject
196
+ //
197
+ // Decode the ident dictionary into a proxy object allowing method invocation.
198
+ //
199
+ coder.decodeObject = closure decodeObject(ident, local x, obj) {
200
+ if coder.mapDecoding[ident.objectID]
201
+ { }
202
+ else if ident.name {
203
+ putproc(ident.name, false)
204
+ obj = coder.mapDecoding[ident.objectID] = closure(nn, objectID) {
205
+ call(enclose(list(`closure, nn, list(`local, `x),
206
+ quote(x=new(object, nn)),
207
+ quote(x@\.tostring = function() { "{proxy ${nn}}" }),
208
+ quote(x@\.undefined = closure undef args {
209
+ let (result) {
210
+ result = evalf(list(`\.execute\-method, objectID, args))
211
+ if result.0
212
+ throw("remoting failure: ${ErrorString(result.0)}")
213
+ else
214
+ result.1
215
+ } ()
216
+ }), `x)))
217
+ } (ident.name, ident.objectID)
218
+ coder.wmapEncoding[obj] = ident
219
+ obj
220
+ } else
221
+ coder.mapDecoding[ident.objectID] = new(object)
222
+ }
223
+
224
+ // invokeMethod
225
+ //
226
+ // Invoke a local object method from the remote proxy, returning a result tuple.
227
+ //
228
+ coder.invokeMethod = closure invokeMethod(objectID, args, local obj) {
229
+ obj = coder.mapDecoding[objectID]
230
+ if obj
231
+ eval(list(deref, obj, pop(args), args))
232
+ else
233
+ throw("remoting error: object ${objectID} not found")
234
+ }
235
+
236
+ // finis
237
+ coder
238
+ };
239
+
240
+
159
241
  /*
160
242
  * runnInit
161
243
  *
@@ -164,8 +246,8 @@ function procstring(procdef, options, local name, output, parm) {
164
246
  */
165
247
 
166
248
  function runnInit(local manifest) {
167
- manifest = `(rootproc, baseproc, pathmatch, procpath, procname, procstring, runnInit)
168
-
249
+ manifest = `(rootproc, baseproc, pathmatch, procpath, procname, procstring, packageCoder,
250
+ runnInit)
169
251
  Naan.module.build(module.id, "running", closure(modobj, compobj) {
170
252
  require("../common").LiveImport()
171
253
  compobj.manifest = manifest
@@ -87,12 +87,14 @@ closure CodeManager(options, local coman, cache)
87
87
  output.push(false)
88
88
  continue
89
89
  }
90
+ if text.startsWith("!")
91
+ text = "(${text})" // no shell escapes here
90
92
  try {
91
93
  source = new(textstream, text.concat(" ;")) // avoid EOF
92
94
  if locals
93
95
  source.setsymbols(locals)
94
96
  registry = modlist().Lib.exports.Registry // dialect registry
95
- dialect = registry.findName(registry.dialects().0) // ### default dialecct
97
+ dialect = registry.findName(registry.dialects().0) // ### default dialect
96
98
  output.push(dialect.parse(source))
97
99
  } catch {
98
100
  if true {
@@ -19,12 +19,27 @@
19
19
  *
20
20
  */
21
21
 
22
- closure TaskDispatcher(replier, local contexts, gohome, util) {
22
+ closure TaskDispatcher(replier, local contexts, gohome, coder, util) {
23
23
  global()
24
24
  contexts = { }
25
25
  gohome = makeActivator()
26
+ coder = packageCoder(evalf)
26
27
  util = modlist().Util.exports
27
- closure(data, local reply, restorens, context, expr, roots, proc, args, result) {
28
+
29
+ // sendEval
30
+ //
31
+ // Send an evaluation request to the original execution context.
32
+ //
33
+ closure evalf(expr) {
34
+ debuglog("TaskDispatcher doesn't yet implement reverse proxy objects")
35
+ list(Error("### reverse object proxy not implemented"))
36
+ }
37
+
38
+ // dispatcher
39
+ //
40
+ // Return a new task dispatcher.
41
+ //
42
+ closure dispatcher(data, local reply, restorens, context, expr, roots, proc, args, result) {
28
43
  reply = {
29
44
  xmsg: "error"
30
45
  xid: data.xid
@@ -70,10 +85,14 @@ closure TaskDispatcher(replier, local contexts, gohome, util) {
70
85
  intern: true // intern symbols
71
86
  localroots: true // make roots local if needed to intern
72
87
  execute: true // execute auto-running procedures
88
+ objectDecoder: coder.decodeObject
73
89
  })
74
90
  expr = caar(expr)
91
+ if expr.0 == `\.execute\-method
92
+ expr = coder.invokeMethod(expr.1, expr.2) // object method, args
93
+ else
94
+ expr = evalactive(expr) // evaluate expression
75
95
  reply.error = undefined
76
- expr = evalactive(expr)
77
96
  if data.get
78
97
  roots = new(data.get).map(function(sym){ // new() to get symbol from compress
79
98
  compress(sym)
@@ -84,7 +103,8 @@ closure TaskDispatcher(replier, local contexts, gohome, util) {
84
103
  roots = `(expr)
85
104
  reply.result = util.pkgSave(`expr, roots, {
86
105
  nsignore: true // don't report namespace errors
87
- comments: true }) // include comments
106
+ comments: true
107
+ objectEncoder: coder.encodeObject}) // include comments
88
108
  if context.namespace !== nsactive().0
89
109
  { // evaluation changed namespace
90
110
  context.namespace = nsactive().0
@@ -34,13 +34,17 @@
34
34
  *
35
35
  */
36
36
 
37
- closure dapoTable(database, path, local table, prefix) {
37
+ closure dapoTable(database, rootpath, local table, prefix) {
38
38
  global()
39
+ if !database || !string(rootpath)
40
+ return (list(Error("invalid arguments")))
39
41
  table = new(object, this)
40
42
  table.type = database.type
41
43
  table.db = database
42
- table.path = path
43
- table.prefix = makePrefix(path)
44
+ if rootpath != "" && rootpath.slice(-1) != "/"
45
+ rootpath = rootpath.concat("/") // "" or "<path>/"
46
+ table.rootpath = rootpath
47
+ table.prefix = makePrefix(rootpath)
44
48
 
45
49
  // makePrefix
46
50
  //
@@ -80,7 +84,7 @@ closure dapoTable(database, path, local table, prefix) {
80
84
  return (syncAdapter(add, key, content, metadata))
81
85
  id = makeID(key)
82
86
  if !string(id)
83
- return (asyncResult(callback, id)) // error from makeID
87
+ return (asyncResult(callback, id)) // error from makeID
84
88
  if metadata && !dictionary(metadata)
85
89
  return (asyncResult(callback, Error("invalid arguments")))
86
90
  if !database.pouch
@@ -291,7 +295,7 @@ closure dapoTable(database, path, local table, prefix) {
291
295
  // only immediate children are listed, with directories at the end--and no metadata for them.
292
296
  //
293
297
 
294
- table.records = closure records(options, callback, local prefix, pouchOpts, output) {
298
+ table.records = closure records(options, callback, local prefix, prelen, pouchOpts, output) {
295
299
  if !callback
296
300
  return (syncAdapter(records, options))
297
301
  output = {
@@ -300,9 +304,11 @@ closure dapoTable(database, path, local table, prefix) {
300
304
  if !string(options.prefix)
301
305
  return (asyncResult(callback, Error("prefix option must be string")))
302
306
  output.prefix = options.prefix
303
- prefix = table.prefix.concat(makePrefix(options.prefix)) }
304
- else
307
+ prefix = table.prefix.concat(makePrefix(options.prefix))
308
+ prelen = options.prefix.length }
309
+ else {
305
310
  prefix = table.prefix
311
+ prelen = 0 }
306
312
  if !database.pouch
307
313
  return (asyncResult(callback, Error("database closed")))
308
314
  pouchOpts = {
@@ -321,19 +327,24 @@ closure dapoTable(database, path, local table, prefix) {
321
327
  key = obj.id.substring(table.prefix.length)
322
328
  else
323
329
  key = "*key error*" // should not happen
324
- if !options.deep {
325
- offset = key.indexOf("/", prefix.length)
326
- if offset >= 0 {
327
- key = key.substring(0, offset+1) // child directory name
328
- if !firstkids[key]
329
- firstkids[key] = key // first time we've seen this child directory
330
- else
331
- continue } } // ignore deeper child
332
330
  item = {
333
331
  key: key
334
332
  _id: obj.id
335
333
  _rev: obj.value.rev
336
334
  }
335
+ if !options.deep {
336
+ offset = key.indexOf("/", prelen) // start looking past options.prefix
337
+ if offset >= 0 { // directory child
338
+ if length(key) > offset+1 { // transitive child (not directory itself)
339
+ obj.doc = undefined // just record directory name
340
+ item._id = undefined
341
+ item._rev = undefined }
342
+ key = key.substring(0, offset+1) // child directory name
343
+ if !firstkids[key]
344
+ firstkids[key] = key // first time we've seen this child directory
345
+ else
346
+ continue // ignore deeper child
347
+ item.key = key } }
337
348
  if obj.doc { // docs were requested
338
349
  if obj.doc.content {
339
350
  item._md5 = HashMD5(obj.doc.content)
@@ -508,7 +519,7 @@ closure ListBrowserDBs(local pending, output, item) {
508
519
  */
509
520
 
510
521
  closure DbConnector(psm, local connClassID, connector, watch) {
511
- global()
522
+ global(js)
512
523
  connClassID = "PouchDB"
513
524
  connector = new(object, this)
514
525
  connector.psm = psm
@@ -741,6 +752,8 @@ closure DbConnector(psm, local connClassID, connector, watch) {
741
752
  //
742
753
  // Connect to a database, returning a PSM view.
743
754
  connector.connect = function connect(resID, service, args, local error, creds, db, rootpath, table) {
755
+ if !resID || args && !tuple(args)
756
+ return (list(Error("invalid arguments")))
744
757
  `(error, creds) = connector.access(resID)
745
758
  if !error
746
759
  `(error, db) = PouchDB(creds)
@@ -751,7 +764,7 @@ closure DbConnector(psm, local connClassID, connector, watch) {
751
764
  else if service == "NideFS" {
752
765
  rootpath = args.0 // connect to a NideFS filesystem
753
766
  if !rootpath
754
- return (Error("mandatory rootpath missing"))
767
+ return (list(Error("mandatory rootpath missing")))
755
768
  `(error, table) = db.table(rootpath)
756
769
  if error
757
770
  list(error)
@@ -766,7 +766,7 @@ function fimaInit(local manifest) {
766
766
 
767
767
  manifest = `(FileManager, fimaStore, fimaInit)
768
768
 
769
- Naan.module.build(module.id, "fileManager", function(modobj, compobj) {
769
+ Naan.module.build(module.id, "file_manager", function(modobj, compobj) {
770
770
  require("./storage.nlg")
771
771
  commonWatching = require("../common/watching.nlg")
772
772
  compobj.manifest = manifest
@@ -260,6 +260,16 @@ closure MakePSMutil(fs, local util) {
260
260
 
261
261
  util.deepcopy = closure deepcopy(inpath, sources, outfs, outpath, options, callback,
262
262
  local errors, copies, fsopt, folders) {
263
+
264
+ // fixpath
265
+ // Fix the path to match the destination.
266
+ function fixpath(path) {
267
+ if fs.path.sep == outfs.path.sep
268
+ path
269
+ else
270
+ path.replaceAll(fs.path.sep, outfs.path.sep)
271
+ }
272
+
263
273
  if !callback
264
274
  return (syncAdapter(deepcopy, inpath, sources, outfs, outpath, options))
265
275
  if sources && !array(sources) || !string(inpath) || !string(outpath)
@@ -281,7 +291,7 @@ closure MakePSMutil(fs, local util) {
281
291
  asyncArray(sources, 10, closure mklist(entry,
282
292
  local srcpath, destpath, epath, error, stat, folder, node) {
283
293
  srcpath = fs.path.join(inpath, entry)
284
- destpath = outfs.path.join(outpath, entry)
294
+ destpath = fixpath(outfs.path.join(outpath, entry))
285
295
  if destpath == "."
286
296
  destpath = "" // joined empty strings
287
297
  `(error, stat) = fs.info(srcpath, fsopt)