@naanlang/naan 1.0.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 (98) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +46 -0
  3. package/dist/env_web.js +431 -0
  4. package/dist/naan.min.js +73 -0
  5. package/frameworks/browser/browser.nlg +30 -0
  6. package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
  7. package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
  8. package/frameworks/browser/sworker.js +192 -0
  9. package/frameworks/browser/terminals.nlg +787 -0
  10. package/frameworks/browser/workers.nlg +386 -0
  11. package/frameworks/client/apiclient.nlg +183 -0
  12. package/frameworks/client/client.nlg +29 -0
  13. package/frameworks/client/psm_client.nlg +572 -0
  14. package/frameworks/common/common.nlg +464 -0
  15. package/frameworks/common/preferences.nlg +131 -0
  16. package/frameworks/common/repltools.nlg +100 -0
  17. package/frameworks/common/watching.nlg +97 -0
  18. package/frameworks/node/apiserver.nlg +399 -0
  19. package/frameworks/node/filesystem.nlg +1012 -0
  20. package/frameworks/node/gitter.nlg +234 -0
  21. package/frameworks/node/node.nlg +81 -0
  22. package/frameworks/node/psm_server.nlg +465 -0
  23. package/frameworks/node/worker.nlg +560 -0
  24. package/frameworks/project/build.nlg +1229 -0
  25. package/frameworks/project/jszip.js +30 -0
  26. package/frameworks/project/jszip.nlg +87 -0
  27. package/frameworks/project/proj_cliser.nlg +139 -0
  28. package/frameworks/project/proj_console.nlg +137 -0
  29. package/frameworks/project/proj_folder.nlg +143 -0
  30. package/frameworks/project/proj_lambda.nlg +137 -0
  31. package/frameworks/project/proj_static.nlg +138 -0
  32. package/frameworks/project/project.nlg +29 -0
  33. package/frameworks/project/projects.nlg +587 -0
  34. package/frameworks/project/uglifyjs.js +1 -0
  35. package/frameworks/project/uglifyjs.nlg +79 -0
  36. package/frameworks/running/debugcom.nlg +163 -0
  37. package/frameworks/running/debugnub.nlg +565 -0
  38. package/frameworks/running/debugutil.nlg +510 -0
  39. package/frameworks/running/executors.nlg +767 -0
  40. package/frameworks/running/running.nlg +151 -0
  41. package/frameworks/running/sourcecode.nlg +346 -0
  42. package/frameworks/running/taskexec.nlg +196 -0
  43. package/frameworks/storage/dbt_pouch.nlg +773 -0
  44. package/frameworks/storage/file_manager.nlg +772 -0
  45. package/frameworks/storage/psm.nlg +326 -0
  46. package/frameworks/storage/psm_dbtables.nlg +814 -0
  47. package/frameworks/storage/resources.nlg +445 -0
  48. package/frameworks/storage/storage.nlg +52 -0
  49. package/lib/browser/env_web.js +431 -0
  50. package/lib/browser/env_webworker.js +146 -0
  51. package/lib/browser/require.js +1653 -0
  52. package/lib/core/naanlib.js +73 -0
  53. package/lib/env_node.js +282 -0
  54. package/lib/env_nodeworker.js +165 -0
  55. package/lib/node_repl.js +25 -0
  56. package/lib/node_repl_init.nlg +52 -0
  57. package/package.json +13 -0
  58. package/plugins/gitClient/clientops.nlg +317 -0
  59. package/plugins/gitClient/gitClient.nlg +63 -0
  60. package/plugins/gitClient/gitutils.nlg +1331 -0
  61. package/plugins/gitClient/minimatch.js +945 -0
  62. package/plugins/gitClient/minimatch_license.txt +15 -0
  63. package/plugins/gitClient/minimatch_readme.md +208 -0
  64. package/plugins/nodeLib/nodeLib.nlg +26 -0
  65. package/plugins/nodeLib/node_cp.nlg +65 -0
  66. package/plugins/nodeLib/node_fs.nlg +137 -0
  67. package/plugins/nodeLib/node_https.nlg +172 -0
  68. package/plugins/nodeLib/node_util.nlg +180 -0
  69. package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
  70. package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
  71. package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
  72. package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
  73. package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
  74. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
  75. package/plugins/serviceAws/aws_cognito.nlg +76 -0
  76. package/plugins/serviceAws/aws_dynamo.nlg +509 -0
  77. package/plugins/serviceAws/aws_lambda.nlg +95 -0
  78. package/plugins/serviceAws/aws_s3.nlg +134 -0
  79. package/plugins/serviceAws/aws_utils.nlg +86 -0
  80. package/plugins/serviceAws/dbt_aws.nlg +769 -0
  81. package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
  82. package/plugins/serviceAws/psm_aws.nlg +258 -0
  83. package/plugins/serviceAws/serviceAws.nlg +38 -0
  84. package/plugins/serviceGitHub/gitops.nlg +522 -0
  85. package/plugins/serviceGitHub/psm_github.nlg +205 -0
  86. package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
  87. package/plugins/serviceGitLab/gitops.nlg +528 -0
  88. package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
  89. package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
  90. package/test/harness.nlg +312 -0
  91. package/test/node_test.js +33 -0
  92. package/test/test_01_core.nlg +768 -0
  93. package/test/test_02_context.nlg +269 -0
  94. package/test/test_03_jsinterop.nlg +137 -0
  95. package/test/test_04_numerics.nlg +450 -0
  96. package/test/test_05_strings.nlg +63 -0
  97. package/test/test_06_lingoparse.nlg +500 -0
  98. package/test/test_07_lingo.nlg +623 -0
