@naanlang/naan 1.0.2 → 1.0.5

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 +10 -8
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +172 -22
  5. package/dist/naan.min.js +6 -6
  6. package/frameworks/browser/https_request.nlg +120 -0
  7. package/frameworks/browser/sworker.js +299 -91
  8. package/frameworks/browser/terminals.nlg +28 -13
  9. package/frameworks/browser/workers.nlg +27 -12
  10. package/frameworks/client/apiclient.nlg +17 -4
  11. package/frameworks/client/psm_client.nlg +184 -81
  12. package/frameworks/common/common.nlg +105 -25
  13. package/frameworks/common/preferences.nlg +1 -0
  14. package/frameworks/common/watching.nlg +1 -0
  15. package/frameworks/node/apiserver.nlg +31 -15
  16. package/frameworks/node/filesystem.nlg +230 -46
  17. package/frameworks/node/gitter.nlg +27 -6
  18. package/frameworks/node/https_request.nlg +119 -0
  19. package/frameworks/node/node.nlg +1 -0
  20. package/frameworks/node/psm_server.nlg +113 -37
  21. package/frameworks/project/build.nlg +40 -23
  22. package/frameworks/project/proj_cliser.nlg +11 -3
  23. package/frameworks/project/proj_console.nlg +13 -4
  24. package/frameworks/project/proj_folder.nlg +10 -1
  25. package/frameworks/project/proj_lambda.nlg +13 -4
  26. package/frameworks/project/proj_static.nlg +11 -3
  27. package/frameworks/project/projects.nlg +128 -40
  28. package/frameworks/running/debugnub.nlg +2 -5
  29. package/frameworks/running/executors.nlg +1 -0
  30. package/frameworks/storage/csv.nlg +120 -0
  31. package/frameworks/storage/dbt_pouch.nlg +41 -25
  32. package/frameworks/storage/psm.nlg +108 -28
  33. package/frameworks/storage/psm_dbtables.nlg +7 -3
  34. package/frameworks/storage/resources.nlg +30 -2
  35. package/lib/browser/env_web.js +170 -20
  36. package/lib/browser/env_webworker.js +1 -1
  37. package/lib/browser/require.js +1 -1
  38. package/lib/core/naanlib.js +6 -6
  39. package/package.json +2 -1
  40. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
  41. package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
  42. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +8 -7
  43. package/plugins/serviceAws/aws_dynamo.nlg +715 -148
  44. package/plugins/serviceAws/aws_dynextra.nlg +294 -0
  45. package/plugins/serviceAws/aws_lambda.nlg +11 -4
  46. package/plugins/serviceAws/aws_s3.nlg +44 -14
  47. package/plugins/serviceAws/dbt_aws.nlg +58 -45
  48. package/plugins/serviceAws/psm_aws.nlg +61 -40
  49. package/plugins/serviceAws/serviceAws.nlg +7 -4
  50. package/plugins/serviceGitHub/psm_github.nlg +41 -25
  51. package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
  52. package/test/harness.nlg +3 -1
  53. package/test/test_01_core.nlg +5 -5
  54. package/test/test_02_context.nlg +4 -4
  55. package/test/test_03_jsinterop.nlg +9 -3
  56. package/plugins/serviceAws/aws_cognito.nlg +0 -76
