@naanlang/naan 1.0.6 → 1.0.8
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.
- package/LICENSE.md +3 -4
- package/README.md +20 -2
- package/bin/index.js +2 -2
- package/dist/env_web.js +91 -18
- package/dist/naan.min.js +7 -7
- package/frameworks/browser/browser.nlg +92 -3
- package/frameworks/browser/https_request.nlg +2 -2
- package/frameworks/browser/sworker.js +17 -17
- package/frameworks/browser/terminals.nlg +14 -12
- package/frameworks/browser/workers.nlg +13 -6
- package/frameworks/client/psm_client.nlg +1 -0
- package/frameworks/common/common.nlg +39 -8
- package/frameworks/node/filesystem.nlg +32 -26
- package/frameworks/node/gitter.nlg +9 -4
- package/frameworks/node/https_request.nlg +19 -14
- package/frameworks/project/build.nlg +187 -74
- package/frameworks/project/projects.nlg +42 -17
- package/frameworks/running/running.nlg +33 -11
- package/frameworks/running/taskexec.nlg +1 -1
- package/frameworks/storage/file_manager.nlg +2 -1
- package/frameworks/storage/psm.nlg +20 -4
- package/frameworks/storage/psm_dbtables.nlg +13 -3
- package/lib/browser/env_web.js +93 -20
- package/lib/browser/env_webworker.js +10 -0
- package/lib/core/naanlib.js +7 -7
- package/package.json +2 -1
- package/plugins/serviceAws/aws/aws-sdk-node.min.js +1 -1
- package/plugins/serviceAws/aws/aws-sdk.min.js +1 -1
- package/plugins/serviceAws/aws/lambda_ws_init.nlg +2 -1
- package/plugins/serviceAws/aws_dynamo.nlg +45 -26
- package/plugins/serviceAws/aws_dynextra.nlg +157 -32
- package/plugins/serviceAws/aws_lambda.nlg +4 -8
- package/plugins/serviceAws/aws_s3.nlg +74 -28
- package/plugins/serviceAws/aws_sqs.nlg +113 -0
- package/plugins/serviceAws/dbt_aws.nlg +73 -24
- package/plugins/serviceAws/psm_aws.nlg +30 -11
- package/plugins/serviceAws/serviceAws.nlg +2 -4
- package/plugins/serviceYC/generic_api.yaml +36 -0
- package/plugins/serviceYC/naanide-relay-index.js +148 -0
- package/plugins/serviceYC/serviceYC.nlg +59 -0
- package/plugins/serviceYC/yc_function.nlg +161 -0
- package/test/test_01_core.nlg +2 -2
- package/test/test_02_context.nlg +2 -2
- package/test/test_05_strings.nlg +11 -1
- package/test/test_06_lingoparse.nlg +43 -14
|
@@ -13,17 +13,36 @@
|
|
|
13
13
|
|
|
14
14
|
/*
|
|
15
15
|
* S3bucket
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
*
|
|
17
|
+
* Bucket ooptions:
|
|
18
|
+
* {
|
|
19
|
+
* cacheControl: "max-age=2592000" - default cache control, e.g. to "max-age=2592000"
|
|
20
|
+
* }
|
|
18
21
|
*
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
|
-
closure S3bucket(s3, name, local bucket) {
|
|
24
|
+
closure S3bucket(s3, name, bucketOptions, local bucket) {
|
|
22
25
|
bucket = new(object, this)
|
|
23
26
|
bucket.name = name
|
|
24
|
-
|
|
27
|
+
|
|
28
|
+
//
|
|
29
|
+
// head
|
|
30
|
+
//
|
|
31
|
+
bucket.head = closure head(key, local pending) {
|
|
32
|
+
pending = new(nonce)
|
|
33
|
+
s3.aws.headObject({
|
|
34
|
+
Bucket: bucket.name
|
|
35
|
+
Key: key
|
|
36
|
+
}, function (error, data) {
|
|
37
|
+
if error
|
|
38
|
+
error = Error("S3bucket.head failed:", error.name, "key:", key, "in", bucket.name)
|
|
39
|
+
pending.signal(list(error, data))
|
|
40
|
+
})
|
|
41
|
+
pending.wait()
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
//
|
|
26
|
-
//
|
|
45
|
+
// listObjects
|
|
27
46
|
//
|
|
28
47
|
bucket.listObjects = closure listObjects(local pending) {
|
|
29
48
|
pending = new(nonce)
|
|
@@ -31,31 +50,46 @@ closure S3bucket(s3, name, local bucket) {
|
|
|
31
50
|
Bucket: bucket.name
|
|
32
51
|
}, function (error, data) {
|
|
33
52
|
if error
|
|
34
|
-
error = Error("S3bucket.list failed", error)
|
|
53
|
+
error = Error("S3bucket.list failed:", error.name, "for", bucket.name)
|
|
35
54
|
pending.signal(list(error, data))
|
|
36
55
|
})
|
|
37
56
|
pending.wait()
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
//
|
|
41
|
-
//
|
|
60
|
+
// put
|
|
42
61
|
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
// options:
|
|
63
|
+
// {
|
|
64
|
+
// contentType: "text/plain" - override contentType if specified
|
|
65
|
+
// cacheControl: "max-age=2592000" - default cache control, e.g. to "max-age=2592000"
|
|
66
|
+
// metadata: { <items> } - optional dictionary of keys to store
|
|
67
|
+
// }
|
|
68
|
+
//
|
|
69
|
+
bucket.put = closure put(key, data, options, local params, pending) {
|
|
70
|
+
if !contentType
|
|
71
|
+
contentType = "application/octet-stream"
|
|
72
|
+
params = {
|
|
46
73
|
Bucket: bucket.name
|
|
47
|
-
ACL: "public-read"
|
|
48
|
-
Body: data
|
|
49
74
|
Key: key
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.
|
|
75
|
+
ContentMD5: HashMD5_base64(data)
|
|
76
|
+
Body: data
|
|
77
|
+
}
|
|
78
|
+
if options.contentType
|
|
79
|
+
params.ContentType = options.contentType
|
|
80
|
+
if options.metadata
|
|
81
|
+
params.Metadata = options.metadata
|
|
82
|
+
else
|
|
83
|
+
params.Metadata = "application/octet-stream"
|
|
84
|
+
if options.cacheControl
|
|
85
|
+
params.CacheControl = options.cacheControl
|
|
86
|
+
else if bucket.cacheControl
|
|
87
|
+
params.CacheControl = bucket.cacheControl
|
|
88
|
+
pending = new(nonce)
|
|
89
|
+
s3.aws.putObject(params, function(error, resp) {
|
|
56
90
|
if error
|
|
57
|
-
error = Error("S3bucket.
|
|
58
|
-
pending.signal(list(error,
|
|
91
|
+
error = Error("S3bucket.put failed:", error.name, "key:", key, "in", bucket.name)
|
|
92
|
+
pending.signal(list(error, resp))
|
|
59
93
|
})
|
|
60
94
|
pending.wait()
|
|
61
95
|
}
|
|
@@ -88,6 +122,8 @@ closure S3(local s3) {
|
|
|
88
122
|
accessKeyId: creds.keyID
|
|
89
123
|
secretAccessKey: creds.keySecret
|
|
90
124
|
}
|
|
125
|
+
if creds.endpoint
|
|
126
|
+
params.endpoint = creds.endpoint
|
|
91
127
|
}
|
|
92
128
|
s3.aws = xnew(awsSDK.S3, params)
|
|
93
129
|
if awsSDK.S3Client
|
|
@@ -111,12 +147,23 @@ closure S3(local s3) {
|
|
|
111
147
|
// This obtains a pre-signed URL for the specified command and its parameters. Options control
|
|
112
148
|
// how the URL is generated.
|
|
113
149
|
//
|
|
150
|
+
// CommandParams:
|
|
151
|
+
// {
|
|
152
|
+
// Bucket: <string> // bucket name
|
|
153
|
+
// Key: <string> // object being operated upon
|
|
154
|
+
// ContentType: <string> // video/mp4 or whatever
|
|
155
|
+
// ResponseCacheControl: <string> // reportedly for getObject only
|
|
156
|
+
// ACL: <string> // e.g. "public-read"
|
|
157
|
+
// Body: <data> // fiik
|
|
158
|
+
// }
|
|
159
|
+
//
|
|
114
160
|
// Options:
|
|
115
161
|
// {
|
|
116
|
-
// expiresIn:
|
|
162
|
+
// expiresIn: <seconds> // default expiration is 900 (15 minutes)
|
|
163
|
+
// signingDate: <epoch-secs> // reference time for URL being created
|
|
117
164
|
// }
|
|
118
165
|
//
|
|
119
|
-
s3.getSignedURL = closure getSignedURL(cmdname, cmdparams, local command,
|
|
166
|
+
s3.getSignedURL = closure getSignedURL(cmdname, cmdparams, options, local command, pending) {
|
|
120
167
|
if s3.client {
|
|
121
168
|
if cmdname == "putObject"
|
|
122
169
|
cmdname = "PutObjectCommand"
|
|
@@ -124,16 +171,15 @@ closure S3(local s3) {
|
|
|
124
171
|
cmdname = "GetObjectCommand"
|
|
125
172
|
else
|
|
126
173
|
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
174
|
command = xnew(awsSDK.s3Commands[cmdname], cmdparams)
|
|
134
175
|
await(awsSDK.getSignedUrl(s3.client, command, options))
|
|
135
176
|
} else {
|
|
177
|
+
debuglog("s3.getSignedURL: warning: old API")
|
|
136
178
|
pending = new(nonce)
|
|
179
|
+
if options.expiresIn {
|
|
180
|
+
cmdparams = new(cmdparams)
|
|
181
|
+
cmdparams.Expires = options.expiresIn
|
|
182
|
+
}
|
|
137
183
|
s3.aws.getSignedUrl(cmdname, cmdparams, function(error, data) {
|
|
138
184
|
pending.signal(list(error, data))
|
|
139
185
|
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* aws_sqs.nlg
|
|
3
|
+
* serviceAws
|
|
4
|
+
*
|
|
5
|
+
* Access to SQS (Simple Queue Service) for AWS.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2023 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* SqsMQ
|
|
16
|
+
*
|
|
17
|
+
* SqsMQ, a container for holding the queue name and sending messages to it.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
closure SqsMQ(sqs, queueUrl, local sqsq) {
|
|
22
|
+
sqsq = new(object, this)
|
|
23
|
+
|
|
24
|
+
//
|
|
25
|
+
// sendMessage
|
|
26
|
+
//
|
|
27
|
+
sqsq.sendMessage = closure sendMessage(msgBody) {
|
|
28
|
+
sqs.sendMessage(queueUrl, msgBody)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// finis
|
|
32
|
+
sqsq
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* SQS
|
|
38
|
+
*
|
|
39
|
+
* SQS service functions.
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
closure SQS(local sqs) {
|
|
44
|
+
sqs = new(object, this)
|
|
45
|
+
|
|
46
|
+
//
|
|
47
|
+
// login
|
|
48
|
+
//
|
|
49
|
+
sqs.login = closure login(creds, local params) {
|
|
50
|
+
if !awsSDK
|
|
51
|
+
return (list(Error("SQS.login: no AWS SDK available")))
|
|
52
|
+
params = {
|
|
53
|
+
}
|
|
54
|
+
if creds {
|
|
55
|
+
params.region = creds.region
|
|
56
|
+
params.credentials = {
|
|
57
|
+
accessKeyId: creds.keyID
|
|
58
|
+
secretAccessKey: creds.keySecret
|
|
59
|
+
}
|
|
60
|
+
if creds.sqs_endpoint
|
|
61
|
+
params.endpoint = creds.sqs_endpoint
|
|
62
|
+
}
|
|
63
|
+
sqs.aws = xnew(awsSDK.SQS, params)
|
|
64
|
+
`(false, { ok: true })
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//
|
|
68
|
+
// sendMessage
|
|
69
|
+
//
|
|
70
|
+
sqs.sendMessage = closure sendMessage(queueUrl, msgBody, local params, pending) {
|
|
71
|
+
debuglog("SQS.sendMessage:", awsSDK, sqs.aws, sqs.aws.sendMessage)
|
|
72
|
+
params = {
|
|
73
|
+
MessageBody: msgBody
|
|
74
|
+
QueueUrl: queueUrl
|
|
75
|
+
}
|
|
76
|
+
pending = new(nonce)
|
|
77
|
+
sqs.aws.sendMessage(params, function(error, data) {
|
|
78
|
+
if error
|
|
79
|
+
ErrorDebuglog("aws.sendMessage failed", error)
|
|
80
|
+
pending.signal(list(error, data))
|
|
81
|
+
})
|
|
82
|
+
pending.wait()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// mq
|
|
87
|
+
//
|
|
88
|
+
sqs.mq = function(queueUrl) {
|
|
89
|
+
SqsMQ(sqs, queueUrl)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// finis
|
|
93
|
+
sqs
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* sqsInit
|
|
99
|
+
*
|
|
100
|
+
* Initialize the SQS module.
|
|
101
|
+
*
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
function sqsInit(local manifest) {
|
|
105
|
+
manifest = `(SqsMQ, SQS, sqsInit)
|
|
106
|
+
|
|
107
|
+
Naan.module.build(module.id, "aws_sqs", function(modobj, compobj) {
|
|
108
|
+
require("./serviceAws.nlg")
|
|
109
|
+
compobj.manifest = manifest
|
|
110
|
+
modobj.exports.SQS = SQS
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
} ();
|
|
@@ -37,6 +37,19 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
37
37
|
closure asyncResult args {
|
|
38
38
|
future(function() { apply(pop(args), args) }).run()
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
// hashFromETag
|
|
42
|
+
//
|
|
43
|
+
// Return the MD5 hash for an ETag
|
|
44
|
+
//
|
|
45
|
+
function hashFromETag(etag) {
|
|
46
|
+
if etag.startsWith("W/")
|
|
47
|
+
etag = etag.slice(2) // "weak" from gzip with NGINX, apparently
|
|
48
|
+
if etag.length == 34 // multipart S3 ETag will is longer and will be ignored
|
|
49
|
+
etag.substring(1,33)
|
|
50
|
+
else
|
|
51
|
+
undefined
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
// close
|
|
42
55
|
//
|
|
@@ -65,8 +78,8 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
65
78
|
function s3GetCB(error, resp) {
|
|
66
79
|
if error
|
|
67
80
|
error = Error("can't get object", error, key)
|
|
68
|
-
else
|
|
69
|
-
resp.md5 = resp.ETag
|
|
81
|
+
else
|
|
82
|
+
resp.md5 = hashFromETag(resp.ETag)
|
|
70
83
|
callback(error, resp)
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -79,12 +92,18 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
79
92
|
// put
|
|
80
93
|
//
|
|
81
94
|
// Put an object in the bucket. Parameters include:
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
95
|
+
// key - the object name, which may not be empty or start with "/"
|
|
96
|
+
// data - optional content to store
|
|
97
|
+
// options:
|
|
98
|
+
// {
|
|
99
|
+
// contentType: "text/plain" - override contentType if specified
|
|
100
|
+
// cacheControl: "max-age=2592000" - default cache control, e.g. to "max-age=2592000"
|
|
101
|
+
// metadata: { <items> } - optional dictionary of keys to store
|
|
102
|
+
// }
|
|
85
103
|
|
|
86
|
-
bucket.put = closure put(key, data,
|
|
87
|
-
|
|
104
|
+
bucket.put = closure put(key, data, options, callback,
|
|
105
|
+
local params, mimeType, md5) {
|
|
106
|
+
if !string(key) || length(key) == 0 || key.substring(0,1) == "/"
|
|
88
107
|
return (asyncResult(callback, Error("invalid arguments")))
|
|
89
108
|
if !s3
|
|
90
109
|
return (asyncResult(callback, Error("database closed")))
|
|
@@ -128,9 +147,13 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
128
147
|
mimeType = mimeType.concat("; charset=UTF-8")
|
|
129
148
|
params.ContentType = mimeType
|
|
130
149
|
}
|
|
150
|
+
if options.contentType
|
|
151
|
+
params.ContentType = options.contentType
|
|
131
152
|
}
|
|
132
|
-
if metadata
|
|
133
|
-
params.Metadata = metadata
|
|
153
|
+
if options.metadata
|
|
154
|
+
params.Metadata = options.metadata
|
|
155
|
+
if options.cacheControl
|
|
156
|
+
params.CacheControl = options.cacheControl
|
|
134
157
|
if data
|
|
135
158
|
params.Body = data
|
|
136
159
|
else
|
|
@@ -142,11 +165,10 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
142
165
|
error = Error("can't put object", error, key)
|
|
143
166
|
else {
|
|
144
167
|
resp.length = length(data)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
resp = false } } }
|
|
168
|
+
resp.md5 = hashFromETag(resp.ETag)
|
|
169
|
+
if resp.md5 && md5 != resp.md5 {
|
|
170
|
+
error = Error("hash match failure", md5, resp.md5) // has match failed on round-trip
|
|
171
|
+
resp = false } }
|
|
150
172
|
callback(error, resp)
|
|
151
173
|
})
|
|
152
174
|
}
|
|
@@ -285,12 +307,31 @@ closure dawsBucket(s3b, local bucket, s3) {
|
|
|
285
307
|
*
|
|
286
308
|
*/
|
|
287
309
|
|
|
288
|
-
closure dawsTable(database, path, local table) {
|
|
310
|
+
closure dawsTable(database, path, tableOptions, local table) {
|
|
289
311
|
table = new(object, this)
|
|
290
312
|
table.type = database.type
|
|
291
313
|
table.db = database
|
|
292
314
|
table.path = path
|
|
293
315
|
table.s3prefix = makePrefix(path)
|
|
316
|
+
|
|
317
|
+
// readableStreamToArrayBuffer
|
|
318
|
+
//
|
|
319
|
+
// For browsers, convert a ReadableStream to an array buffer.
|
|
320
|
+
//
|
|
321
|
+
function readableStreamToArrayBuffer(stream, local result, reader, error, data, chunk) {
|
|
322
|
+
result = xnew(js.w.Uint8Array, 0)
|
|
323
|
+
reader = stream.getReader()
|
|
324
|
+
loop {
|
|
325
|
+
`(error, data) = await(reader.read())
|
|
326
|
+
if error || data.done
|
|
327
|
+
break
|
|
328
|
+
chunk = xnew(js.w.Uint8Array, result.length + data.value.length)
|
|
329
|
+
chunk.set(result)
|
|
330
|
+
chunk.set(data.value, result.length)
|
|
331
|
+
result = chunk
|
|
332
|
+
}
|
|
333
|
+
result
|
|
334
|
+
}
|
|
294
335
|
|
|
295
336
|
// makePrefix
|
|
296
337
|
//
|
|
@@ -344,7 +385,7 @@ closure dawsTable(database, path, local table) {
|
|
|
344
385
|
if resp.ContentType
|
|
345
386
|
doc.contentType = resp.ContentType
|
|
346
387
|
if resp.md5
|
|
347
|
-
doc.
|
|
388
|
+
doc._md5 = resp.md5
|
|
348
389
|
doc
|
|
349
390
|
}
|
|
350
391
|
|
|
@@ -394,11 +435,14 @@ closure dawsTable(database, path, local table) {
|
|
|
394
435
|
if content
|
|
395
436
|
doc.content = content
|
|
396
437
|
metadata = metadata2s3(metadata) // filter out any invalid metadata
|
|
397
|
-
database.bucket.put(id, content,
|
|
438
|
+
database.bucket.put(id, content, {
|
|
439
|
+
metadata: metadata
|
|
440
|
+
cacheControl: tableOptions.cacheControl
|
|
441
|
+
}, function(error, resp) {
|
|
398
442
|
if error
|
|
399
443
|
error = Error("add failed", error, id, { status: error.$metadata.httpStatusCode })
|
|
400
444
|
else {
|
|
401
|
-
doc.
|
|
445
|
+
doc._md5 = resp.md5 // update the original doc
|
|
402
446
|
doc.length = resp.length }
|
|
403
447
|
callback(error, doc)
|
|
404
448
|
})
|
|
@@ -411,14 +455,17 @@ closure dawsTable(database, path, local table) {
|
|
|
411
455
|
table.update = closure update(doc, callback, local metadata) {
|
|
412
456
|
if !callback
|
|
413
457
|
return (syncAdapter(update, doc))
|
|
414
|
-
if !doc._id || !doc._id.startsWith(table.s3prefix) || !doc.
|
|
458
|
+
if !doc._id || !doc._id.startsWith(table.s3prefix) || !doc._md5
|
|
415
459
|
return (asyncResult(callback, Error("invalid argument")))
|
|
416
460
|
metadata = metadata2s3(doc)
|
|
417
|
-
database.bucket.put(doc._id, doc.content,
|
|
461
|
+
database.bucket.put(doc._id, doc.content, {
|
|
462
|
+
metadata: metadata
|
|
463
|
+
cacheControl: tableOptions.cacheControl
|
|
464
|
+
}, function(error, resp) {
|
|
418
465
|
if error
|
|
419
466
|
error = Error("update failed", error, doc._id, { status: error.$metadata.httpStatusCode })
|
|
420
467
|
else {
|
|
421
|
-
doc.
|
|
468
|
+
doc._md5 = resp.md5 // update the original doc
|
|
422
469
|
doc.modified = undefined // we don't know exact modify date
|
|
423
470
|
doc.length = resp.length }
|
|
424
471
|
callback(error, doc)
|
|
@@ -433,7 +480,7 @@ closure dawsTable(database, path, local table) {
|
|
|
433
480
|
table.delete = closure delete(doc, callback) {
|
|
434
481
|
if !callback
|
|
435
482
|
return (syncAdapter(delete, doc))
|
|
436
|
-
if !doc._id || !doc._id.startsWith(table.s3prefix) || !doc.
|
|
483
|
+
if !doc._id || !doc._id.startsWith(table.s3prefix) || !(doc._md5 || doc._id.slice(-1) == "/")
|
|
437
484
|
return (asyncResult(callback, Error("invalid argument")))
|
|
438
485
|
database.bucket.delete(doc._id, function(error, resp) {
|
|
439
486
|
if error
|
|
@@ -469,6 +516,8 @@ closure dawsTable(database, path, local table) {
|
|
|
469
516
|
{ } // already "decoded", e.g. base64
|
|
470
517
|
else if jsTypedArray(resp.content)
|
|
471
518
|
resp.content = xnew(TextDecoder, "utf-8").decode(resp.content)
|
|
519
|
+
else if jsInstanceOf(resp.content, js.w.ReadableStream)
|
|
520
|
+
resp.content = xnew(TextDecoder, "utf-8").decode(readableStreamToArrayBuffer(resp.content))
|
|
472
521
|
resp._id = id }
|
|
473
522
|
callback(error, resp)
|
|
474
523
|
})
|
|
@@ -748,12 +797,12 @@ closure S3DB(s3b, local database, result) {
|
|
|
748
797
|
//
|
|
749
798
|
// Return a table-access object for the table.
|
|
750
799
|
|
|
751
|
-
database.table = function table(path) {
|
|
800
|
+
database.table = function table(path, options) {
|
|
752
801
|
if !database.bucket
|
|
753
802
|
return (list(Error("database closed")))
|
|
754
803
|
if !string(path)
|
|
755
804
|
return (list(Error("invalid table path")))
|
|
756
|
-
dawsTable(database, path)
|
|
805
|
+
dawsTable(database, path, options)
|
|
757
806
|
}
|
|
758
807
|
|
|
759
808
|
// finis
|
|
@@ -47,7 +47,11 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
47
47
|
{
|
|
48
48
|
label: "Bucket"
|
|
49
49
|
placeholder: "e.g. myBucketName"
|
|
50
|
-
key: "bucketName" }
|
|
50
|
+
key: "bucketName" },
|
|
51
|
+
{
|
|
52
|
+
label: "Endpoint"
|
|
53
|
+
placeholder: "custom URL"
|
|
54
|
+
key: "endpoint" }
|
|
51
55
|
]
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -59,6 +63,7 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
59
63
|
`(error, creds) = connector.vault.accessResource(resID)
|
|
60
64
|
if creds && (!resource.region || resource.region == creds.region)
|
|
61
65
|
&& (!resource.bucketName || resource.bucketName == creds.bucketName)
|
|
66
|
+
&& (!resource.endpoint || resource.endpoint == creds.endpoint)
|
|
62
67
|
&& (!resource.label || resource.label == creds.label)
|
|
63
68
|
return (resID)
|
|
64
69
|
}
|
|
@@ -75,6 +80,7 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
75
80
|
// keySecret - AWS IAM keySecret
|
|
76
81
|
// region - AWS region
|
|
77
82
|
// bucketName - AWS bucketName
|
|
83
|
+
// endpoint - custom URL
|
|
78
84
|
// hidden - [optional] don't enumerate to user
|
|
79
85
|
// locked - [optional] don't allow user delete
|
|
80
86
|
// The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
|
|
@@ -90,6 +96,8 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
90
96
|
region: resource.region,
|
|
91
97
|
bucketName: resource.bucketName
|
|
92
98
|
}
|
|
99
|
+
if resource.endpoint
|
|
100
|
+
creds.endpoint = resource.endpoint
|
|
93
101
|
errors = { }
|
|
94
102
|
if resource.label
|
|
95
103
|
creds.label = resource.label
|
|
@@ -107,6 +115,8 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
107
115
|
errors.region = "required field"
|
|
108
116
|
if badField(resource.bucketName)
|
|
109
117
|
errors.bucketName = "required field"
|
|
118
|
+
if resource.endpoint && badField(resource.endpoint)
|
|
119
|
+
errors.endpoint = "invalid field"
|
|
110
120
|
if length(errors) > 0
|
|
111
121
|
error = errors
|
|
112
122
|
else {
|
|
@@ -164,6 +174,8 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
164
174
|
resource.region = creds.region
|
|
165
175
|
if string(creds.bucketName)
|
|
166
176
|
resource.bucketName = creds.bucketName
|
|
177
|
+
if string(creds.endpoint)
|
|
178
|
+
resource.endpoint = creds.endpoint
|
|
167
179
|
list(false, resource)
|
|
168
180
|
}
|
|
169
181
|
|
|
@@ -209,22 +221,28 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
209
221
|
//
|
|
210
222
|
// Return an S3 access object, which is the credentials to access an AWS S3 bucket.
|
|
211
223
|
|
|
212
|
-
connector.access = function access(resID, local error, creds, s3b) {
|
|
224
|
+
connector.access = function access(resID, local error, creds, params, s3b) {
|
|
213
225
|
`(error, creds) = connector.vault.accessResource(resID)
|
|
214
226
|
if error
|
|
215
227
|
return (list(error)) // can't access that resource
|
|
216
228
|
if !awsSDK.S3
|
|
217
229
|
return (list(Error("S3 connector: no AWS SDK available")))
|
|
230
|
+
params = {
|
|
231
|
+
region: creds.region
|
|
232
|
+
credentials: {
|
|
233
|
+
accessKeyId: creds.keyID
|
|
234
|
+
secretAccessKey: creds.keySecret
|
|
235
|
+
}
|
|
236
|
+
}
|
|
218
237
|
s3b = {
|
|
219
|
-
s3: xnew(awsSDK.S3, {
|
|
220
|
-
region: creds.region
|
|
221
|
-
credentials: {
|
|
222
|
-
accessKeyId: creds.keyID
|
|
223
|
-
secretAccessKey: creds.keySecret
|
|
224
|
-
}})
|
|
225
238
|
bucketName: creds.bucketName
|
|
226
239
|
region: creds.region
|
|
227
240
|
}
|
|
241
|
+
if creds.endpoint {
|
|
242
|
+
params.endpoint = creds.endpoint
|
|
243
|
+
s3b.endpoint = creds.endpoint
|
|
244
|
+
}
|
|
245
|
+
s3b.s3 = xnew(awsSDK.S3, params)
|
|
228
246
|
list(false, s3b)
|
|
229
247
|
}
|
|
230
248
|
|
|
@@ -232,10 +250,11 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
232
250
|
//
|
|
233
251
|
// Connect to an AWS bucket filesystem, returning a view.
|
|
234
252
|
|
|
235
|
-
connector.connect = function connect(resID, service, args,
|
|
253
|
+
connector.connect = function connect(resID, service, args,
|
|
254
|
+
local error, s3b, db, rootpath, table) {
|
|
236
255
|
`(error, s3b) = connector.access(resID)
|
|
237
256
|
if !error
|
|
238
|
-
`(error, db) = S3DB(s3b)
|
|
257
|
+
`(error, db) = S3DB(s3b, args.1) // get S3 bucket as a database
|
|
239
258
|
if error
|
|
240
259
|
list(error)
|
|
241
260
|
else if service == "NideDB"
|
|
@@ -244,7 +263,7 @@ closure psmaConnector(psm, local connClassID, connector, watch) {
|
|
|
244
263
|
rootpath = args.0 // connect to a NideFS filesystem
|
|
245
264
|
if !rootpath
|
|
246
265
|
return (Error("mandatory rootpath missing"))
|
|
247
|
-
`(error, table) = db.table(rootpath)
|
|
266
|
+
`(error, table) = db.table(rootpath, args.1) // args.1 is table options; see dawsTable
|
|
248
267
|
if error
|
|
249
268
|
list(error)
|
|
250
269
|
else
|
|
@@ -26,10 +26,8 @@ function sawsInit(local manifest) {
|
|
|
26
26
|
require("frameworks/common").LiveImport()
|
|
27
27
|
require("./aws_utils.nlg")
|
|
28
28
|
function sawsPostload() {
|
|
29
|
-
if js.
|
|
30
|
-
awsSDK = js.r("/
|
|
31
|
-
else if js.g
|
|
32
|
-
awsSDK = js.r("aws-sdk-node.min") // SDK must be in search path
|
|
29
|
+
if js.g
|
|
30
|
+
awsSDK = js.r(JSpath.resolve(js.d, module.locpath, "aws/aws-sdk-node.min.js"))
|
|
33
31
|
else
|
|
34
32
|
awsSDK = JsLoadScript(JSpath.join(module.locpath, "./aws/aws-sdk.min.js"), "awsSdk")
|
|
35
33
|
module.exports.awsSDK = awsSDK
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: tutor-backend API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
servers:
|
|
6
|
+
- url: https://d5dhrgakpe39e11lqefr.apigw.yandexcloud.net
|
|
7
|
+
paths:
|
|
8
|
+
/{proxy+}:
|
|
9
|
+
options:
|
|
10
|
+
parameters:
|
|
11
|
+
- name: proxy
|
|
12
|
+
in: path
|
|
13
|
+
required: true
|
|
14
|
+
schema:
|
|
15
|
+
type: string
|
|
16
|
+
x-yc-apigateway-integration:
|
|
17
|
+
type: dummy
|
|
18
|
+
http_code: 200
|
|
19
|
+
http_headers:
|
|
20
|
+
Access-Control-Allow-Headers: "*"
|
|
21
|
+
Access-Control-Allow-Methods: "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT"
|
|
22
|
+
Access-Control-Allow-Origin: "*"
|
|
23
|
+
content:
|
|
24
|
+
"application/json": ""
|
|
25
|
+
x-yc-apigateway-any-method:
|
|
26
|
+
parameters:
|
|
27
|
+
- name: proxy
|
|
28
|
+
in: path
|
|
29
|
+
required: true
|
|
30
|
+
schema:
|
|
31
|
+
type: string
|
|
32
|
+
x-yc-apigateway-integration:
|
|
33
|
+
type: cloud_functions
|
|
34
|
+
function_id: d4ehjjcfk8ffe43o5t7j
|
|
35
|
+
tag: $latest
|
|
36
|
+
service_account_id: ajeuftvbtshn348ert80
|