@@ -0,0 +1,773 @@
1
+ /*
2
+ * dbt_pouch.nlg
3
+ * frameworks/storage
4
+ *
5
+ * Naan database tables using PouchDB. These functions implement tables on top of PouchDB for use
6
+ * in both the browser and NodeJS.
7
+ *
8
+ * column positioning: // // !
9
+ *
10
+ * Copyright (c) 2017-2021 by Richard C. Zulch
11
+ *
12
+ */
13
+
14
+
15
+ /*
16
+ * dapoTable
17
+ *
18
+ * Create a database table access object.
19
+ *
20
+ * If a callback function is specified then it is called when the operation completes with the
21
+ * usual (error, result) arguments. If a callback function is not specified then the call blocks
22
+ * until the operation is complete, and an (error, result) tuple is returned.
23
+ * Records are presented as dictionaries to the client of this interface. The client's data is
24
+ * stored under <record>.content, while the record key is available under <record>.key.
25
+ *
26
+ * Internals
27
+ * Each PouchDB database offers a single linear list of records. The key structuring scheme here
28
+ * allows us to segregate each database into independent folders sharing a single bucket.
29
+ * Within each database, a table instance has a path starting with "" representing the root of
30
+ * the database. All records in the database having that path as a key prefix belong to this table
31
+ * instance. A pseudo-folder structure is implemented using "/" (slash) characters as delimiters.
32
+ * PouchDB does conflict detection, so the _rev key in a document must match the current value
33
+ * in the database or an error is generated on update.
34
+ *
35
+ */
36
+
37
+ closure dapoTable(database, path, local table, prefix) {
38
+ table = new(object, this)
39
+ table.type = database.type
40
+ table.db = database
41
+ table.path = path
42
+ table.prefix = makePrefix(path)
43
+
44
+ // makePrefix
45
+ //
46
+ // Make a key prefix from a path.
47
+
48
+ function makePrefix(path) {
49
+ if path == ""
50
+ path // in the root
51
+ else if path.slice(-1) == "/"
52
+ path // already complete prefix
53
+ else
54
+ path.concat("/") // non-root prefix always ends in slash
55
+ }
56
+
57
+ // makeID
58
+ //
59
+ // Make an internal ID string from an external key, using the table's pre-determined prefix.
60
+
61
+ function makeID(key) {
62
+ if key eq `prefix
63
+ table.prefix
64
+ else if integer(key)
65
+ table.prefix.concat(tostring(key))
66
+ else if not string(key)
67
+ return (Error("invalid key type: ", typeof(key)))
68
+ else
69
+ table.prefix.concat(key)
70
+ }
71
+
72
+ // add
73
+ //
74
+ // Add a new record to the table with optional metadata. This will fail if the key already
75
+ // exists. Returns the new document record.
76
+
77
+ table.add = closure add(key, content, metadata, callback, local id, doc) {
78
+ if !callback
79
+ return (syncAdapter(add, key, content, metadata))
80
+ id = makeID(key)
81
+ if !string(id)
82
+ return (asyncResult(callback, id)) // error from makeID
83
+ if metadata && !dictionary(metadata)
84
+ return (asyncResult(callback, Error("invalid arguments")))
85
+ if !database.pouch
86
+ return (asyncResult(callback, Error("database closed")))
87
+ if metadata {
88
+ doc = new(metadata) // start with metadata
89
+ doc._rev = undefined } // _rev not allowed in metadata
90
+ else
91
+ doc = { }
92
+ if key eq `prefix
93
+ key = id
94
+ doc.key = key
95
+ doc._id = id
96
+ if content
97
+ doc.content = content
98
+ // ### encode large content to blob attachment here
99
+ database.pouch.put(doc, function(error, resp) {
100
+ if error
101
+ error = Error("add failed", error)
102
+ else {
103
+ doc._rev = resp.rev // update the original doc
104
+ if content {
105
+ doc._md5 = HashMD5(content)
106
+ doc._length = length(content) } }
107
+ callback(error, doc)
108
+ })
109
+ }
110
+
111
+ // update
112
+ //
113
+ // Update a record in the table using the document previously returned by add or get.
114
+ // This will fail if the record has been modified by someone else in the meantime.
115
+
116
+ table.update = closure update(doc, callback) {
117
+ if !callback
118
+ return (syncAdapter(update, doc))
119
+ if !doc._id || !doc._id.startsWith(table.prefix) || !doc._rev
120
+ return (asyncResult(callback, Error("invalid argument")))
121
+ if makeID(doc.key) != doc._id
122
+ return (asyncResult(callback, Error("key corrupted")))
123
+ if !database.pouch
124
+ return (asyncResult(callback, Error("database closed")))
125
+ doc = new(doc)
126
+ doc._md5 = undefined
127
+ doc._length = undefined
128
+ database.pouch.put(doc, function(error, resp) {
129
+ if error
130
+ error = Error("update failed", error)
131
+ else {
132
+ doc._rev = resp.rev // update the original doc
133
+ if doc.content {
134
+ doc._md5 = HashMD5(doc.content)
135
+ doc._length = length(doc.content) } }
136
+ callback(error, doc)
137
+ })
138
+ }
139
+
140
+ // delete
141
+ //
142
+ // Delete a record in the table using the document previously returned by add or get. This
143
+ // marks the record deleted but does not actually reclaim storage.
144
+
145
+ table.delete = closure delete(doc, callback) {
146
+ if !callback
147
+ return (syncAdapter(delete, doc))
148
+ if !doc._id || !doc._id.startsWith(table.prefix) || !doc._rev
149
+ return (asyncResult(callback, Error("invalid argument")))
150
+ if !database.pouch
151
+ return (asyncResult(callback, Error("database closed")))
152
+ doc = {
153
+ _id: doc._id
154
+ _rev: doc._rev
155
+ _deleted: true
156
+ }
157
+ database.pouch.put(doc, function(error, resp) {
158
+ if error
159
+ error = Error("delete failed", error)
160
+ else
161
+ resp = { ok: true }
162
+ callback(error, resp)
163
+ })
164
+ }
165
+
166
+ // get
167
+ //
168
+ // Get the record in the table stored in the specified key.
169
+
170
+ table.get = closure get(key, callback, local id) {
171
+ if !callback
172
+ return (syncAdapter(get, key))
173
+ id = makeID(key)
174
+ if !string(id)
175
+ return (asyncResult(callback, id)) // error from makeID
176
+ if !database.pouch
177
+ return (asyncResult(callback, Error("database closed")))
178
+ database.pouch.get(id, function(error, resp) {
179
+ if error
180
+ error = Error("get failed", error)
181
+ else {
182
+ resp.key = key // ensure doc's key matches id
183
+ if resp.content {
184
+ resp._md5 = HashMD5(resp.content)
185
+ resp._length = length(resp.content) }
186
+ }
187
+ callback(error, resp)
188
+ })
189
+ }
190
+
191
+ // md5
192
+ //
193
+ // Compute the MD5 hash of the content of the specified record if it exists. The result is "" if
194
+ // no content exists. The metadata is not part of the hash.
195
+
196
+ table.md5 = closure md5(key, callback) {
197
+ if !callback
198
+ return (syncAdapter(md5, key))
199
+ table.get(key, function(error, resp, local md5) {
200
+ if error
201
+ error = Error("get md5 failed", error, { status: error.status })
202
+ else if resp.content
203
+ md5 = HashMD5(resp.content)
204
+ else
205
+ md5 = ""
206
+ callback(error, md5)
207
+ })
208
+ }
209
+
210
+ // head
211
+ //
212
+ // Retrieve the head record for a key (i.e. everything but the content.)
213
+
214
+ table.head = closure head(key, callback) {
215
+ if !callback
216
+ return (syncAdapter(head, key))
217
+ table.get(key, function(error, resp) {
218
+ if error
219
+ error = Error("head failed", error, { status: error.status })
220
+ else
221
+ resp.content = undefined
222
+ callback(error, resp)
223
+ })
224
+ }
225
+
226
+ // copy
227
+ //
228
+ // Copy a record from a source key to a new record under a destination key.
229
+
230
+ table.copy = closure copy(srckey, destkey, callback, local srcid, destid) {
231
+ if !callback
232
+ return (syncAdapter(copy, srckey, destkey))
233
+ srcid = makeID(srckey)
234
+ if !string(srcid)
235
+ return (asyncResult(callback, srcid)) // error from makeID
236
+ destid = makeID(destkey)
237
+ if !string(destid)
238
+ return (asyncResult(callback, destid)) // error from makeID
239
+ if !database.pouch
240
+ return (asyncResult(callback, Error("database closed")))
241
+ database.pouch.get(srcid, function(error, resp, local doc) {
242
+ if error
243
+ return (callback(Error("copy read failed", {
244
+ source: srckey
245
+ destination: destkey
246
+ }, error)))
247
+ doc = resp
248
+ doc._id = destid
249
+ doc.key = destkey
250
+ doc._rev = undefined
251
+ database.pouch.put(resp, function(error, resp) {
252
+ if error
253
+ return (callback(Error("copy write failed", {
254
+ source: srckey
255
+ destination: destkey
256
+ }, error)))
257
+ doc._rev = resp.rev // return new doc
258
+ if doc.content {
259
+ doc._md5 = HashMD5(doc.content)
260
+ doc._length = length(doc.content) }
261
+ callback(false, doc)
262
+ })
263
+ })
264
+ }
265
+
266
+ // records
267
+ //
268
+ // List all records in the table with the following options:
269
+ // prefix: <string> -- only records with the specified key prefix are included
270
+ // deep: true|false -- if true, include transitive children; default false
271
+ // head: true|false -- if true, include doc element with header for each record
272
+ // get: true|false -- if true, include doc element with content for each record
273
+ //
274
+ // The return structure has the following form:
275
+ //
276
+ // {prefix : "joe/bill/",
277
+ // rows : [
278
+ // {
279
+ // key : "john",
280
+ // _modified : [Date Sat Apr 04 2020 17:05:38 GMT-0700 (Pacific Daylight Time)]
281
+ // _md5 : "55ff07fb005494c4bd42322d4845e858"
282
+ // _length : 156043
283
+ // [ doc | error: <document from "get" or "head"> | <error from get|head> ] },
284
+ // {
285
+ // key : "jill",
286
+ // _modified : [Date Sat Apr 22 2020 17:05:38 GMT-0700 (Pacific Daylight Time)]
287
+ // _md5 : "f53f07fb00d42322dc4bd42322d4845e"
288
+ // _length : 155
289
+ // [ doc : <document from "get" or "head"> ] } ] }
290
+ //
291
+ // All transitive nested children are included with the options.deep flag non-false. Otherwise
292
+ // only immediate children are listed, with directories at the end--and no metadata for them.
293
+ //
294
+
295
+ table.records = closure records(options, callback, local prefix, pouchOpts, output) {
296
+ if !callback
297
+ return (syncAdapter(records, options))
298
+ output = {
299
+ rows: [] }
300
+ if options.prefix {
301
+ if !string(options.prefix)
302
+ return (asyncResult(callback, Error("prefix option must be string")))
303
+ output.prefix = options.prefix
304
+ prefix = table.prefix.concat(makePrefix(options.prefix)) }
305
+ else
306
+ prefix = table.prefix
307
+ if !database.pouch
308
+ return (asyncResult(callback, Error("database closed")))
309
+ pouchOpts = {
310
+ startkey: prefix
311
+ endkey: prefix.concat("\ufff0") // recommended last character
312
+ }
313
+ if options.get || options.head
314
+ pouchOpts.include_docs = true
315
+ database.pouch.allDocs(pouchOpts, function(error, rowlist, local firstkids, obj, key, offset, item) {
316
+ if error
317
+ error = Error("allDocs failed", error)
318
+ else {
319
+ firstkids = { }
320
+ for obj in rowlist.rows {
321
+ if obj.id.startsWith(table.prefix)
322
+ key = obj.id.substring(table.prefix.length)
323
+ else
324
+ key = "*key error*" // should not happen
325
+ if !options.deep {
326
+ offset = key.indexOf("/", prefix.length)
327
+ if offset >= 0 {
328
+ key = key.substring(0, offset+1) // child directory name
329
+ if !firstkids[key]
330
+ firstkids[key] = key // first time we've seen this child directory
331
+ else
332
+ continue } } // ignore deeper child
333
+ item = {
334
+ key: key
335
+ _id: obj.id
336
+ _rev: obj.value.rev
337
+ }
338
+ if obj.doc {
339
+ if obj.doc.content {
340
+ item._md5 = HashMD5(obj.doc.content)
341
+ item._length = length(obj.doc.content) }
342
+ item.doc = obj.doc
343
+ if !options.get
344
+ item.doc.content = undefined }
345
+ output.rows.push(item) }
346
+ callback(false, output)
347
+ }
348
+ })
349
+ }
350
+
351
+ // finis
352
+
353
+ list(false, table)
354
+ };
355
+
356
+
357
+ /*
358
+ * PouchDB
359
+ *
360
+ * column positioning: // // !
361
+ * Make a database access object for a PouchDB database at the specified urlname. The database is
362
+ * created if it does not already exist. The urlname can be a string like:
363
+ * urlname - URL (e.g. "http://domain.com/dbname") - remote access
364
+ * - filepath (e.g. "/Users/richardz/dbname") - NodeJS filesystem access
365
+ * - string (e.g. "dbname") - local browser access
366
+ * or it can be a dictionary containing:
367
+ * urlname - as above
368
+ * authname - the authentication name for a URL
369
+ * authpw - the autentication password, required if authname is provided
370
+ * Return values from the various methods are typically a two-element tuple of (error, data), where
371
+ * at most one of the two values is non-false.
372
+ *
373
+ */
374
+
375
+ closure PouchDB(urlname, local database, options) {
376
+ options = {
377
+ revs_limit: 1 // number of revs to maintain for replication
378
+ auto_compaction: false } // don't compcat on every change
379
+ if !string(urlname) {
380
+ if urlname.authname
381
+ options.auth = {
382
+ username: urlname.authName
383
+ password: urlname.authPW
384
+ }
385
+ urlname = urlname.urlName
386
+ }
387
+ if !string(urlname) || urlname == ""
388
+ return (list(Error("invalid database url/path/name")))
389
+ database = new(object, this)
390
+ database.type = "PouchDB"
391
+ if RegExp("https?://").test(urlname)
392
+ database.name = urlname.match(RegExp("https?://[^?]*?([^/?]+)([?].*)*$")).1
393
+ else if urlname.indexOf("/") >= 0 {
394
+ options.prefix = jspath.dirname(urlname).concat("/")
395
+ database.name = jspath.basename(urlname) }
396
+ else
397
+ database.name = urlname
398
+ if !xobject(pouchdb)
399
+ return (list(Error("PouchDB module not found")))
400
+ database.pouch = xnew(pouchdb, database.name, options) // don't compact after every change
401
+ if !database.pouch
402
+ return (list(Error("could not access database", urlname)))
403
+
404
+ // isopen
405
+ //
406
+ // False iff the database is not open.
407
+
408
+ database.isopen = function isopen() {
409
+ !!database.pouch
410
+ }
411
+
412
+ // close
413
+ //
414
+ // Close the database.
415
+
416
+ database.close = function close(local pending) {
417
+ if !database.pouch
418
+ return (list(Error("database closed")))
419
+ pending = new(nonce)
420
+ database.pouch.close(function(error) {
421
+ database.pouch = false
422
+ pending.signal(list(error, { ok: true }))
423
+ })
424
+ pending.wait()
425
+ }
426
+
427
+ // info
428
+ //
429
+ // Return PouchDB-specif database info.
430
+
431
+ database.info = function info(local pending) {
432
+ if !database.pouch
433
+ return (list(Error("database closed")))
434
+ pending = new(nonce)
435
+ database.pouch.info(function(error, resp) {
436
+ resp = new(resp)
437
+ resp.type = database.type
438
+ pending.signal(list(error, resp))
439
+ })
440
+ pending.wait()
441
+ }
442
+
443
+ // destroy
444
+ //
445
+ // Delete the database and close it.
446
+
447
+ database.destroy = function destroy(local pending) {
448
+ if !database.pouch
449
+ return (list(Error("database closed")))
450
+ pending = new(nonce)
451
+ database.pouch.destroy(function(error, resp) {
452
+ database.pouch = false
453
+ pending.signal(list(error, resp))
454
+ })
455
+ pending.wait()
456
+ }
457
+
458
+ // table
459
+ //
460
+ // Return a table-access object for the table.
461
+
462
+ database.table = function table(path) {
463
+ if !database.pouch
464
+ return (list(Error("database closed")))
465
+ if !string(path)
466
+ return (list(Error("invalid table path")))
467
+ dapoTable(database, path)
468
+ }
469
+
470
+ // finis
471
+
472
+ list(false, database)
473
+ };
474
+
475
+
476
+ /*
477
+ * ListBrowserDBs
478
+ *
479
+ * List PouchDB instances in the browser.
480
+ *
481
+ */
482
+
483
+ closure ListBrowserDBs(local pending, output) {
484
+ if !indexedDB
485
+ return (list(Error("browser database unavailable")))
486
+ if !indexedDB.databases
487
+ return (list(Error("API for indexedDB.databases unavailable")))
488
+ pending = new(nonce)
489
+ output = []
490
+ indexedDB.databases().then(function(success) {
491
+ for item in success
492
+ if item.name.startsWith("_pouch_")
493
+ output.push(item.name.substring(7))
494
+ pending.signal(list(false, output))
495
+ }, function(failure) {
496
+ pending.signal(list(Error("can't list databases", failure)))
497
+ })
498
+ pending.wait()
499
+ };
500
+
501
+
502
+ /*
503
+ * DbConnector
504
+ *
505
+ * Make a PSM connector for PouchDB database tables.
506
+ *
507
+ */
508
+
509
+ closure DbConnector(psm, local connClassID, connector, watch) {
510
+ connClassID = "PouchDB"
511
+ connector = new(object, this)
512
+ connector.psm = psm
513
+ connector.vault = psm.vault(connClassID)
514
+ connector.label = "PouchDB"
515
+ watch = psm.watchable(connector)
516
+
517
+ // localDBcred
518
+ //
519
+ // True iff the specified credential dictionary is for a local browser DB.
520
+ function localDBcred(creds) {
521
+ !creds.authName && creds.urlName.indexOf("://") < 0
522
+ }
523
+
524
+ // uifields
525
+ //
526
+ // Report the UI fields we need to add a resource
527
+ connector.uifields = function uifields() {
528
+ [{
529
+ label: "URL | Path | Name"
530
+ placeholder: "http://domain.com/dbname | /Host-path/dbname | dbname"
531
+ key: "urlName" },
532
+ {
533
+ label: "Auth Name"
534
+ placeholder: "account name"
535
+ key: "authName" },
536
+ {
537
+ label: "Auth Password"
538
+ placeholder: "account password"
539
+ key: "authPW"
540
+ type: "password" },
541
+ ]
542
+ }
543
+
544
+ // hashResourceID
545
+ //
546
+ // Make a stable hash based on the resource credentials that we can persist or share between
547
+ // different domains accessing the same resource.
548
+ connector.hashResourceID = function hashResourceID(resource, local ident) {
549
+ ident = resource.urlName
550
+ if resource.label
551
+ ident = ident.concat(resource.label)
552
+ HashSHA256(ident)
553
+ }
554
+
555
+ // findResource
556
+ //
557
+ // Find a resource with the specified contents, returning the resID or false. The loose criteria
558
+ // is: if added, would the specified contents be redundant to an existing resource?
559
+ connector.findResource = function findResource(resource, local resID, creds) {
560
+ for resID in listResources().1 {
561
+ creds = access(resID).1
562
+ if resource.urlName == creds.urlName
563
+ && (!resource.label || resource.label == creds.label)
564
+ && resource.authName == creds.authName
565
+ return (resID)
566
+ }
567
+ false
568
+ }
569
+
570
+ // addResource
571
+ //
572
+ // Add credentials for a named resource. The resource is a dictionary comprising these keys:
573
+ // label - [optional] label we use for this resource
574
+ // urlName - URL (e.g. "http://domain.com/dbname")
575
+ // - filepath (e.g. "/Users/richardz/dbname") - NodeJS filesystem access
576
+ // - string (e.g. "dbname") - local browser access
577
+ // authName - [optional] the authentication name for a URL
578
+ // authPW - [optional] the autentication password, required if authname is provided
579
+ // hidden - [optional] don't enumerate to user
580
+ // locked - [optional] don't allow user delete
581
+ // existng - [optional] true to enable using existing database
582
+ // The return value is (error, result) tuple. Error, if non-false, is a dictionary of field
583
+ // keys and error diagnostic strings. The special key "label" refers to the label string
584
+ connector.addResource = function addResource(resource, local errors, creds, original, error, resID, data) {
585
+ function badField(str) { !string(str) || str.trim().length == 0 }
586
+ creds = { urlName: resource.urlName }
587
+ errors = { }
588
+ if resource.label
589
+ creds.label = resource.label
590
+ else
591
+ creds.label = resource.urlName
592
+ if badField(creds.label)
593
+ errors.label = "invalid label"
594
+ if badField(resource.urlName)
595
+ errors.urlName = "required field"
596
+ else if !resource.existing {
597
+ original = ListBrowserDBs().1
598
+ if original && original.indexOf(resource.urlName) >= 0
599
+ errors.urlName = "database already exists" // database exists
600
+ }
601
+ if !badField(resource.authName) {
602
+ creds.authName = resource.authName
603
+ creds.authPW = resource.authPW
604
+ if badField(resource.authPW)
605
+ errors.authPW = "required if Auth Name specified" }
606
+ if length(errors) > 0
607
+ error = errors
608
+ else if localDBcred(creds)
609
+ `(error, data) = PouchDB(creds) // create the database now
610
+ if !error {
611
+ if resource.hidden
612
+ creds.hidden = resource.hidden
613
+ if resource.locked
614
+ creds.locked = resource.locked
615
+ resID = hashResourceID(resource)
616
+ `(error, data) = connector.vault.addResource(resID, creds)
617
+ if data {
618
+ watch.notify(connClassID, { added: [resID] })
619
+ data = resID }
620
+ }
621
+ list(error, data)
622
+ }
623
+
624
+ // getResource
625
+ //
626
+ // Get credentials for a named resource, matching the dictionary semantics of addResource. The
627
+ // result is a standard result tuple.
628
+ connector.getResource = function getResource(resID, local error, creds, resource) {
629
+ `(error, creds) = connector.vault.accessResource(resID)
630
+ if error
631
+ return (list(error))
632
+ resource = {
633
+ classID: connClassID
634
+ label: creds.label
635
+ }
636
+ if string(creds.urlName)
637
+ resource.urlName = creds.urlName
638
+ if string(creds.authName)
639
+ resource.authName = creds.authName
640
+ if string(creds.authPW)
641
+ resource.authPW = creds.authPW
642
+ list(false, resource)
643
+ }
644
+
645
+ // deleteResource
646
+ //
647
+ // Delete credentials for a resource, which includes the database itself if local.
648
+ connector.deleteResource = function deleteResource(resID, local error, creds, db) {
649
+ `(error, creds) = access(resID)
650
+ if error || creds.locked
651
+ return (list(Error("database protected", resID)))
652
+ if !error && localDBcred(creds) {
653
+ if !js.w.confirm("Really delete PouchDB ".concat(creds.urlName, " permanently?"))
654
+ return (list(Error("delete canceled"))) // ### temporary until debugged
655
+ `(error, db) = PouchDB(creds)
656
+ if db
657
+ db.destroy()
658
+ }
659
+ connector.vault.deleteResource(resID)
660
+ watch.notify(connClassID, { deleted: [resID] })
661
+ list(false, { ok: true})
662
+ }
663
+
664
+ // listResources
665
+ //
666
+ // List saved resources in our database
667
+ connector.listResources = function listResources(local error, output, original, resID, creds, ddex) {
668
+ `(error, output) = connector.vault.listResources()
669
+ if error
670
+ return (list(error)) // can't access our saved resources
671
+ original = ListBrowserDBs().1
672
+ if original {
673
+ for resID in output {
674
+ `(error, creds) = connector.vault.accessResource(resID)
675
+ if !error && localDBcred(creds) && (ddex = original.indexOf(creds.urlName)) >= 0
676
+ original.splice(ddex, 1) // remove local databases we have credential for
677
+ }
678
+ output = original.concat(output) // add unreferenced local DBs
679
+ }
680
+ list(false, output)
681
+ }
682
+
683
+ // info
684
+ //
685
+ // Return info for a resource.
686
+ connector.info = function info(resID, local info, error, creds) {
687
+ info = {
688
+ classID: connClassID
689
+ type: "PouchDB"
690
+ services: ["NideDB", "NideFS"]
691
+ }
692
+ `(error, creds) = connector.vault.accessResource(resID)
693
+ if creds {
694
+ if creds.label
695
+ info.name = creds.label
696
+ if localDBcred(creds)
697
+ info.type = "browser storage"
698
+ if creds.locked
699
+ info.locked = true
700
+ if creds.hidden
701
+ info.hidden = true
702
+ info.where = creds.urlName }
703
+ else {
704
+ info.where = "internal"
705
+ info.hidden = true
706
+ info.locked = true }
707
+ list(false, info)
708
+ }
709
+
710
+ // access
711
+ //
712
+ // Return an PouchDB access object, which is the credentials to open a database.
713
+ connector.access = function access(resID, local error, creds) {
714
+ `(error, creds) = connector.vault.accessResource(resID)
715
+ if error && ListBrowserDBs().1.indexOf(resID) >= 0 {
716
+ error = false
717
+ creds = resID
718
+ }
719
+ list(error, creds)
720
+ }
721
+
722
+ // connect
723
+ //
724
+ // Connect to a database, returning a PSM view.
725
+ connector.connect = function connect(resID, service, args, local error, creds, db, rootpath, table) {
726
+ `(error, creds) = connector.access(resID)
727
+ if !error
728
+ `(error, db) = PouchDB(creds)
729
+ if error
730
+ list(error)
731
+ else if service == "NideDB"
732
+ list(false, db)
733
+ else if service == "NideFS" {
734
+ rootpath = args.0 // connect to a NideFS filesystem
735
+ if !rootpath
736
+ return (Error("mandatory rootpath missing"))
737
+ `(error, table) = db.table(rootpath)
738
+ if error
739
+ list(error)
740
+ else
741
+ FSview(table, rootpath)
742
+ } else
743
+ list(Error("unsupported service", service))
744
+ }
745
+
746
+ // finis
747
+
748
+ psm.register(connClassID, connector)
749
+ connector
750
+ };
751
+
752
+
753
+ /*
754
+ * dapoInit
755
+ *
756
+ * Initialize the Database module.
757
+ *
758
+ */
759
+
760
+ function dapoInit(local manifest) {
761
+
762
+ manifest = `(dapoTable, PouchDB, ListBrowserDBs, DbConnector, dapoInit)
763
+
764
+ Naan.module.build(module.id, "dbt_pouch", function(modobj, compobj) {
765
+ require("./storage.nlg")
766
+ require("./psm_dbtables.nlg")
767
+ compobj.manifest = manifest
768
+ modobj.exports.PouchDB = PouchDB
769
+ modobj.exports.ListBrowserDBs = ListBrowserDBs
770
+ modobj.exports.DbConnector = DbConnector
771
+ })
772
+
773
+ } ();