@naanlang/naan 1.2.0 → 1.3.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 (41) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/bin/index.js +5 -2
  4. package/dist/env_web.js +11 -1
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/https_request.nlg +2 -0
  7. package/frameworks/browser/sworker.js +23 -23
  8. package/frameworks/browser/terminals.nlg +132 -20
  9. package/frameworks/browser/workers.nlg +4 -1
  10. package/frameworks/browser/ws_client.nlg +9 -3
  11. package/frameworks/client/apiclient.nlg +8 -6
  12. package/frameworks/client/psm_client.nlg +4 -4
  13. package/frameworks/client/relaycon.nlg +5 -1
  14. package/frameworks/common/common.nlg +10 -4
  15. package/frameworks/node/apiserver.nlg +190 -4
  16. package/frameworks/node/worker.nlg +112 -10
  17. package/frameworks/node/ws_client.nlg +10 -3
  18. package/frameworks/project/build.nlg +13 -11
  19. package/frameworks/project/projects.nlg +7 -5
  20. package/frameworks/running/executors.nlg +124 -34
  21. package/frameworks/running/taskexec.nlg +5 -1
  22. package/lib/browser/env_web.js +17 -7
  23. package/lib/browser/env_webworker.js +22 -3
  24. package/lib/core/naanlib.js +5 -5
  25. package/lib/env_node.js +15 -4
  26. package/lib/env_nodeworker.js +21 -1
  27. package/package.json +8 -5
  28. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -2
  29. package/plugins/serviceAws/aws/aws-sdk.min.js +3 -2
  30. package/plugins/serviceAws/aws_dynamo.nlg +19 -19
  31. package/plugins/serviceAws/dbt_aws.nlg +1 -1
  32. package/plugins/serviceYC/generic_api.yaml +36 -0
  33. package/plugins/serviceYC/naanide-relay-index.js +148 -0
  34. package/plugins/serviceYC/serviceYC.nlg +60 -0
  35. package/plugins/serviceYC/yc_apigateway.nlg +205 -0
  36. package/plugins/serviceYC/yc_function.nlg +163 -0
  37. package/plugins/serviceYC/yc_vm.nlg +207 -0
  38. package/test/test_02_context.nlg +1 -1
  39. package/test/test_03_jsinterop.nlg +2 -2
  40. package/test/test_05_strings.nlg +7 -4
  41. package/test/test_07_lingo.nlg +1 -1