@@ -0,0 +1,294 @@
1
+ /*
2
+ * aws_dynextra.nlg
3
+ * serviceAws
4
+ *
5
+ * Extra DynamoDB services for AWS.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2022 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * dynexBatchGetRecords
16
+ *
17
+ * Retrieve records from one or more tables using arrays of keys. The results are put back in a
18
+ * dictionary with the same shape as the source. The result is a standard `(error, data) tuple.
19
+ * The request/response layout is as follows:
20
+ * [
21
+ * {
22
+ * table: <DynaTable object> // table to act upon
23
+ * keys: [<hash-range tuple>] // records to get
24
+ * data: [<retrieved records>] // received records in reply
25
+ * },
26
+ * { <next-table> }
27
+ * ]
28
+ * The operations are processed in parallel batches, and the final result is a standard result
29
+ * tuple `(error, result). An error is only reported if we are unable to complete all operations.
30
+ *
31
+ */
32
+
33
+ function dynexBatchGetRecords(dyna, request,
34
+ local index, titem, tname, error, batches, activity, params, batchsize, primary) {
35
+ if !dyna.aws
36
+ return (list(Error("DynaTable.batchGetRecords: not logged into AWS")))
37
+ request = new(request) // allow local modifications
38
+ index = { }
39
+ for titem in request { // preflight all tables
40
+ tname = titem.table.tablename
41
+ if index[tname]
42
+ return (Error("DynaTable.batchGetRecords: duplicate table in request:", tname))
43
+ `(error) = titem.table.info()
44
+ if error
45
+ return (Error("DynaTable.batchGetRecords: table", tname, ":", error))
46
+ titem.data = []
47
+ index[tname] = titem
48
+ }
49
+ batches = []
50
+ activity = true
51
+ while activity { // loop until requests empty
52
+ params = {
53
+ RequestItems: { }
54
+ }
55
+ batchsize = 0
56
+ while :fillbatch activity { // loop until batch full
57
+ activity = false
58
+ for `(tname, titem) in index {
59
+ if titem.keys.length > 0 {
60
+ primary = titem.table.makeDynKey(titem.keys.unshift())
61
+ if params.RequestItems[tname].Keys
62
+ params.RequestItems[tname].Keys.push(primary)
63
+ else
64
+ params.RequestItems[tname] = {
65
+ Keys: [primary]
66
+ }
67
+ activity = true
68
+ if ++batchsize >= 100
69
+ break :fillbatch // our batch is full
70
+ } else
71
+ item.keys = undefined // clear for return result
72
+ }
73
+ }
74
+ if batchsize > 0
75
+ batches.push(params)
76
+ }
77
+ asyncArray(batches, 10, closure bgr(params, local delayer, pending, err, response, table) {
78
+ delayer = dyna.conv.genBackoffDelayer()
79
+ loop {
80
+ pending = new(nonce)
81
+ dyna.aws.batchGetItem(params, function(err, data) {
82
+ if err
83
+ debuglog("aws.batchGetItem failed", err)
84
+ pending.signal(err, data)
85
+ })
86
+ `(err, response) = pending.wait()
87
+ for `(tname, table) in response.Responses
88
+ for titem in table
89
+ index[tname].data.push(dyna.conv.recordDynToNaan(titem))
90
+ if response.UnprocessedKeys
91
+ params.RequestItems = response.UnprocessedKeys // still more to do
92
+ else
93
+ break // total success or failure
94
+ if delayer() > dyna.timeout {
95
+ if !error // too slow
96
+ error = err
97
+ break
98
+ }
99
+ }
100
+ }).wait()
101
+ if error
102
+ list(error)
103
+ else
104
+ list(false, request)
105
+ };
106
+
107
+
108
+ /*
109
+ * dynexBatchWriteRecords
110
+ *
111
+ * Put arrays of records into one or more tables, replacing or deleting any existing records with
112
+ * the same keys. This returns arrays of results coresponding to the input records, where each
113
+ * is a standard `(error, data) tuple. The request layout is as follows:
114
+ * [
115
+ * {
116
+ * table: <DynaTable object> // table to act upon
117
+ * delete: [<key tuples/records>] // records to delete (needing only keys)
118
+ * put: [<records>] // records to put
119
+ * },
120
+ * { <next-table> }
121
+ * ]
122
+ * The operations are processed in parallel batches, and the final result is a standard result
123
+ * tuple `(error, result). An error is only reported if we are unable to complete all operations.
124
+ *
125
+ */
126
+
127
+ closure dynexBatchWriteRecords(dyna, request, local batches, error, activity) {
128
+ if !dyna.aws
129
+ return (list(Error("DynaTable.batchWriteRecords: not logged into AWS")))
130
+
131
+ //
132
+ // addOp
133
+ // Add a put or delete operation to the parameters
134
+ //
135
+ function addOp(params, table, del, record, local request) {
136
+ if del { // deleting a record
137
+ if tuple(record)
138
+ record = table.makeDynKey(record) // caller provided hash/range key pair
139
+ else
140
+ request = { DeleteRequest: { Key: dyna.conv.recordNaanToDyn(record) } }
141
+ }
142
+ else // putting a record
143
+ request = { PutRequest: { Item: dyna.conv.recordNaanToDyn(record) } }
144
+ if params.RequestItems[table.tablename]
145
+ params.RequestItems[table.tablename].push(request)
146
+ else
147
+ params.RequestItems[table.tablename] = [request]
148
+ }
149
+
150
+ // transform into batches
151
+ //
152
+ // This is tricky because it wants each batch to use multiple tables for maximum parallelism,
153
+ // just in case AWS isn't already optimizing this. It takes a request from each table and
154
+ // combines them into a single batch repeatedly, until the batch is full or the tables in the
155
+ // request have no more entries.
156
+ //
157
+ request = new(request) // allow local modifications
158
+ batches = []
159
+ activity = true
160
+ while activity
161
+ let (params, batchsize, table) {
162
+ params = {
163
+ RequestItems: { }
164
+ }
165
+ batchsize = 0
166
+ while :fillbatch activity { // loop until no activity or batch full
167
+ activity = false
168
+ for table in request {
169
+ if table.delete.length > 0
170
+ addOp(params, table, true, table.delete.unshift())
171
+ else if table.put.length > 0
172
+ addOp(params, table, false, table.put.unshift())
173
+ else
174
+ continue
175
+ activity = true
176
+ if ++batchsize >= 25
177
+ break :fillbatch // our batch is full
178
+ }
179
+ }
180
+ if batchsize > 0
181
+ batches.push(params)
182
+ } ()
183
+ asyncArray(batches, 10, closure bwr(params, local delayer, pending, err, response) {
184
+ delayer = dyna.conv.genBackoffDelayer()
185
+ loop {
186
+ pending = new(nonce)
187
+ dyna.aws.batchWriteItem(params, function(err, data) {
188
+ if err
189
+ debuglog("aws.batchWriteItem failed", err)
190
+ pending.signal(err, data)
191
+ })
192
+ `(err, response) = pending.wait()
193
+ if response.UnprocessedKeys
194
+ params.RequestItems = response.UnprocessedKeys // still more to do
195
+ else
196
+ break // total success or failure
197
+ if delayer() > dyna.timeout {
198
+ if !error // too slow
199
+ error = err
200
+ break
201
+ }
202
+ }
203
+ }).wait()
204
+ if error
205
+ list(error)
206
+ else
207
+ list(false, { ok: true })
208
+ };
209
+
210
+
211
+
212
+ /*
213
+ * dynexTransactGetRecords
214
+ *
215
+ * Retrieve records from one or more tables using arrays of keys as a single transaction. The
216
+ * results are put back in an array with the same shape as the source. The request/response
217
+ * layout is as follows:
218
+ * [
219
+ * {
220
+ * table: <DynaTable object> // table to act upon
221
+ * keys: [<hash-range tuple>] // records to get
222
+ * data: [<retrieved records>] // received records in reply
223
+ * },
224
+ * { <next-table> }
225
+ * ]
226
+ * This returns a standard result tuple `(error, result). An error is only reported if we are
227
+ * unable to complete all operations.
228
+ *
229
+ */
230
+
231
+ closure dynexTransactGetRecords(dyna, request,
232
+ local index, titem, tname, error, params, pending, response) {
233
+ if !dyna.aws
234
+ return (list(Error("DynaTable.transactGetRecords: not logged into AWS")))
235
+ request = new(request) // allow local modifications
236
+ index = { }
237
+ for titem in request { // preflight all tables
238
+ tname = titem.table.tablename
239
+ if index[tname]
240
+ return (Error("DynaTable.batchGetRecords: duplicate table in request:", tname))
241
+ `(error) = titem.table.info()
242
+ if error
243
+ return (Error("DynaTable.batchGetRecords: table", tname, ":", error))
244
+ titem.data = []
245
+ index[tname] = titem
246
+ }
247
+ params = {
248
+ TransactItems: []
249
+ }
250
+ for `(tname, titem) in index {
251
+ while titem.keys.length > 0 {
252
+ params.TransactItems.push({
253
+ Get: {
254
+ TableName: tname
255
+ Key: makeDynKey(titem.keys.unshift())
256
+ }
257
+ })
258
+ }
259
+ titem.keys = undefined
260
+ }
261
+ pending = new(nonce)
262
+ dyna.aws.transactGetItems(params, function(err, data) {
263
+ if err
264
+ debuglog("aws.transactGetItems failed", err)
265
+ pending.signal(err, data)
266
+ })
267
+ `(error, response) = pending.wait()
268
+ if error
269
+ list(error)
270
+ else {
271
+ for titem in response.Responses {
272
+ tname = titem.Item.*.0
273
+ index[tname].data.push(dyna.conv.recordDynToNaan(titem.Item[tname]))
274
+ }
275
+ list(false, request)
276
+ }
277
+ };
278
+
279
+
280
+ /*
281
+ * dynexInit
282
+ *
283
+ * Initialize the component.
284
+ *
285
+ */
286
+
287
+ function dynexInit(local manifest) {
288
+ manifest = `(dynexBatchGetRecords, dynexBatchWriteRecords, dynexTransactGetRecords, dynexInit)
289
+
290
+ Naan.module.build(module.id, "aws_dynextra", function(modobj, compobj) {
291
+ compobj.manifest = manifest
292
+ require("./serviceAws.nlg")
293
+ })
294
+ } ();
@@ -26,10 +26,13 @@ closure Lambda(local lamb) {
26
26
  //
27
27
  lamb.login = closure login(creds) {
28
28
  lamb.aws = xnew(awsSDK.Lambda, {
29
- apiVersion: "2015-03-31",
30
- accessKeyId: creds.keyID,
31
- secretAccessKey: creds.keySecret,
32
- region: creds.region })
29
+ region: creds.region
30
+ credentials: {
31
+ accessKeyId: creds.keyID
32
+ secretAccessKey: creds.keySecret
33
+ }
34
+ })
35
+ list(false, { ok: true })
33
36
  }
34
37
 
35
38
  //
@@ -55,8 +58,12 @@ closure Lambda(local lamb) {
55
58
  //
56
59
  // updateFunctionCode
57
60
  //
61
+ // Note that as of 2022-05-17 AWS SDK v3 requires that the zipfile be a Uint8Array, not an
62
+ // ArrayBuffer or anything simple like that. What a #$*#(! waste of time.
63
+ //
58
64
  lamb.updateFunctionCode = closure updateFunctionCode(name, zipfile, local params, pending) {
59
65
  params = {
66
+ Architectures: [ "x86_64" ]
60
67
  FunctionName: name
61
68
  ZipFile: zipfile
62
69
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2021 by Richard C. Zulch
9
+ * Copyright (c) 2020-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -81,15 +81,17 @@ closure S3(local s3) {
81
81
  s3.login = closure login(creds, local params) {
82
82
  if !awsSDK
83
83
  return (list(Error("S3.login: no AWS SDK available")))
84
- params = {
85
- apiVersion: "2012-08-10"
86
- }
84
+ params = { }
87
85
  if creds {
88
- params.accessKeyId = creds.keyID,
89
- params.secretAccessKey = creds.keySecret,
90
86
  params.region = creds.region
87
+ params.credentials = {
88
+ accessKeyId: creds.keyID
89
+ secretAccessKey: creds.keySecret
90
+ }
91
91
  }
92
92
  s3.aws = xnew(awsSDK.S3, params)
93
+ if awsSDK.S3Client
94
+ s3.client = xnew(awsSDK.S3Client, params)
93
95
  `(false, { ok: true })
94
96
  }
95
97
 
@@ -99,16 +101,44 @@ closure S3(local s3) {
99
101
  // Return a new bucket access object for the table of the specified name. The bucket may not
100
102
  // yet exist; see S3bucket for methods.
101
103
  //
102
- // parameters:
104
+ s3.bucket = closure bucket(name) {
105
+ S3bucket(s3, name)
106
+ }
107
+
108
+ //
109
+ // getSignedURL
110
+ //
111
+ // This obtains a pre-signed URL for the specified command and its parameters. Options control
112
+ // how the URL is generated.
113
+ //
114
+ // Options:
103
115
  // {
104
- // hash: <name> // primary hash key attribute name (required)
105
- // hashType: <type> // string | numeric (required)
106
- // range: <name> // primary range key attribute name (optional)
107
- // rangeType: <name> // string | numeric (required if range specified)
116
+ // expiresIn: <seconds> // default expiration is 900 (15 minutes)
108
117
  // }
109
118
  //
110
- s3.bucket = closure bucket(name) {
111
- S3bucket(s3, name)
119
+ s3.getSignedURL = closure getSignedURL(cmdname, cmdparams, local command, options, pending) {
120
+ if s3.client {
121
+ if cmdname == "putObject"
122
+ cmdname = "PutObjectCommand"
123
+ else if cmdname == "getObject"
124
+ cmdname = "GetObjectCommand"
125
+ else
126
+ return (list(Error("S3bucket.getSignedURL: command not present:", cmdname)))
127
+ if cmdparams.Expires {
128
+ cmdparams = new(cmdparams)
129
+ options.expiresIn = cmdparams.Expires
130
+ cmdparams.Expires = undefined
131
+ } else
132
+ options = { }
133
+ command = xnew(awsSDK.s3Commands[cmdname], cmdparams)
134
+ await(awsSDK.getSignedUrl(s3.client, command, options))
135
+ } else {
136
+ pending = new(nonce)
137
+ s3.aws.getSignedUrl(cmdname, cmdparams, function(error, data) {
138
+ pending.signal(list(error, data))
139
+ })
140
+ pending.wait()
141
+ }
112
142
  }
113
143
 
114
144
  // finis
@@ -126,7 +156,7 @@ closure S3(local s3) {
126
156
  function s3Init(local manifest) {
127
157
  manifest = `(S3bucket, S3, s3Init)
128
158
 
129
- Naan.module.build(module.id, "S3", function(modobj, compobj) {
159
+ Naan.module.build(module.id, "aws_s3", function(modobj, compobj) {
130
160
  require("./serviceAws.nlg")
131
161
  compobj.manifest = manifest
132
162
  modobj.exports.S3 = S3
@@ -6,23 +6,11 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2021 by Richard C. Zulch
9
+ * Copyright (c) 2020-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
13
13
 
14
- /*
15
- * dawsValidateName
16
- *
17
- * True iff the specified name is valid here.
18
- *
19
- */
20
-
21
- function dawsValidateName(key, path) {
22
-
23
- };
24
-
25
-
26
14
  /*
27
15
  * dawsBucket
28
16
  *
@@ -33,12 +21,12 @@ function dawsValidateName(key, path) {
33
21
  *
34
22
  */
35
23
 
36
- closure dawsBucket(s3, local bucket) {
24
+ closure dawsBucket(s3b, local bucket, s3) {
37
25
  bucket = new(object, this)
38
- bucket.s3 = s3
39
- bucket.name = s3.config.params.Bucket
40
- bucket.region = s3.config.region
41
- if not s3 || !string(bucket.region) || bucket.region.length == 0 || !string(bucket.name) || bucket.bucket.length == 0
26
+ s3 = s3b.s3
27
+ bucket.name = s3b.bucketName
28
+ bucket.region = s3b.region
29
+ if !s3 || !string(bucket.region) || bucket.region.length == 0 || !string(bucket.name) || bucket.bucket.length == 0
42
30
  return (list(Error("invalid S3 parameter")))
43
31
 
44
32
  // asyncResult
@@ -56,7 +44,7 @@ closure dawsBucket(s3, local bucket) {
56
44
  // that the bucket is still open before beginning (or continuing) any operations.
57
45
 
58
46
  bucket.close = function close() {
59
- bucket.s3 = false // just remove access
47
+ bucket.s3 = s3 = false // just remove access
60
48
  list(false, { })
61
49
  }
62
50
 
@@ -70,6 +58,7 @@ closure dawsBucket(s3, local bucket) {
70
58
  if !s3
71
59
  return (asyncResult(callback, Error("database closed")))
72
60
  params = {
61
+ Bucket: bucket.name
73
62
  Key: key
74
63
  }
75
64
 
@@ -100,6 +89,7 @@ closure dawsBucket(s3, local bucket) {
100
89
  if !s3
101
90
  return (asyncResult(callback, Error("database closed")))
102
91
  params = {
92
+ Bucket: bucket.name
103
93
  Key: key
104
94
  }
105
95
  if data {
@@ -110,6 +100,7 @@ closure dawsBucket(s3, local bucket) {
110
100
  mimeType = {
111
101
  "css": "text/css",
112
102
  "csv": "text/csv",
103
+ "gif": "image/gif",
113
104
  "htm": "text/html",
114
105
  "html": "text/html",
115
106
  "ico": "image/x-icon",
@@ -170,6 +161,7 @@ closure dawsBucket(s3, local bucket) {
170
161
  if !s3
171
162
  return (asyncResult(callback, Error("database closed")))
172
163
  params = {
164
+ Bucket: bucket.name
173
165
  Key: key
174
166
  }
175
167
  s3.deleteObject(params, function(error, resp) {
@@ -251,7 +243,9 @@ closure dawsBucket(s3, local bucket) {
251
243
 
252
244
  if !s3
253
245
  return (asyncResult(callback, Error("database closed")))
254
- params = { }
246
+ params = {
247
+ Bucket: bucket.name
248
+ }
255
249
  if string(prefix)
256
250
  params.Prefix = prefix
257
251
  if onelevel
@@ -259,6 +253,8 @@ closure dawsBucket(s3, local bucket) {
259
253
  listOne()
260
254
  }
261
255
 
256
+ // finis
257
+
262
258
  list(false, bucket)
263
259
 
264
260
  };
@@ -400,7 +396,7 @@ closure dawsTable(database, path, local table) {
400
396
  metadata = metadata2s3(metadata) // filter out any invalid metadata
401
397
  database.bucket.put(id, content, metadata, function(error, resp) {
402
398
  if error
403
- error = Error("add failed", error, id, { status: error.statusCode })
399
+ error = Error("add failed", error, id, { status: error.$metadata.httpStatusCode })
404
400
  else {
405
401
  doc.md5 = resp.md5 // update the original doc
406
402
  doc.length = resp.length }
@@ -420,7 +416,7 @@ closure dawsTable(database, path, local table) {
420
416
  metadata = metadata2s3(doc)
421
417
  database.bucket.put(doc._id, doc.content, metadata, function(error, resp) {
422
418
  if error
423
- error = Error("update failed", error, doc._id, { status: error.statusCode })
419
+ error = Error("update failed", error, doc._id, { status: error.$metadata.httpStatusCode })
424
420
  else {
425
421
  doc.md5 = resp.md5 // update the original doc
426
422
  doc.modified = undefined // we don't know exact modify date
@@ -441,7 +437,7 @@ closure dawsTable(database, path, local table) {
441
437
  return (asyncResult(callback, Error("invalid argument")))
442
438
  database.bucket.delete(doc._id, function(error, resp) {
443
439
  if error
444
- error = Error("delete failed", error, doc._id, { status: error.statusCode })
440
+ error = Error("delete failed", error, doc._id, { status: error.$metadata.httpStatusCode })
445
441
  callback(error, resp)
446
442
  })
447
443
  }
@@ -458,7 +454,7 @@ closure dawsTable(database, path, local table) {
458
454
  return (asyncResult(callback, id)) // error from makeID
459
455
  database.bucket.get(id, false, function(error, resp) {
460
456
  if error
461
- error = Error("get failed", error, id, { status: error.statusCode })
457
+ error = Error("get failed", error, id, { status: error.$metadata.httpStatusCode })
462
458
  else {
463
459
  resp.key = key
464
460
  resp = s3resp2doc(resp)
@@ -491,7 +487,7 @@ closure dawsTable(database, path, local table) {
491
487
  return (asyncResult(callback, id)) // error from makeID
492
488
  database.bucket.get(id, true, function(error, resp) {
493
489
  if error
494
- error = Error("md5 failed", error, id, { status: error.statusCode })
490
+ error = Error("md5 failed", error, id, { status: error.$metadata.httpStatusCode })
495
491
  else if resp._md5
496
492
  resp = resp._md5
497
493
  else
@@ -502,7 +498,10 @@ closure dawsTable(database, path, local table) {
502
498
 
503
499
  // head
504
500
  //
505
- // Return the header of the specified record, i.e. everything except the content.
501
+ // Return the header of the specified record, i.e. everything except the content. This operates
502
+ // on directories (with names ending in slash) differently because AWS does not support calling
503
+ // headObject upon them. We simply want to confirm that the directory exists, so we list objects
504
+ // that have the name without the slash and we get a common prefix if it exists.
506
505
 
507
506
  table.head = closure head(key, callback, local id) {
508
507
  if !callback
@@ -510,15 +509,30 @@ closure dawsTable(database, path, local table) {
510
509
  id = makeID(key)
511
510
  if !string(id)
512
511
  return (asyncResult(callback, id)) // error from makeID
513
- database.bucket.get(id, true, function(error, resp) {
514
- if error
515
- error = Error("head failed", error, id, { status: error.statusCode })
516
- else {
517
- resp.key = key
518
- resp = s3resp2doc(resp)
519
- resp._id = id }
520
- callback(error, resp)
521
- })
512
+ if id.slice(-1) == "/"
513
+ database.bucket.list(id.slice(0,-1), true, function blcb(error, resp) {
514
+ if error
515
+ error = Error("head failed", error, id, { status: error.$metadata.httpStatusCode })
516
+ else if id != resp.CommonPrefixes.0.Prefix {
517
+ error = Error("head not found", id, resp.CommonPrefixes.0.Prefix, { status: 404 })
518
+ resp = false
519
+ }
520
+ else {
521
+ resp.key = key
522
+ resp = s3resp2doc(resp)
523
+ resp._id = id }
524
+ callback(error, resp)
525
+ })
526
+ else
527
+ database.bucket.get(id, true, function bgcb(error, resp) {
528
+ if error
529
+ error = Error("head failed", error, id, { status: error.$metadata.httpStatusCode })
530
+ else {
531
+ resp.key = key
532
+ resp = s3resp2doc(resp)
533
+ resp._id = id }
534
+ callback(error, resp)
535
+ })
522
536
  }
523
537
 
524
538
  // copy
@@ -539,7 +553,7 @@ closure dawsTable(database, path, local table) {
539
553
  error = Error("copy failed", {
540
554
  source: srckey
541
555
  destination: destkey
542
- status: error.statusCode
556
+ status: error.$metadata.httpStatusCode
543
557
  }, error)
544
558
  callback(error, resp)
545
559
  })
@@ -631,7 +645,7 @@ closure dawsTable(database, path, local table) {
631
645
  s3prefix = table.s3prefix
632
646
  database.bucket.list(s3prefix, !options.deep, function(error, objects, local obj, key) {
633
647
  if error
634
- error = Error("records list failed", error, options.prefix, { status: error.statusCode })
648
+ error = Error("records list failed", error, options.prefix, { status: error.$metadata.httpStatusCode })
635
649
  else {
636
650
  for obj in objects.Contents {
637
651
  if obj.Key.startsWith(table.s3prefix)
@@ -674,13 +688,13 @@ closure dawsTable(database, path, local table) {
674
688
  *
675
689
  */
676
690
 
677
- closure S3DB(s3, local database, result) {
691
+ closure S3DB(s3b, local database, result) {
678
692
  database = new(object, this)
679
- result = dawsBucket(s3)
693
+ result = dawsBucket(s3b)
680
694
  if result.0
681
695
  return (result) // can't access bucket
682
696
  database.bucket = result.1
683
- database.s3 = s3
697
+ database.s3b = s3b
684
698
  database.type = "Amazon S3"
685
699
 
686
700
  // isopen
@@ -700,7 +714,6 @@ closure S3DB(s3, local database, result) {
700
714
  return (list(Error("database closed")))
701
715
  database.bucket.close()
702
716
  database.bucket = false
703
- database.s3 = false
704
717
  list(false, { ok: true })
705
718
  }
706
719
 
@@ -712,10 +725,10 @@ closure S3DB(s3, local database, result) {
712
725
  if !database.bucket
713
726
  return (list(Error("database closed")))
714
727
  result = {
715
- type: database.type
728
+ type: database.type
716
729
  info: {
717
- region: s3.config.region
718
- bucket: s3.config.params.Bucket
730
+ region: s3b.region
731
+ bucket: s3b.bucketName
719
732
  }
720
733
  }
721
734
  list(false, result)
@@ -758,7 +771,7 @@ closure S3DB(s3, local database, result) {
758
771
 
759
772
  function dawsInit(local manifest) {
760
773
 
761
- manifest = `(dawsValidateName, dawsBucket, dawsTable, S3DB, dawsInit)
774
+ manifest = `(dawsBucket, dawsTable, S3DB, dawsInit)
762
775
 
763
776
  Naan.module.build(module.id, "dbt_aws", function(modobj, compobj) {
764
777
  require("./serviceAws.nlg")