@@ -0,0 +1,163 @@
1
+ /*
2
+ * yc_function.nlg
3
+ * serviceYC
4
+ *
5
+ * Access to serverless function management services for YC.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2022 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * YCFunction
16
+ *
17
+ * YCFunction manipulation functions.
18
+ *
19
+ */
20
+
21
+ closure YCFunction(local ycfun) {
22
+ ycfun = new(object, this)
23
+
24
+ //
25
+ // login
26
+ //
27
+ ycfun.login = closure login(creds) {
28
+ ycfun.creds = creds
29
+ list(false, { ok: true })
30
+ }
31
+
32
+ //
33
+ // getFunction
34
+ //
35
+ // Get basic function info like:
36
+ // {
37
+ // id : "d4er5sq67r5j47nno6nb",
38
+ // folderId : "b1gjjahqm5u9tjolom9l",
39
+ // createdAt : "2022-08-30T00:57:18.024Z",
40
+ // name : "hello-world",
41
+ // description : "A quick first function.",
42
+ // logGroupId : "ckgbmh6df6dt92pphc6b",
43
+ // httpInvokeUrl: "https://functions.yandexcloud.net/d4er5sq67r5j47nno6nb",
44
+ // status : "ACTIVE"
45
+ // }
46
+ //
47
+ ycfun.getFunction = closure getFunction(funcID, local url, error, data) {
48
+ debuglog("YCFunction.getFunction", funcID)
49
+ url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/functions/".concat(funcID)
50
+ `(error, data) = YcRequest(url, ycfun.creds, {
51
+ query: { x: 1 }
52
+ })
53
+ if error
54
+ debuglog("YCFunction.getFunction failed:", ErrorString(error))
55
+ list(error, data)
56
+ }
57
+
58
+ //
59
+ // getVersionByTag
60
+ //
61
+ // Get info about a specific function version using its tag, e.g. $latest:
62
+ // {
63
+ // resources : { memory: "134217728" },
64
+ // tags : ["$latest"],
65
+ // id : "d4evm4lsi91hqkv78ane",
66
+ // functionId : "d4er5sq67r5j47nno6nb",
67
+ // createdAt : "2022-08-31T03:32:59.577Z",
68
+ // runtime : "nodejs16",
69
+ // entrypoint : "index.handler",
70
+ // executionTimeout: "3s",
71
+ // serviceAccountId: "aje9s3df60gofsddio0c",
72
+ // imageSize : "4096",
73
+ // status : "ACTIVE",
74
+ // logGroupId : "ckgbmh6df6dt92pphc6b" }
75
+ // }
76
+ //
77
+ ycfun.getVersionByTag = closure getVersionByTag(funcID, tag, local url, error, data) {
78
+ url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/versions:byTag".concat(
79
+ EncodeQuery("?", {
80
+ functionId: funcID
81
+ tag: tag
82
+ }))
83
+ `(error, data) = YcRequest(url, ycfun.creds, {
84
+ query: { x: 1 }
85
+ })
86
+ if error
87
+ debuglog("YCFunction.getVersionByTag failed:", ErrorString(error))
88
+ list(error, data)
89
+ }
90
+
91
+ //
92
+ // invokeFunction
93
+ //
94
+ // Invoke a public cloud function. It is impossible to invoke a private cloud function from
95
+ // browsers because the function cannot respond to a CORS authorization request using the OPTIONS
96
+ // method without an authorization header that cannot be added to the preflight.
97
+ //
98
+ ycfun.invokeFunction = closure invokeFunction(funcID, local url, error, data) {
99
+ url = "https://functions.yandexcloud.net/".concat(funcID)
100
+ `(error, data) = https.HttpsApiRequest(url, {
101
+ query: { x: 1 }
102
+ })
103
+ if error
104
+ debuglog("YCFunction.invokeFunction failed:", ErrorString(error))
105
+ list(error, data)
106
+ }
107
+
108
+ //
109
+ // updateFunctionCode
110
+ //
111
+ //
112
+ ycfun.updateFunctionCode = closure updateFunctionCode(info, bucket, key, sha256,
113
+ local url, params, error, data) {
114
+ params = {
115
+ function_id: info.functionId
116
+ functionID: info.functionId
117
+ runtime: info.runtime
118
+ description: "Updated: ".concat(Date())
119
+ entrypoint: info.entrypoint
120
+ resources: info.resources
121
+ executionTimeout: info.executionTimeout
122
+ serviceAccountId: info.serviceAccountId
123
+ package: {
124
+ bucketName: bucket
125
+ objectName: key
126
+ sha256: sha256
127
+ }
128
+ }
129
+ if info.environment
130
+ params.environment = info.environment
131
+ if info.logOptions
132
+ params.logOptions = info.logOptions
133
+ url = "https://serverless-functions.api.cloud.yandex.net/functions/v1/versions"
134
+ `(error, data) = YcRequest(url, ycfun.creds, {
135
+ putdata: params
136
+ })
137
+ if error
138
+ debuglog("YCFunction.updateFunctionCode failed:", ErrorString(error))
139
+ list(error, data)
140
+ }
141
+
142
+ // finis
143
+ ycfun
144
+ }
145
+
146
+
147
+ /*
148
+ * ycFunctionInit
149
+ *
150
+ * Initialize the module.
151
+ *
152
+ */
153
+
154
+ function ycFunctionInit(local manifest) {
155
+ manifest = `(YCFunction, ycFunctionInit)
156
+
157
+ Naan.module.build(module.id, "yc_function", function(modobj, compobj) {
158
+ require("./serviceYC.nlg")
159
+ compobj.manifest = manifest
160
+ modobj.exports.YCFunction = YCFunction
161
+ })
162
+
163
+ } ();
@@ -0,0 +1,207 @@
1
+ /*
2
+ * yc_vm.nlg
3
+ * serviceYC
4
+ *
5
+ * YC VM inquiry and control.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2024 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+ vmState = false;
14
+
15
+ /*
16
+ * VmControl
17
+ *
18
+ * When run on a YC VM instance, this class can acquire useful VM information and perform selected
19
+ * operations. At the present time it can obtain the identity document with instanceID, auth token,
20
+ * and shut down the VM.
21
+ *
22
+ */
23
+
24
+ closure VmControl(local ycvm) {
25
+ global(https, nodeHttp, vmState)
26
+ if vmState.ycvm
27
+ return (vmState.ycvm) // singleton
28
+ ycvm = new(object, this)
29
+ vmState = new(nonce) // don't save with state
30
+ vmState.ycvm = ycvm
31
+
32
+ // httpLocalRequest
33
+ //
34
+ // Make a simple GET request for the Docker's 169.254.169.254 metadata interface. This is not
35
+ // supported by the standard NaaN framework HttpsApiRequest because it is HTTP and returns JSON
36
+ // without bothering to set a contentType advising this.
37
+ //
38
+
39
+ closure httpLocalRequest(url, params, local pending, chunks, request) {
40
+ pending = new(nonce)
41
+ chunks = []
42
+ request = nodeHttp.request(url, params, closure (response) {
43
+ response.on("data", function (chunk) {
44
+ chunks.push(chunk)
45
+ })
46
+ response.on("end", function (local content, error) {
47
+ content = js.g.Buffer.concat(chunks)
48
+ if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
49
+ debuglog("httpRequest status:", url, response.statusCode, "message:", content)
50
+ pending.signal(list(Error("httpRequest statusCode:", url, response.statusCode, {
51
+ status: response.statusCode
52
+ }), false, { status: response.statusCode }))
53
+ return
54
+ }
55
+ if response.complete {
56
+ try {
57
+ content = js.g.JSON.parse(content.toString())
58
+ } catch {
59
+ error = Error("httpRequest decode:", url, exception)
60
+ }
61
+ }
62
+ else
63
+ error = Error("httpRequest terminated prematurely:", url, response.statusCode)
64
+ pending.signal(list(error, content, {
65
+ headers: response.headers
66
+ status: response.statusCode
67
+ contentType: response.headers["content-type"]
68
+ }))
69
+ })
70
+ })
71
+ request.on("error", function hre(error) {
72
+ debuglog("httpRequest error:", url, "error:", error)
73
+ error = Error("httpRequest failed:", url, error)
74
+ pending.signal(list(error))
75
+ })
76
+ request.end()
77
+ pending.wait()
78
+ }
79
+
80
+ // getIdentityDoc
81
+ //
82
+ // Ask the VM for its identity document. This information does not change.
83
+ //
84
+ // How to get instanceID from "identity document":
85
+ // curl -H Metadata-Flavor:Google 169.254.169.254/computeMetadata/v1/instance/vendor/identity/document
86
+ // {"instanceId":"epd15j0nbbp0mgs9p9ra","productCodes":null,"imageId":"fd8ru2jkl1laurb1emo7", \
87
+ // "productIds":["f2eo49853qme9iril0tp"],"createdAt":"2024-06-15T18:12:50Z","version":"2023-03-01"}
88
+ //
89
+ closure getIdentityDoc(local error, idDoc) {
90
+ if ycvm.idDoc
91
+ return (list(false, ycvm.idDoc))
92
+ `(error, idDoc) = httpLocalRequest("http://169.254.169.254/computeMetadata/v1/instance/vendor/identity/document", {
93
+ headers: {
94
+ "Metadata-Flavor": "Google"
95
+ }
96
+ })
97
+ if error
98
+ list(error)
99
+ else
100
+ list(false, ycvm.idDoc = idDoc)
101
+ }
102
+
103
+ // getAuthDoc
104
+ //
105
+ // Ask the VM for an auth token so we can call YC APIs using the VM's permissions. This expires
106
+ // so we keep track of when to call it again.
107
+ //
108
+ // How to get auth token:
109
+ // curl -H Metadata-Flavor:Google 169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token
110
+ // {"access_token":"t1.9euelZqVjY2Tk8rIlYyQnpGPjZbJmO3rnpWazpqcmJ6dnJKLzoqKlJeOmpHl9PdVKkhM-e8lZC_l3fT \
111
+ // 3FVlFTPnvJWQv5c3n9euelZqLlJaSnZaUypaYzc_Pnc2Mme_8xeuelZqLlJaSnZaUypaYzc_Pnc2Mmb3rnpWanZ2WnpaPysfNzc \
112
+ // eUy8zOmI-13oac0ZyQko-Ki5rRi5nSnJCSj4qLmtKSmouem56LntKMng.-wi4CzEJbPX5ume3Ma_P7nZG-g0YRCQ2P4l2koIRYV \
113
+ // drsy9QGHR5JEx2zD--xtvXGBBCbZNo-I41GdjEb_IPCg","expires_in":41998,"token_type":"Bearer"}
114
+ //
115
+ closure getAuthDoc(local now, error, authDoc, expireTime) {
116
+ now = milliseconds()
117
+ if ycvm.authDoc && ycvm.authDocExpire > milliseconds()
118
+ return (list(false, ycvm.authDoc)) // use cached credential
119
+ `(error, authDoc) = httpLocalRequest("http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token", {
120
+ headers: {
121
+ "Metadata-Flavor": "Google"
122
+ }
123
+ })
124
+ if authDoc {
125
+ expireTime = now + (authDoc.expires_in - 3600) * 1000 // give us 1 hour of margin
126
+ if !expireTime || expireTime < now + 3600000 { // if not at least one hour from now
127
+ debuglog("YcVmControl.getAuthDoc: invalid credential expires_in", authDoc.expires_in)
128
+ expireTime = now + 3600000
129
+ }
130
+ ycvm.authDocExpire = expireTime
131
+ ycvm.authDoc = authDoc
132
+ }
133
+ if error
134
+ list(error)
135
+ else
136
+ list(false, authDoc)
137
+ }
138
+
139
+ // authHeader
140
+ //
141
+ // Return authorization header for the VM to be used in API requests to YC, as a result tuple.
142
+ // The returned header is a list: (`Authorization, "Bearer <token>")
143
+ //
144
+ ycvm.authHeader = closure authHeader(error, authDoc) {
145
+ `(error, authDoc) = getAuthDoc()
146
+ if error
147
+ list(error)
148
+ else
149
+ list(false, list(`Authorization, "${authDoc.token_type} ${authDoc.access_token}"))
150
+ }
151
+
152
+ // shutdownVM
153
+ //
154
+ // Tell YC to shutdown our VM.
155
+ //
156
+ // curl -X POST -H "Authorization:Bearer <token>" \
157
+ // https://compute.api.cloud.yandex.net/compute/v1/instances/epd15j0nbbp0mgs9p9ra:stop
158
+ // {
159
+ // "done": false,
160
+ // "metadata": {
161
+ // "@type": "type.googleapis.com/yandex.cloud.compute.v1.StopInstanceMetadata",
162
+ // "instanceId": "epd15j0nbbp0mgs9p9ra"
163
+ // },
164
+ // "id": "epdnbllb1cgtmukpt1aq",
165
+ // "description": "Stop instance",
166
+ // "createdAt": "2024-06-15T19:47:40.315889611Z",
167
+ // "createdBy": "aje1ecgabcmt1uukhqen",
168
+ // "modifiedAt": "2024-06-15T19:47:40.315889611Z"
169
+ // }
170
+ //
171
+ ycvm.shutdownVM = closure shutdownVM(local error, idDoc, header, data) {
172
+ `(error, idDoc) = getIdentityDoc()
173
+ if !error
174
+ `(error, header) = authHeader()
175
+ if !error
176
+ `(error, data) = https.HttpsApiRequest("https://compute.api.cloud.yandex.net/compute/v1/instances/${idDoc.instanceId}:stop", {
177
+ method: "POST"
178
+ headers: [ header ]
179
+ })
180
+ if error
181
+ list(error)
182
+ else
183
+ list(false, data)
184
+ }
185
+
186
+ // finis
187
+
188
+ ycvm
189
+ };
190
+
191
+
192
+ /*
193
+ * ycvmInit
194
+ *
195
+ * Initialize the module.
196
+ *
197
+ */
198
+
199
+ function ycvmInit(local manifest) {
200
+ manifest = `(vmState, VmControl, ycvmInit)
201
+
202
+ Naan.module.build(module.id, "yc_vm", function(modobj, compobj) {
203
+ require("./serviceYC.nlg")
204
+ compobj.manifest = manifest
205
+ module.exports.VmControl = VmControl
206
+ })
207
+ } ();
@@ -38,7 +38,7 @@ RegisterTestCategory("context", [
38
38
  [ "function setNS(ns) { ns=reverse(ns),sudo(nsactive(reverse(ns, defNS))) };", "setNS" ],
39
39
  [ "x;", "x" ],
40
40
  [ "y;", "y" ],
41
- [ "length(symlist(nsactive(true).0));", "172"],
41
+ [ "length(symlist(nsactive(true).0));", "171"],
42
42
  [ "testvar;", "testvar" ],
43
43
  [ "function setTestvar(v) { testvar=v };", "setTestvar" ],
44
44
  [ "naanvar;", "naanvar" ],
@@ -35,11 +35,11 @@ RegisterTestCategory("JSinterop", [
35
35
 
36
36
  [ "o(3);", {
37
37
  "dev": { errors: "xcall \"[object Object]\": TypeError: object.apply is not a function" }
38
- "prod": { errors: "xcall \"[object Object]\": TypeError: r[3].apply is not a function" }
38
+ "prod": { errors: "xcall \"[object Object]\": TypeError: e[3].apply is not a function" }
39
39
  }[ "prod"]],
40
40
  [ "o(Number);", {
41
41
  "dev": { errors: "xcall \"[object Object]\": TypeError: object.apply is not a function" }
42
- "prod": { errors: "xcall \"[object Object]\": TypeError: r[3].apply is not a function" }
42
+ "prod": { errors: "xcall \"[object Object]\": TypeError: e[3].apply is not a function" }
43
43
  }[ "prod"]],
44
44
  [ "Number(3);", "[Number 3]" ],
45
45
  [ "Number(3).toString();", "\"3\"" ],
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2023 by Richard C. Zulch
9
+ * Copyright (c) 2017-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -52,11 +52,14 @@ RegisterTestCategory("strings", [
52
52
  // template strings
53
53
  //
54
54
 
55
- [ "'$${4+3}';", '"7"' ], // these have an extra $escape
55
+ [ "'$${4+3}';", '"7"' ], // needs extra $escape to be source
56
56
  [ "'$$${4+3}';", '"$${4+3}"' ],
57
57
  [ "'a$${4+3}b';", '"a7b"' ],
58
- [ "'$${}';", '' ],
59
- [ "'$${';", '' ], // ### should be "${"
58
+ [ "'$${}';", { errors: "incomplete expression at (1, 3) found ;" } ],
59
+ [ "'$${';", '"${"' ],
60
+ [ "'$$${';", '"$${"' ],
61
+ [ "'a$${\"\"}z';", '"az"'],
62
+ [ "'a$$${}z';", '"a$${}z"'],
60
63
  [ "quote('a$${4*5}b');>", '(strcat "a" (* 4 5) "b")' ],
61
64
 
62
65
 
@@ -192,7 +192,7 @@ RegisterTestCategory("Lingo", [
192
192
  [ "macro(x){x.c=5}({a:3,b:4});", { errors: "can't modify NaanConst::Dictionary{2} from Test-Lingo" } ],
193
193
  [ "macro(x){x[1]=6}([3,4,5]);", { errors: "can't modify NaanConst::Array[3] from Test-Lingo" } ],
194
194
 
195
- [ "js.*;", '("n", "o", "i", "d", "g", "r", "t")' ],
195
+ [ "js.*;", '("n", "o", "i", "d", "g", "r", "t", "k")' ],
196
196
 
197
197
  [ "[1,2,3].concat(4,5,6);", "[1, 2, 3, 4, 5, 6]" ],
198
198
  [ "[1,2,3].concat([4,5,6]);", "[1, 2, 3, 4, 5, 6]" ],