firefly-compiler 0.5.35 → 0.5.37

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 (225) hide show
  1. package/.hintrc +4 -4
  2. package/.vscode/settings.json +4 -4
  3. package/bin/Release.ff +157 -154
  4. package/bin/firefly.mjs +1 -1
  5. package/compiler/Builder.ff +275 -277
  6. package/compiler/Compiler.ff +234 -233
  7. package/compiler/Dependencies.ff +186 -187
  8. package/compiler/DependencyLock.ff +17 -17
  9. package/compiler/Deriver.ff +23 -31
  10. package/compiler/Dictionaries.ff +1 -1
  11. package/compiler/Inference.ff +43 -20
  12. package/compiler/JsEmitter.ff +1437 -1282
  13. package/compiler/LspHook.ff +202 -202
  14. package/compiler/Main.ff +25 -24
  15. package/compiler/ModuleCache.ff +178 -178
  16. package/compiler/Parser.ff +36 -109
  17. package/compiler/Resolver.ff +5 -8
  18. package/compiler/Substitution.ff +1 -1
  19. package/compiler/Syntax.ff +1 -16
  20. package/compiler/Token.ff +9 -0
  21. package/compiler/Tokenizer.ff +4 -0
  22. package/compiler/Workspace.ff +88 -88
  23. package/core/.firefly/include/package.json +5 -5
  24. package/core/.firefly/package.ff +2 -2
  25. package/core/Any.ff +26 -30
  26. package/core/Array.ff +298 -265
  27. package/core/Atomic.ff +63 -64
  28. package/core/Box.ff +7 -7
  29. package/core/BrowserSystem.ff +40 -40
  30. package/core/Buffer.ff +185 -152
  31. package/core/BuildSystem.ff +156 -148
  32. package/core/Channel.ff +95 -92
  33. package/core/Char.ff +3 -2
  34. package/core/Core.ff +16 -23
  35. package/core/Crypto.ff +94 -96
  36. package/core/Equal.ff +41 -36
  37. package/core/Error.ff +15 -10
  38. package/core/FileHandle.ff +45 -37
  39. package/core/Float.ff +176 -200
  40. package/core/HttpClient.ff +142 -148
  41. package/core/Instant.ff +6 -8
  42. package/core/Int.ff +40 -24
  43. package/core/IntMap.ff +61 -39
  44. package/core/Js.ff +305 -0
  45. package/core/JsSystem.ff +135 -114
  46. package/core/JsValue.ff +303 -159
  47. package/core/Json.ff +423 -443
  48. package/core/List.ff +482 -486
  49. package/core/Lock.ff +108 -144
  50. package/core/Log.ff +25 -14
  51. package/core/NodeSystem.ff +198 -191
  52. package/core/Ordering.ff +160 -161
  53. package/core/Path.ff +377 -409
  54. package/core/Queue.ff +90 -0
  55. package/core/Random.ff +140 -134
  56. package/core/RbMap.ff +216 -216
  57. package/core/Serializable.ff +16 -13
  58. package/core/Show.ff +44 -43
  59. package/core/SourceLocation.ff +68 -68
  60. package/core/Stream.ff +1 -1
  61. package/core/String.ff +224 -202
  62. package/core/StringMap.ff +58 -36
  63. package/core/Task.ff +165 -149
  64. package/experimental/benchmarks/ListGrab.ff +23 -23
  65. package/experimental/benchmarks/ListGrab.java +55 -55
  66. package/experimental/benchmarks/Pyrotek45.ff +30 -30
  67. package/experimental/benchmarks/Pyrotek45.java +64 -64
  68. package/experimental/bidirectional/Bidi.ff +88 -88
  69. package/experimental/lines/Main.ff +40 -0
  70. package/experimental/random/Index.ff +53 -53
  71. package/experimental/random/Process.ff +120 -120
  72. package/experimental/random/RunLength.ff +65 -65
  73. package/experimental/random/Scrape.ff +51 -51
  74. package/experimental/random/Symbols.ff +73 -73
  75. package/experimental/random/Tensor.ff +52 -52
  76. package/experimental/random/Units.ff +36 -36
  77. package/experimental/s3/S3TestAuthorizationHeader.ff +39 -39
  78. package/experimental/s3/S3TestPut.ff +16 -16
  79. package/experimental/tests/TestJson.ff +26 -26
  80. package/firefly.sh +0 -0
  81. package/fireflysite/.firefly/package.ff +4 -4
  82. package/fireflysite/CommunityOverview.ff +20 -20
  83. package/fireflysite/CountingButtonDemo.ff +58 -58
  84. package/fireflysite/DocumentParser.ff +325 -331
  85. package/fireflysite/ExamplesOverview.ff +40 -40
  86. package/fireflysite/FrontPage.ff +344 -344
  87. package/fireflysite/GettingStarted.ff +45 -45
  88. package/fireflysite/Guide.ff +456 -456
  89. package/fireflysite/Main.ff +163 -152
  90. package/fireflysite/MatchingPasswordsDemo.ff +82 -82
  91. package/fireflysite/PackagesOverview.ff +49 -49
  92. package/fireflysite/PostgresqlDemo.ff +34 -34
  93. package/fireflysite/ReferenceAll.ff +18 -18
  94. package/fireflysite/ReferenceIntroduction.ff +11 -11
  95. package/fireflysite/Styles.ff +567 -567
  96. package/fireflysite/Test.ff +121 -62
  97. package/fireflysite/assets/markdown/reference/BaseTypes.md +209 -209
  98. package/fireflysite/assets/markdown/reference/EmittedJavascript.md +65 -65
  99. package/fireflysite/assets/markdown/reference/Exceptions.md +101 -101
  100. package/fireflysite/assets/markdown/reference/FunctionsAndMethods.md +364 -364
  101. package/fireflysite/assets/markdown/reference/JavascriptInterop.md +235 -172
  102. package/fireflysite/assets/markdown/reference/ModulesAndPackages.md +162 -162
  103. package/fireflysite/assets/markdown/reference/OldStructuredConcurrency.md +48 -48
  104. package/fireflysite/assets/markdown/reference/PatternMatching.md +224 -224
  105. package/fireflysite/assets/markdown/reference/StatementsAndExpressions.md +86 -86
  106. package/fireflysite/assets/markdown/reference/StructuredConcurrency.md +99 -99
  107. package/fireflysite/assets/markdown/reference/TraitsAndInstances.md +100 -100
  108. package/fireflysite/assets/markdown/reference/UserDefinedTypes.md +184 -184
  109. package/fireflysite/assets/markdown/scratch/ControlFlow.md +136 -136
  110. package/fireflysite/assets/markdown/scratch/Toc.md +40 -40
  111. package/lsp/.firefly/package.ff +1 -1
  112. package/lsp/CompletionHandler.ff +827 -827
  113. package/lsp/Handler.ff +714 -714
  114. package/lsp/HoverHandler.ff +79 -79
  115. package/lsp/LanguageServer.ff +272 -272
  116. package/lsp/SignatureHelpHandler.ff +55 -55
  117. package/lsp/SymbolHandler.ff +181 -181
  118. package/lsp/TestReferences.ff +17 -17
  119. package/lsp/TestReferencesCase.ff +7 -7
  120. package/lsp/stderr.txt +1 -1
  121. package/lsp/stdout.txt +34 -34
  122. package/lux/.firefly/package.ff +1 -1
  123. package/lux/Css.ff +648 -648
  124. package/lux/CssTest.ff +48 -48
  125. package/lux/Lux.ff +608 -617
  126. package/lux/LuxEvent.ff +79 -116
  127. package/lux/Main.ff +123 -123
  128. package/lux/Main2.ff +143 -143
  129. package/lux/TestDry.ff +28 -28
  130. package/output/js/ff/compiler/Builder.mjs +72 -71
  131. package/output/js/ff/compiler/Compiler.mjs +19 -13
  132. package/output/js/ff/compiler/Dependencies.mjs +8 -7
  133. package/output/js/ff/compiler/DependencyLock.mjs +6 -4
  134. package/output/js/ff/compiler/Deriver.mjs +26 -24
  135. package/output/js/ff/compiler/Dictionaries.mjs +14 -18
  136. package/output/js/ff/compiler/Environment.mjs +6 -4
  137. package/output/js/ff/compiler/Inference.mjs +238 -164
  138. package/output/js/ff/compiler/JsEmitter.mjs +1160 -350
  139. package/output/js/ff/compiler/JsImporter.mjs +20 -18
  140. package/output/js/ff/compiler/LspHook.mjs +12 -10
  141. package/output/js/ff/compiler/Main.mjs +61 -41
  142. package/output/js/ff/compiler/ModuleCache.mjs +10 -8
  143. package/output/js/ff/compiler/Parser.mjs +153 -669
  144. package/output/js/ff/compiler/Patterns.mjs +12 -10
  145. package/output/js/ff/compiler/Resolver.mjs +52 -78
  146. package/output/js/ff/compiler/Substitution.mjs +12 -16
  147. package/output/js/ff/compiler/Syntax.mjs +50 -341
  148. package/output/js/ff/compiler/Token.mjs +126 -4
  149. package/output/js/ff/compiler/Tokenizer.mjs +62 -52
  150. package/output/js/ff/compiler/Unification.mjs +74 -90
  151. package/output/js/ff/compiler/Wildcards.mjs +4 -2
  152. package/output/js/ff/compiler/Workspace.mjs +26 -20
  153. package/output/js/ff/core/Any.mjs +20 -20
  154. package/output/js/ff/core/Array.mjs +268 -175
  155. package/output/js/ff/core/AssetSystem.mjs +8 -6
  156. package/output/js/ff/core/Atomic.mjs +84 -52
  157. package/output/js/ff/core/Bool.mjs +6 -4
  158. package/output/js/ff/core/BrowserSystem.mjs +38 -29
  159. package/output/js/ff/core/Buffer.mjs +285 -133
  160. package/output/js/ff/core/BuildSystem.mjs +36 -56
  161. package/output/js/ff/core/Channel.mjs +250 -97
  162. package/output/js/ff/core/Char.mjs +5 -3
  163. package/output/js/ff/core/Core.mjs +28 -34
  164. package/output/js/ff/core/Crypto.mjs +30 -52
  165. package/output/js/ff/core/Duration.mjs +4 -2
  166. package/output/js/ff/core/Equal.mjs +14 -12
  167. package/output/js/ff/core/Error.mjs +17 -11
  168. package/output/js/ff/core/FileHandle.mjs +76 -38
  169. package/output/js/ff/core/Float.mjs +92 -160
  170. package/output/js/ff/core/HttpClient.mjs +208 -76
  171. package/output/js/ff/core/Instant.mjs +8 -10
  172. package/output/js/ff/core/Int.mjs +36 -26
  173. package/output/js/ff/core/IntMap.mjs +79 -33
  174. package/output/js/ff/core/Js.mjs +751 -0
  175. package/output/js/ff/core/JsSystem.mjs +54 -60
  176. package/output/js/ff/core/JsValue.mjs +294 -143
  177. package/output/js/ff/core/Json.mjs +443 -253
  178. package/output/js/ff/core/List.mjs +262 -214
  179. package/output/js/ff/core/Lock.mjs +156 -125
  180. package/output/js/ff/core/Log.mjs +20 -10
  181. package/output/js/ff/core/Map.mjs +10 -8
  182. package/output/js/ff/core/NodeSystem.mjs +189 -123
  183. package/output/js/ff/core/Nothing.mjs +4 -2
  184. package/output/js/ff/core/Option.mjs +40 -38
  185. package/output/js/ff/core/Ordering.mjs +26 -20
  186. package/output/js/ff/core/Pair.mjs +4 -2
  187. package/output/js/ff/core/Path.mjs +517 -315
  188. package/output/js/ff/core/Queue.mjs +306 -0
  189. package/output/js/ff/core/Random.mjs +141 -77
  190. package/output/js/ff/core/RbMap.mjs +36 -34
  191. package/output/js/ff/core/Serializable.mjs +44 -28
  192. package/output/js/ff/core/Set.mjs +6 -4
  193. package/output/js/ff/core/Show.mjs +8 -6
  194. package/output/js/ff/core/SourceLocation.mjs +4 -2
  195. package/output/js/ff/core/Stream.mjs +30 -50
  196. package/output/js/ff/core/String.mjs +263 -172
  197. package/output/js/ff/core/StringMap.mjs +77 -31
  198. package/output/js/ff/core/Task.mjs +91 -76
  199. package/output/js/ff/core/Try.mjs +20 -18
  200. package/output/js/ff/core/Unit.mjs +4 -2
  201. package/package.json +1 -1
  202. package/postgresql/Pg.ff +53 -59
  203. package/rpc/.firefly/package.ff +1 -1
  204. package/rpc/Rpc.ff +70 -70
  205. package/s3/.firefly/package.ff +1 -1
  206. package/s3/S3.ff +92 -94
  207. package/vscode/LICENSE.txt +21 -21
  208. package/vscode/Prepublish.ff +15 -15
  209. package/vscode/README.md +16 -16
  210. package/vscode/client/package-lock.json +544 -544
  211. package/vscode/client/package.json +22 -22
  212. package/vscode/client/src/extension.ts +104 -104
  213. package/vscode/icons/firefly-icon.svg +10 -10
  214. package/vscode/language-configuration.json +61 -61
  215. package/vscode/package-lock.json +3623 -3623
  216. package/vscode/package.json +1 -1
  217. package/vscode/snippets.json +241 -241
  218. package/vscode/syntaxes/firefly-markdown-injection.json +45 -45
  219. package/webserver/.firefly/include/package.json +5 -5
  220. package/webserver/.firefly/package.ff +2 -2
  221. package/webserver/WebServer.ff +647 -685
  222. package/websocket/.firefly/package.ff +1 -1
  223. package/websocket/WebSocket.ff +100 -131
  224. package/core/UnsafeJs.ff +0 -42
  225. package/output/js/ff/core/UnsafeJs.mjs +0 -191
@@ -1,685 +1,647 @@
1
- capability WebServer(
2
- intenalSystem: NodeSystem
3
- internalHost: String
4
- internalPort: Int
5
- mutable internalJsWebSockets: JsValue
6
- mutable internalJsWildcard: JsValue
7
- mutable internalJsHosts: Array[JsValue]
8
- mutable internalJsApp: JsValue
9
- )
10
-
11
- class WebRequest[T](
12
- internalJsApp: JsValue
13
- internalJsContext: JsValue
14
- internalMethod: String
15
- internalPath: String
16
- internalQuery: String
17
- internalHeaders: StringMap[String]
18
- internalJsResponse: JsValue
19
- internalResponse: T
20
- internalResponseHeaders: StringMap[String]
21
- internalResponseChunks: Array[JsValue]
22
- mutable internalResponseStatus: String
23
- mutable internalCloseConnection: Bool
24
- mutable internalResolveFlush: JsValue
25
- mutable internalContentSize: Int
26
- mutable internalResponded: Bool
27
- )
28
-
29
- capability WebResponse {}
30
-
31
- data MultipartField {}
32
-
33
- capability WebSocketConnection {}
34
-
35
- new(
36
- system: NodeSystem
37
- host: String
38
- port: Int
39
- ): WebServer {
40
- WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
41
- }
42
-
43
- extend self: WebServer {
44
-
45
- enableWebSockets(
46
- maxPayloadBytes: Int = 16384
47
- maxBackpressureBytes: Int = 65536
48
- closeDueToMaxBackpressureBytes: Bool = False
49
- maxLifetime: Option[Duration] = None
50
- idleTimeout: Option[Duration] = None
51
- ) {
52
- self.internalJsWebSockets = self.intenalSystem.js().object()
53
- self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
54
- self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
55
- self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
56
- maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
57
- idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
58
- }
59
-
60
- addHost(
61
- hostname: String
62
- key: Option[Path] = None
63
- certificate: Option[Path] = None
64
- certificateAuthority: Option[Path] = None
65
- passphrase: Option[String] = None
66
- dhParameters: Option[Path] = None
67
- ciphers: Option[String] = None
68
- preferLowMemoryUsage: Bool = False
69
- ) {
70
- let object = self.intenalSystem.js().object().with("hostname", hostname)
71
- key.each {object.set("key_file_name", _.absolute())}
72
- certificate.each {object.set("cert_file_name", _.absolute())}
73
- certificateAuthority.each {object.set("ca_file_name", _.absolute())}
74
- passphrase.each {object.set("passphrase", _)}
75
- dhParameters.each {object.set("dh_params_file_name", _.absolute())}
76
- ciphers.each {object.set("ssl_ciphers", _)}
77
- if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
78
- self.internalJsHosts.push(object)
79
- }
80
-
81
- setWildcard(
82
- key: Option[Path] = None
83
- certificate: Option[Path] = None
84
- certificateAuthority: Option[Path] = None
85
- passphrase: Option[String] = None
86
- dhParameters: Option[Path] = None
87
- ciphers: Option[String] = None
88
- preferLowMemoryUsage: Bool = False
89
- ) {
90
- let object = self.intenalSystem.js().object()
91
- key.each {object.set("key_file_name", _.absolute())}
92
- certificate.each {object.set("cert_file_name", _.absolute())}
93
- certificateAuthority.each {object.set("ca_file_name", _.absolute())}
94
- passphrase.each {object.set("passphrase", _)}
95
- dhParameters.each {object.set("dh_params_file_name", _.absolute())}
96
- ciphers.each {object.set("ssl_ciphers", _)}
97
- if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
98
- self.internalJsWildcard = object
99
- }
100
-
101
- listen(handler: WebRequest[WebResponse] => Unit)
102
- target node async """
103
- import * as uws from 'uWebSockets.js'
104
- ff_core_Task.Task_throwIfAborted($task);
105
- await new Promise((resolveServer, rejectServer) => {
106
- self_.internalJsApp_ = self_.internalJsWilcard_ ? uws.SSLApp(self_.internalJsWilcard_)
107
- : self_.internalJsHosts_.array.some(h => h.key_file_name) ? uws.SSLApp() : uws.App();
108
- self_.internalJsHosts_.array.forEach(h => self_.internalJsApp_.addServerName(h.hostname, h));
109
- const handleRequest = (res, req, context) => {
110
- let abortedByUws = false;
111
- const method = req.getMethod();
112
- const path = req.getUrl();
113
- const query = req.getQuery() || '';
114
- const headers = new Map();
115
- req.forEach((key, value) => headers.set(key, value));
116
- const chunks = internalArray_();
117
- const task = ff_core_Task.Task_spawn($task, async task => {
118
- try {
119
- ff_core_Task.Task_throwIfAborted(task);
120
- const webRequest = WebRequest(
121
- self_.internalJsApp_, context,
122
- method, path, query, headers,
123
- res, null,
124
- new Map(), internalArray_(),
125
- "200 OK", false, null, -1, false
126
- );
127
- await handler_(webRequest, task);
128
- ff_core_Task.Task_throwIfAborted(task);
129
- if(context == null && !webRequest.internalResponded_ && webRequest.internalContentSize_ >= 0) {
130
- await WebRequest_flush$(webRequest, task);
131
- ff_core_Task.Task_throwIfAborted(task);
132
- }
133
- if(context == null && !webRequest.internalResponded_) await new Promise((resolve, reject) => {
134
- res.cork(() => {
135
- try {
136
- ff_core_Task.Task_throwIfAborted(task);
137
- if(!webRequest.internalResolveFlush_) {
138
- res.writeStatus(webRequest.internalResponseStatus_);
139
- webRequest.internalResponseHeaders_.forEach((value, key) => res.writeHeader(key, value));
140
- }
141
- const chunk = internalChunksToChunk_(webRequest.internalResponseChunks_);
142
- res.end(chunk, webRequest.internalCloseConnection_);
143
- resolve();
144
- } catch(e) {
145
- reject(e);
146
- }
147
- });
148
- });
149
- } catch(e) {
150
- if(!abortedByUws) console.error(e);
151
- if(!abortedByUws && context == null) res.cork(() => {
152
- if(!abortedByUws) res.writeStatus("500 Internal Server Error");
153
- if(!abortedByUws) res.end();
154
- });
155
- }
156
- });
157
- res.onAborted(() => {
158
- abortedByUws = true;
159
- ff_core_Task.Task_abort(task);
160
- });
161
- }
162
- self_.internalJsApp_.any('/*', handleRequest);
163
- if(self_.internalJsWebSockets_) self_.internalJsApp_.ws("/*", {
164
- ...self_.internalJsWebSockets_,
165
- upgrade: handleRequest,
166
- open: ws => ws.getUserData().onOpen(ws),
167
- message: (ws, m, b) => ws.getUserData().onMessage(m, b),
168
- close: ws => ws.getUserData().onClose(),
169
- });
170
- self_.internalJsApp_.listen(self_.internalHost_, self_.internalPort_, listenSocket => {
171
- if(listenSocket) {
172
- const abort = () => {
173
- $task.controller.signal.removeEventListener('abort', abort);
174
- uws.us_listen_socket_close(listenSocket);
175
- self_.internalJsApp_.close();
176
- }
177
- $task.controller.signal.addEventListener('abort', abort);
178
- } else {
179
- rejectServer();
180
- }
181
- });
182
- });
183
- """
184
-
185
- publishBuffer(topic: String, message: Buffer) // Should this be corked?
186
- target js async """
187
- if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_.buffer, true);
188
- """
189
-
190
- publishText(topic: String, message: String) // Should this be corked?
191
- target js async """
192
- if(self_.internalJsApp_) self_.internalJsApp_.publish(topic_, message_, false);
193
- """
194
-
195
- }
196
-
197
- extend self[T]: WebRequest[T] {
198
-
199
- readMethod(): String {
200
- self.internalMethod
201
- }
202
-
203
- readPath(): String {
204
- self.internalPath
205
- }
206
-
207
- readRawQueryString(): String {
208
- self.internalQuery
209
- }
210
-
211
- readQuery(name: String): Option[String]
212
- target node sync """
213
- if(self_.internalQuery_.length === 0) return ff_core_Option.None();
214
- const ps = self_.internalQuery_.split('&');
215
- const n = name_ + '=';
216
- const r = ps.find(p => p === name_ || p.startsWith(n));
217
- if(r == null) return ff_core_Option.None();
218
- return ff_core_Option.Some(decodeURIComponent(r.slice(name_.length + 1)));
219
- """
220
-
221
- readHeader(lowerCaseName: String): Option[String] {
222
- self.internalHeaders.get(lowerCaseName)
223
- }
224
-
225
- eachHeader(body: (String, String) => Unit) {
226
- self.internalHeaders.each {body(_, _)}
227
- }
228
-
229
- }
230
-
231
- extend self[T]: WebRequest[T] {
232
-
233
- writeStatus(codeAndMessage: String) {
234
- self.internalResponseStatus = codeAndMessage
235
- }
236
-
237
- writeHeader(name: String, value: String) {
238
- self.internalResponseHeaders.set(name, value)
239
- }
240
-
241
- writeContentSize(contentSize: Int) {
242
- if(contentSize >= 0) {
243
- self.internalContentSize = contentSize
244
- }
245
- }
246
-
247
- writeText(data: String) {
248
- self.internalResponseChunks.push(internalTextChunk(data))
249
- }
250
-
251
- writeLine(data: String) {
252
- self.writeText(data + "\n")
253
- }
254
-
255
- writeBuffer(data: Buffer) {
256
- self.internalResponseChunks.push(internalBufferChunk(data))
257
- }
258
-
259
- writeStream(data: Stream[Buffer]) {
260
- data.each {self.writeBuffer(_)}
261
- }
262
-
263
- }
264
-
265
- extend self[T]: WebRequest[T] {
266
-
267
- publishBuffer(topic: String, message: Buffer) // Should this be corked?
268
- target js sync """
269
- self_.internalJsApp_.publish(topic_, message_.buffer, true);
270
- """
271
-
272
- publishText(topic: String, message: String) // Should this be corked?
273
- target js sync """
274
- self_.internalJsApp_.publish(topic_, message_, false);
275
- """
276
-
277
- }
278
-
279
- extend self[T]: WebRequest[T] {
280
-
281
- closeConnection() {
282
- self.internalCloseConnection = True
283
- }
284
-
285
- readRemoteAddress(): String
286
- target js sync """
287
- return new TextDecoder().decode(self_.internalJsResponse_.getRemoteAddressAsText())
288
- """
289
-
290
- readProxiedRemoteAddress(): String
291
- target js sync """
292
- return new TextDecoder().decode(self_.internalJsResponse_.getProxiedRemoteAddressAsText())
293
- """
294
-
295
- }
296
-
297
- extend self: WebRequest[WebResponse] {
298
-
299
- flush()
300
- target js async """
301
- if(!self_.internalResolveFlush_) {
302
- self_.internalResolveFlush_ = () => {};
303
- self_.internalJsResponse_.cork(() => {
304
- self_.internalJsResponse_.writeStatus(self_.internalResponseStatus_);
305
- self_.internalResponseHeaders_.forEach((value, key) =>
306
- self_.internalJsResponse_.writeHeader(key, value)
307
- );
308
- self_.internalJsResponse_.onWritable(() => {
309
- self_.internalResolveFlush_();
310
- return true;
311
- });
312
- });
313
- }
314
- while(self_.internalResponseChunks_.array.length !== 0) {
315
- await new Promise((resolve, reject) => {
316
- self_.internalJsResponse_.cork(() => {
317
- let backpressure = false;
318
- let i = 0;
319
- for(i = 0; i < self_.internalResponseChunks_.array.length && !backpressure; i++) {
320
- let chunk = self_.internalResponseChunks_.array[i];
321
- if(chunk && (chunk.byteLength || chunk.length)) {
322
- if(typeof chunk !== 'string') {
323
- chunk = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
324
- } else if(self_.internalContentSize_ >= 0) {
325
- chunk = new TextEncoder().encode(chunk);
326
- }
327
- if(self_.internalContentSize_ >= 0) {
328
- const lastOffset = self_.internalJsResponse_.getWriteOffset();
329
- const [ok, responded] = self_.internalJsResponse_.tryEnd(chunk, self_.internalContentSize_);
330
- if(responded) self_.internalResponded_ = true;
331
- if(!ok) {
332
- const written = self_.internalJsResponse_.getWriteOffset() - lastOffset;
333
- backpressure = true;
334
- self_.internalResponseChunks_.array[i] =
335
- new Uint8Array(chunk.buffer, chunk.byteOffset + written, chunk.byteLength - written);
336
- i -= 1;
337
- }
338
- } else {
339
- backpressure = !self_.internalJsResponse_.write(chunk);
340
- }
341
- }
342
- }
343
- if(i > 0) self_.internalResponseChunks_.array.splice(0, i);
344
- if(!backpressure) {
345
- resolve();
346
- } else {
347
- self_.internalResolveFlush_ = resolve;
348
- }
349
- });
350
- });
351
- }
352
- """
353
-
354
- flushText(data: String) {
355
- self.writeText(data)
356
- self.flush()
357
- }
358
-
359
- flushBuffer(data: Buffer) {
360
- self.writeBuffer(data)
361
- self.flush()
362
- }
363
-
364
- flushStream(data: Stream[Buffer]) {
365
- data.each {self.flushBuffer(_)}
366
- }
367
-
368
- }
369
-
370
- extend self: WebRequest[WebResponse] {
371
-
372
- readText(encoding: String = "utf8"): String {
373
- self.readStream(1073741823).toString(encoding)
374
- }
375
-
376
- readBuffer(): Buffer {
377
- self.readStream(1073741823).toBuffer()
378
- }
379
-
380
- readStream(maxPendingBytes: Int = 65536): Stream[Buffer]
381
- target js async """
382
- const front = [];
383
- const back = [];
384
- let pendingBytes = 0
385
- let closed = false;
386
- let callResolve = null;
387
- let callReject = null;
388
- self_.internalJsResponse_.onData((chunk, isLast) => {
389
- if(closed) return;
390
- if(isLast) closed = true;
391
- else chunk = cunk.slice();
392
- pendingBytes += chunk.byteLength;
393
- back.push(new DataView(chunk));
394
- if(pendingBytes >= maxPendingBytes_) self_.internalJsResponse_.pause();
395
- if(callResolve) callResolve();
396
- });
397
- const abort = () => {
398
- $task.controller.signal.removeEventListener('abort', abort);
399
- if(callReject) callReject();
400
- }
401
- $task.controller.signal.addEventListener('abort', abort);
402
- return ff_core_Stream.Stream(
403
- async () => {
404
- while(true) {
405
- ff_core_Task.Task_throwIfAborted($task);
406
- if(front.length === 0) {
407
- while(back.length !== 0) front.push(back.pop());
408
- }
409
- if(front.length !== 0) {
410
- const chunk = front.pop();
411
- const paused = pendingBytes >= maxPendingBytes_;
412
- pendingBytes -= chunk.byteLength;
413
- if(!closed && paused && pendingBytes < maxPendingBytes_) self_.internalJsResponse_.unpause();
414
- return ff_core_Option.Some(chunk);
415
- } else if(closed) {
416
- return ff_core_Option.None();
417
- } else {
418
- await new Promise((resolve, reject) => {
419
- callResolve = resolve;
420
- callReject = reject;
421
- });
422
- callResolve = null;
423
- callReject = null;
424
- }
425
- }
426
- },
427
- async () => {
428
- closed = true;
429
- ff_core_Task.Task_throwIfAborted($task);
430
- if(callResolve) callResolve();
431
- }
432
- )
433
- """
434
-
435
- }
436
-
437
- extend self[T]: WebRequest[T] {
438
-
439
- needsWebSocket(): Bool
440
- target js sync """
441
- return self_.internalJsContext_ != null;
442
- """
443
-
444
- }
445
-
446
- extend self: WebRequest[WebResponse] {
447
-
448
- openWebSocket(): WebSocketConnection
449
- target js async """
450
- ff_core_Task.Task_throwIfAborted($task);
451
- const userData = {};
452
- const wrapper = {front: [], back: [], listeners: [], task: $task};
453
- const promise = new Promise((resolve, reject) => {
454
- const abort = () => {wrapper.webSocket = null; reject()};
455
- userData.onOpen = ws => {wrapper.webSocket = ws; resolve(wrapper)};
456
- userData.onClose = () => {
457
- $task.controller.signal.removeEventListener('abort', abort);
458
- abort();
459
- for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
460
- wrapper.listeners.length = 0;
461
- };
462
- userData.onMessage = (message, isBinary) => {
463
- wrapper.back.push(isBinary ? new DataView(message) : new TextDecoder().decode(message));
464
- for(let i = 0; i < wrapper.listeners.length; i++) wrapper.listeners[i]();
465
- wrapper.listeners.length = 0;
466
- };
467
- $task.controller.signal.addEventListener('abort', abort);
468
- });
469
- self_.internalJsResponse_.cork(() => {
470
- ff_core_Task.Task_throwIfAborted($task);
471
- self_.internalJsResponse_.upgrade(
472
- userData,
473
- self_.internalHeaders_.get('sec-websocket-key'),
474
- self_.internalHeaders_.get('sec-websocket-protocol'),
475
- self_.internalHeaders_.get('sec-websocket-extensions'),
476
- self_.internalJsContext_
477
- )
478
- });
479
- return await promise;
480
- """
481
-
482
- }
483
-
484
- extend self: WebSocketConnection {
485
-
486
- readText(encoding: String = "utf8"): Option[String] {
487
- self.readAny {_} {_.toString(encoding)}
488
- }
489
-
490
- readBuffer(): Option[Buffer] {
491
- self.readAny {_.toBuffer()} {_}
492
- }
493
-
494
- readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T]
495
- target node async """
496
- ff_core_Task.Task_throwIfAborted($task);
497
- while(self_.front.length === 0) {
498
- while(self_.back.length !== 0) {
499
- self_.front.push(self_.back.pop());
500
- }
501
- if(self_.front.length !== 0) break;
502
- if(self_.webSocket == null) return ff_core_Option.None();
503
- let abort = null;
504
- try {
505
- await new Promise((resolve, reject) => {
506
- self_.listeners.push(resolve);
507
- if($task !== self_.task) {
508
- abort = () => {
509
- self_.listeners = self_.listeners.filter(l => l != resolve);
510
- reject();
511
- };
512
- $task.controller.signal.addEventListener('abort', abort);
513
- }
514
- });
515
- } finally {
516
- if($task !== self_.task) {
517
- $task.controller.signal.removeEventListener('abort', abort);
518
- }
519
- }
520
- }
521
- const data = self_.front.pop();
522
- if(typeof data === 'string') return ff_core_Option.Some(await fromText_(data));
523
- return ff_core_Option.Some(await fromBuffer_(data));
524
- """
525
-
526
- readStream(): Stream[Buffer] {
527
- Stream {self.readBuffer()} {self.close()}
528
- }
529
-
530
- writeBuffer(message: Buffer)
531
- target node async """
532
- await internalSend_$(self_, () => self_.webSocket.send(message_.buffer, true) !== 2, $task);
533
- """
534
-
535
- writeText(message: String)
536
- target node async """
537
- await internalSend_$(self_, () => self_.webSocket.send(message_, false) !== 2, $task);
538
- """
539
-
540
- publishBuffer(topic: String, message: Buffer)
541
- target node async """
542
- await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_.buffer, true), $task);
543
- """
544
-
545
- publishText(topic: String, message: String)
546
- target node async """
547
- await internalSend_$(self_, () => self_.webSocket.publish(topic_, message_, false), $task);
548
- """
549
-
550
- subscribe(topic: String)
551
- target node async """
552
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
553
- self_.webSocket.subscribe(topic_);
554
- """
555
-
556
- unsubscribe(topic: String)
557
- target node async """
558
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
559
- self_.webSocket.unsubscribe(topic_);
560
- """
561
-
562
- isSubscribedTo(topic: String): Bool
563
- target node async """
564
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
565
- return self_.webSocket.isSubscribed(topic_);
566
- """
567
-
568
- subscriptions(): Array[String]
569
- target node async """
570
- if(self_.webSocket == null) throw new Error("WebSocket is closed");
571
- return self_.webSocket.getTopics();
572
- """
573
-
574
- close(): Unit
575
- target node async """
576
- if(self_.webSocket != null) {
577
- self_.webSocket.close();
578
- self_.webSocket = null;
579
- }
580
- """
581
-
582
- }
583
-
584
- internalSend[T](self: WebSocketConnection, send: () => Bool): JsValue
585
- target node async """
586
- let abort = null;
587
- try {
588
- await new Promise((resolve, reject) => {
589
- if(self_.webSocket == null) new Error("WebSocket is closed");
590
- else self_.webSocket.cork(() => {
591
- if($task !== self_.task) {
592
- abort = () => {
593
- self_.listeners = self_.listeners.filter(l => l != resolve);
594
- reject();
595
- };
596
- $task.controller.signal.addEventListener('abort', abort);
597
- }
598
- if(self_.webSocket == null) {
599
- reject(new Error("WebSocket is closed"));
600
- } else if(!send_()) {
601
- reject(new Error("Dropped message due to backpressure"));
602
- } else {
603
- resolve();
604
- }
605
- })
606
- });
607
- } finally {
608
- if($task !== self_.task) {
609
- $task.controller.signal.removeEventListener('abort', abort);
610
- }
611
- }
612
- """
613
-
614
- internalArray[T](): Array[T] {
615
- Array.new()
616
- }
617
-
618
- internalTextChunk(data: String): JsValue
619
- target js sync """
620
- return data_;
621
- """
622
-
623
- internalBufferChunk(data: Buffer): JsValue
624
- target js sync """
625
- return data_;
626
- """
627
-
628
- internalChunksToChunk(chunks: Array[JsValue]): JsValue
629
- target js sync """
630
- const chunks = chunks_.array;
631
- if(chunks.length === 0) return;
632
- const firstIsString = typeof chunks[0] === 'string';
633
- if(firstIsString && chunks.length === 1) return chunks[0];
634
- if(firstIsString && chunks.every(c => typeof c === 'string')) {
635
- let result = chunks[0];
636
- for(let i = 1; i < chunks.length; i++) {
637
- result += chunks[i];
638
- }
639
- return result;
640
- }
641
- let totalLength = 0;
642
- for(let i = 0; i < chunks.length; i++) {
643
- if(typeof chunks[i] === 'string') chunks[i] = new TextEncoder().encode(chunks[i]);
644
- totalLength += chunks[i].byteLength;
645
- }
646
- const result = new Uint8Array(totalLength);
647
- let offset = 0;
648
- for(let i = 0; i < chunks.length; i++) {
649
- const chunk = chunks[i];
650
- const uint8Array = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
651
- result.set(uint8Array, offset);
652
- offset += chunk.byteLength;
653
- }
654
- return result;
655
- """
656
-
657
- parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]]
658
- target js sync """
659
- const parts = getParts(data_, contentType_);
660
- return parts == null ? ff_core_Option.None() : ff_core_Option.Some(parts);
661
- """
662
-
663
- extend self: MultipartField {
664
-
665
- data(): Buffer
666
- target js sync """
667
- return new DataView(self_.data);
668
- """
669
-
670
- name(): String
671
- target js sync """
672
- return self_.name;
673
- """
674
-
675
- type(): Option[String]
676
- target js sync """
677
- return self_.type == null ? ff_core_Option.None() : ff_core_Option.Some(self_.type);
678
- """
679
-
680
- filename(): Option[String]
681
- target js sync """
682
- return self_.filename == null ? ff_core_Option.None() : ff_core_Option.Some(self_.filename);
683
- """
684
-
685
- }
1
+ capability WebServer(
2
+ intenalSystem: NodeSystem
3
+ internalHost: String
4
+ internalPort: Int
5
+ mutable internalJsWebSockets: JsValue
6
+ mutable internalJsWildcard: JsValue
7
+ mutable internalJsHosts: Array[JsValue]
8
+ mutable internalJsApp: JsValue
9
+ )
10
+
11
+ class WebRequest[T](
12
+ internalJsApp: JsValue
13
+ internalJsContext: JsValue
14
+ internalMethod: String
15
+ internalPath: String
16
+ internalQuery: String
17
+ internalHeaders: StringMap[String]
18
+ internalJsResponse: JsValue
19
+ internalResponse: Option[T]
20
+ internalResponseHeaders: StringMap[String]
21
+ internalResponseChunks: Array[JsValue]
22
+ mutable internalResponseStatus: String
23
+ mutable internalCloseConnection: Bool
24
+ mutable internalResolveFlush: JsValue
25
+ mutable internalContentSize: Int
26
+ mutable internalResponded: Bool
27
+ )
28
+
29
+ capability WebResponse {}
30
+
31
+ data MultipartField {}
32
+
33
+ capability WebSocketConnection(
34
+ messages: Queue[JsValue]
35
+ listeners: Queue[Unit => Unit]
36
+ mutable webSocket: JsValue
37
+ )
38
+
39
+ new(
40
+ system: NodeSystem
41
+ host: String
42
+ port: Int
43
+ ): WebServer {
44
+ WebServer(system, host, port, system.js().null(), system.js().null(), [].toArray(), system.js().null())
45
+ }
46
+
47
+ extend self: WebServer {
48
+
49
+ enableWebSockets(
50
+ maxPayloadBytes: Int = 16384
51
+ maxBackpressureBytes: Int = 65536
52
+ closeDueToMaxBackpressureBytes: Bool = False
53
+ maxLifetime: Option[Duration] = None
54
+ idleTimeout: Option[Duration] = None
55
+ ) {
56
+ self.internalJsWebSockets = self.intenalSystem.js().object()
57
+ self.internalJsWebSockets.set("maxPayloadLength", maxPayloadBytes)
58
+ self.internalJsWebSockets.set("maxBackpressureBytes", maxBackpressureBytes)
59
+ self.internalJsWebSockets.set("closeOnBackpressureLimit", closeDueToMaxBackpressureBytes)
60
+ maxLifetime.each {self.internalJsWebSockets.set("maxLifetime", (_.seconds / 60).round().toInt())}
61
+ idleTimeout.each {self.internalJsWebSockets.set("idleTimeout", _.seconds.round().toInt())}
62
+ }
63
+
64
+ addHost(
65
+ hostname: String
66
+ key: Option[Path] = None
67
+ certificate: Option[Path] = None
68
+ certificateAuthority: Option[Path] = None
69
+ passphrase: Option[String] = None
70
+ dhParameters: Option[Path] = None
71
+ ciphers: Option[String] = None
72
+ preferLowMemoryUsage: Bool = False
73
+ ) {
74
+ let object = self.intenalSystem.js().object().with("hostname", hostname)
75
+ key.each {object.set("key_file_name", _.absolute())}
76
+ certificate.each {object.set("cert_file_name", _.absolute())}
77
+ certificateAuthority.each {object.set("ca_file_name", _.absolute())}
78
+ passphrase.each {object.set("passphrase", _)}
79
+ dhParameters.each {object.set("dh_params_file_name", _.absolute())}
80
+ ciphers.each {object.set("ssl_ciphers", _)}
81
+ if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
82
+ self.internalJsHosts.push(object)
83
+ }
84
+
85
+ setWildcard(
86
+ key: Option[Path] = None
87
+ certificate: Option[Path] = None
88
+ certificateAuthority: Option[Path] = None
89
+ passphrase: Option[String] = None
90
+ dhParameters: Option[Path] = None
91
+ ciphers: Option[String] = None
92
+ preferLowMemoryUsage: Bool = False
93
+ ) {
94
+ let object = self.intenalSystem.js().object()
95
+ key.each {object.set("key_file_name", _.absolute())}
96
+ certificate.each {object.set("cert_file_name", _.absolute())}
97
+ certificateAuthority.each {object.set("ca_file_name", _.absolute())}
98
+ passphrase.each {object.set("passphrase", _)}
99
+ dhParameters.each {object.set("dh_params_file_name", _.absolute())}
100
+ ciphers.each {object.set("ssl_ciphers", _)}
101
+ if(preferLowMemoryUsage) {object.set("ssl_prefer_low_memory_usage", True)}
102
+ self.internalJsWildcard = object
103
+ }
104
+
105
+ listen(handler: WebRequest[WebResponse] => Unit) {
106
+ let uws = Js.import("uWebSockets.js")
107
+ Js.awaitCancellablePromise {resolveServer, rejectServer, onSettleServer =>
108
+ self.internalJsApp = if(!self.internalJsWildcard.isNullOrUndefined()) {
109
+ uws->SSLApp(self.internalJsWildcard)
110
+ } elseIf {self.internalJsHosts.any {h => h->"key_file_name".truthy()}} {
111
+ uws->SSLApp()
112
+ } else {
113
+ uws->App()
114
+ }
115
+ self.internalJsHosts.each {h =>
116
+ self.internalJsApp->addServerName(h->hostname, h)
117
+ }
118
+ let handleRequest = Js->{res, req, context =>
119
+ mutable abortedByUws = False
120
+ let method = req->getMethod()?
121
+ let path = req->getUrl()?
122
+ let query = req->getQuery().coalesce("")?
123
+ let headers = StringMap.new()
124
+ req->forEach(Js->{key, value => headers.set(key?, value?)})
125
+ let chunks = Array.new()
126
+ let task = Js.currentTask().spawn {task =>
127
+ try {
128
+ task.throwIfAborted()
129
+ let webRequest = WebRequest(
130
+ internalJsApp = self.internalJsApp
131
+ internalJsContext = context
132
+ internalMethod = method
133
+ internalPath = path
134
+ internalQuery = query
135
+ internalHeaders = headers
136
+ internalJsResponse = res
137
+ internalResponse = None
138
+ internalResponseHeaders = StringMap.new()
139
+ internalResponseChunks = Array.new()
140
+ internalResponseStatus = "200 OK"
141
+ internalCloseConnection = False
142
+ internalResolveFlush = Js.null()
143
+ internalContentSize = -1
144
+ internalResponded = False
145
+ );
146
+ handler(webRequest)
147
+ task.throwIfAborted()
148
+ if(context.nullish() && !webRequest.internalResponded && webRequest.internalContentSize >= 0) {
149
+ webRequest.flush()
150
+ task.throwIfAborted()
151
+ }
152
+ if(context.nullish() && !webRequest.internalResponded) {
153
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
154
+ res->cork(Js->{
155
+ try {
156
+ // Js.throwIfCancelled() <- TODO ignored in sync context
157
+ if(webRequest.internalResolveFlush.falsy()) {
158
+ res->writeStatus(webRequest.internalResponseStatus)
159
+ webRequest.internalResponseHeaders.each {key, value =>
160
+ res->writeHeader(key, value)
161
+ }
162
+ }
163
+ let chunk = internalChunksToChunk(webRequest.internalResponseChunks)
164
+ res->end(chunk, webRequest.internalCloseConnection)
165
+ resolve(Unit)
166
+ } catchAny {e =>
167
+ reject(e)
168
+ }
169
+ })
170
+ }
171
+ }
172
+ } catchAny {e =>
173
+ if(!abortedByUws) {Js->console->error(e)}
174
+ if(!abortedByUws && context.nullish()) {
175
+ res->cork(Js->{
176
+ if(!abortedByUws) {res->writeStatus("500 Internal Server Error")}
177
+ if(!abortedByUws) {res->end()}
178
+ })
179
+ }
180
+ }
181
+ }
182
+ res->onAborted(Js->{
183
+ abortedByUws = True
184
+ task.abort()
185
+ })
186
+ }
187
+ self.internalJsApp->any("/*", handleRequest)
188
+ if(!self.internalJsWebSockets.nullish()) {
189
+ self.internalJsApp->ws("/*", self.internalJsWebSockets
190
+ .with("upgrade", handleRequest)
191
+ .with("open", Js->{ws => ws->getUserData()->onOpen(ws)})
192
+ .with("message", Js->{ws, m, b => ws->getUserData()->onMessage(m, b)})
193
+ .with("close", Js->{ws => ws->getUserData()->onClose()})
194
+ )
195
+ }
196
+ self.internalJsApp->listen(self.internalHost, self.internalPort, Js->{listenSocket =>
197
+ if(!listenSocket.nullish()) {
198
+ mutable abort = Js->{}
199
+ abort = Js->{
200
+ Js.currentTask().controller->signal->removeEventListener("abort", abort)
201
+ uws->"us_listen_socket_close"(listenSocket)
202
+ self.internalJsApp->close()
203
+ }
204
+ Js.currentTask().controller->signal->addEventListener("abort", abort)
205
+ } else {
206
+ rejectServer(Js->Error->("listenSocket was nullish")?)
207
+ }
208
+ Unit
209
+ })
210
+ }
211
+ }
212
+
213
+ publishBuffer(topic: String, message: Buffer) { // Should this be corked?
214
+ if(!self.internalJsApp.nullish()) {
215
+ self.internalJsApp->publish(topic, message!->buffer, True)
216
+ }
217
+ }
218
+
219
+ publishText(topic: String, message: String) { // Should this be corked?
220
+ if(!self.internalJsApp.nullish()) {
221
+ self.internalJsApp->publish(topic, message, False)
222
+ }
223
+ }
224
+ }
225
+
226
+ extend self[T]: WebRequest[T] {
227
+
228
+ readMethod(): String {
229
+ self.internalMethod
230
+ }
231
+
232
+ readPath(): String {
233
+ self.internalPath
234
+ }
235
+
236
+ readRawQueryString(): String {
237
+ self.internalQuery
238
+ }
239
+
240
+ readQuery(name: String): Option[String] {
241
+ if(self.internalQuery.size() == 0) {None} else:
242
+ let ps = self.internalQuery.split('&')
243
+ let n = name + "="
244
+ ps.find {p => p == name || p.startsWith(n)}.map {r =>
245
+ Js->decodeURIComponent(r.dropFirst(n.size()))?
246
+ }
247
+ }
248
+
249
+ readHeader(lowerCaseName: String): Option[String] {
250
+ self.internalHeaders.get(lowerCaseName)
251
+ }
252
+
253
+ eachHeader(body: (String, String) => Unit) {
254
+ self.internalHeaders.each {body(_, _)}
255
+ }
256
+
257
+ }
258
+
259
+ extend self[T]: WebRequest[T] {
260
+
261
+ writeStatus(codeAndMessage: String) {
262
+ self.internalResponseStatus = codeAndMessage
263
+ }
264
+
265
+ writeHeader(name: String, value: String) {
266
+ self.internalResponseHeaders.set(name, value)
267
+ }
268
+
269
+ writeContentSize(contentSize: Int) {
270
+ if(contentSize >= 0) {
271
+ self.internalContentSize = contentSize
272
+ }
273
+ }
274
+
275
+ writeText(data: String) {
276
+ self.internalResponseChunks.push(data!)
277
+ }
278
+
279
+ writeLine(data: String) {
280
+ self.writeText(data + "\n")
281
+ }
282
+
283
+ writeBuffer(data: Buffer) {
284
+ self.internalResponseChunks.push(data!)
285
+ }
286
+
287
+ writeStream(data: Stream[Buffer]) {
288
+ data.each {self.writeBuffer(_)}
289
+ }
290
+
291
+ }
292
+
293
+ extend self[T]: WebRequest[T] {
294
+
295
+ publishBuffer(topic: String, message: Buffer) { // Should this be corked?
296
+ self.internalJsApp->publish(topic, message!->buffer, True)
297
+ }
298
+
299
+ publishText(topic: String, message: String) { // Should this be corked?
300
+ self.internalJsApp->publish(topic, message, False)
301
+ }
302
+
303
+ }
304
+
305
+ extend self[T]: WebRequest[T] {
306
+
307
+ closeConnection() {
308
+ self.internalCloseConnection = True
309
+ }
310
+
311
+ readRemoteAddress(): String {
312
+ Js->TextDecoder->()->decode(self.internalJsResponse->getRemoteAddressAsText())?
313
+ }
314
+
315
+ readProxiedRemoteAddress(): String {
316
+ Js->TextDecoder->()->decode(self.internalJsResponse->getProxiedRemoteAddressAsText())?
317
+ }
318
+
319
+ }
320
+
321
+ extend self: WebRequest[WebResponse] {
322
+
323
+ flush() {
324
+ if(self.internalResolveFlush.nullish()) {
325
+ self.internalResolveFlush = Js->{}
326
+ self.internalJsResponse->cork(Js->{
327
+ self.internalJsResponse->writeStatus(self.internalResponseStatus)
328
+ self.internalResponseHeaders.each {key, value =>
329
+ self.internalJsResponse->writeHeader(key, value)
330
+ }
331
+ self.internalJsResponse->onWritable(Js->{
332
+ self.internalResolveFlush.callValue0()
333
+ True
334
+ })
335
+ })
336
+ }
337
+ while {!self.internalResponseChunks.isEmpty()} {
338
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
339
+ self.internalJsResponse->cork(Js->{
340
+ mutable backpressure = False
341
+ mutable i = 0
342
+ while {i < self.internalResponseChunks.size() && !backpressure} {
343
+ let rawChunk = self.internalResponseChunks.grab(i)
344
+ let chunk = if(rawChunk.typeof() != "string") {
345
+ Js->Uint8Array->(rawChunk!->buffer, rawChunk!->byteOffset, rawChunk!->byteLength)
346
+ } else {
347
+ Js->TextEncoder->()->encode(rawChunk)
348
+ }
349
+ if(self.internalContentSize >= 0) {
350
+ let lastOffset = self.internalJsResponse->getWriteOffset()?
351
+ let pair = self.internalJsResponse->tryEnd(chunk, self.internalContentSize)
352
+ let ok = pair.get(0)
353
+ let responded = pair.get(1)
354
+ if(!ok?) {
355
+ let written = self.internalJsResponse->getWriteOffset() - lastOffset
356
+ backpressure = True
357
+ self.internalResponseChunks.set(i, Js->Uint8Array->(chunk->buffer, chunk->byteOffset + written, chunk->byteLength - written))
358
+ i -= 1
359
+ }
360
+ } else {
361
+ backpressure = !self.internalJsResponse->write(chunk)?
362
+ }
363
+ i += 1
364
+ }
365
+ if(i > 0) {
366
+ self.internalResponseChunks.delete(0, i)
367
+ }
368
+ if(!backpressure) {
369
+ resolve(Unit)
370
+ } else {
371
+ self.internalResolveFlush = Js->{resolve(Unit)}
372
+ }
373
+ })
374
+ }
375
+ }
376
+ }
377
+
378
+ flushText(data: String) {
379
+ self.writeText(data)
380
+ self.flush()
381
+ }
382
+
383
+ flushBuffer(data: Buffer) {
384
+ self.writeBuffer(data)
385
+ self.flush()
386
+ }
387
+
388
+ flushStream(data: Stream[Buffer]) {
389
+ data.each {self.flushBuffer(_)}
390
+ }
391
+
392
+ }
393
+
394
+ extend self: WebRequest[WebResponse] {
395
+
396
+ readText(encoding: String = "utf8"): String {
397
+ self.readStream(1073741823).toString(encoding)
398
+ }
399
+
400
+ readBuffer(): Buffer {
401
+ self.readStream(1073741823).toBuffer()
402
+ }
403
+
404
+ readStream(maxPendingBytes: Int = 65536): Stream[Buffer] {
405
+ let queue = Queue.new[Buffer]()
406
+ mutable pendingBytes = 0
407
+ mutable closed = False
408
+ mutable callResolve = {_ => }
409
+ self.internalJsResponse->onData(Js->{chunk, isLast =>
410
+ if(closed) {} else:
411
+ mutable newChunk = chunk
412
+ if(isLast.truthy()) {
413
+ closed = True
414
+ } else {
415
+ newChunk = chunk->slice()
416
+ }
417
+ pendingBytes += newChunk->byteLength?
418
+ queue.push(Js->DataView->(newChunk)?)
419
+ if(pendingBytes >= maxPendingBytes) {self.internalJsResponse->pause()}
420
+ callResolve(Unit)
421
+ })
422
+ Stream {
423
+ mutable result = None
424
+ doWhile {
425
+ Js.throwIfCancelled()
426
+ if(!queue.isEmpty()) {
427
+ let chunk = queue.pop().grab()
428
+ let paused = pendingBytes >= maxPendingBytes
429
+ pendingBytes -= chunk.size()
430
+ if(!closed && paused && pendingBytes < maxPendingBytes) {
431
+ self.internalJsResponse->unpause()
432
+ }
433
+ result = Some(chunk)
434
+ False
435
+ } elseIf {closed} {
436
+ False
437
+ } else {
438
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
439
+ callResolve = resolve
440
+ }
441
+ True
442
+ }
443
+ }
444
+ result
445
+ } {
446
+ closed = True
447
+ callResolve(Unit)
448
+ }
449
+ }
450
+
451
+ }
452
+
453
+ extend self[T]: WebRequest[T] {
454
+
455
+ needsWebSocket(): Bool {
456
+ !self.internalJsContext.nullish()
457
+ }
458
+
459
+ }
460
+
461
+ extend self: WebRequest[WebResponse] {
462
+
463
+ openWebSocket(): WebSocketConnection {
464
+ let connection = WebSocketConnection(
465
+ messages = Queue.new()
466
+ listeners = Queue.new()
467
+ webSocket = Js.null()
468
+ )
469
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
470
+ let userData = Js->(
471
+ onOpen = Js->{webSocket =>
472
+ connection.webSocket = webSocket
473
+ resolve(connection)
474
+ }
475
+ onClose = Js->{
476
+ connection.webSocket = Js.null()
477
+ reject(Js->Error->("WebSocket closed")?)
478
+ connection.listeners.each {_, listener => listener(Unit)}
479
+ connection.listeners.clear()
480
+ }
481
+ onMessage = Js->{message, isBinary =>
482
+ connection.messages.push(if(isBinary.truthy()) {
483
+ Js->DataView->(message)
484
+ } else {
485
+ Js->TextDecoder->()->decode(message)
486
+ })
487
+ connection.listeners.each {_, listener => listener(Unit)}
488
+ connection.listeners.clear()
489
+ }
490
+ )
491
+ self.internalJsResponse->cork(Js->{
492
+ self.internalJsResponse->upgrade(
493
+ userData
494
+ self.internalHeaders.grab("sec-websocket-key")
495
+ self.internalHeaders.grab("sec-websocket-protocol")
496
+ self.internalHeaders.grab("sec-websocket-extensions")
497
+ self.internalJsContext
498
+ )
499
+ })
500
+ }
501
+ }
502
+
503
+ }
504
+
505
+ extend self: WebSocketConnection {
506
+
507
+ readText(encoding: String = "utf8"): Option[String] {
508
+ self.readAny {_} {_.toString(encoding)}
509
+ }
510
+
511
+ readBuffer(): Option[Buffer] {
512
+ self.readAny {_.toBuffer()} {_}
513
+ }
514
+
515
+ readAny[T](fromText: String => T, fromBuffer: Buffer => T): Option[T] {
516
+ Js.throwIfCancelled()
517
+ while {self.messages.isEmpty() && !self.webSocket.nullish()} {
518
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
519
+ let key = self.listeners.push(resolve)
520
+ onSettle {_ => self.listeners.remove(key)}
521
+ }
522
+ }
523
+ if(self.webSocket.nullish()) {None} else:
524
+ let data = self.messages.pop().grab()
525
+ if(data.typeof() == "string") {
526
+ Some(fromText(data?))
527
+ } else {
528
+ Some(fromBuffer(data?))
529
+ }
530
+ }
531
+
532
+ readStream(): Stream[Buffer] {
533
+ Stream {self.readBuffer()} {self.close()}
534
+ }
535
+
536
+ writeBuffer(message: Buffer) {
537
+ internalSend(self) {self.webSocket->send(message!->buffer, True) !== 2}
538
+ }
539
+
540
+ writeText(message: String) {
541
+ internalSend(self) {self.webSocket->send(message!, True) !== 2}
542
+ }
543
+
544
+ publishBuffer(topic: String, message: Buffer) {
545
+ internalSend(self) {self.webSocket->publish(topic, message!->buffer, True).truthy()}
546
+ }
547
+
548
+ publishText(topic: String, message: String) {
549
+ internalSend(self) {self.webSocket->publish(topic, message!, True).truthy()}
550
+ }
551
+
552
+ subscribe(topic: String) {
553
+ if(self.webSocket.nullish()) {Js.throw(Js->Error->("WebSocket is closed"))}
554
+ self.webSocket->subscribe(topic)
555
+ }
556
+
557
+ unsubscribe(topic: String) {
558
+ if(self.webSocket.nullish()) {Js.throw(Js->Error->("WebSocket is closed"))}
559
+ self.webSocket->unsubscribe(topic)
560
+ }
561
+
562
+ isSubscribedTo(topic: String): Bool {
563
+ if(self.webSocket.nullish()) {Js.throw(Js->Error->("WebSocket is closed"))}
564
+ self.webSocket->isSubscribed(topic)?
565
+ }
566
+
567
+ subscriptions(): List[String] {
568
+ if(self.webSocket.nullish()) {Js.throw(Js->Error->("WebSocket is closed"))}
569
+ self.webSocket->getTopics()?
570
+ }
571
+
572
+ close(): Unit {
573
+ if(!self.webSocket.nullish()) {
574
+ self.webSocket->close()
575
+ self.webSocket = Js.null()
576
+ }
577
+ }
578
+
579
+ }
580
+
581
+ internalSend[T](self: WebSocketConnection, send: () => Bool) {
582
+ Js.awaitCancellablePromise {resolve, reject, onSettle =>
583
+ if(self.webSocket.nullish()) {reject(Js->Error->("WebSocket is closed")?)} else:
584
+ self.webSocket->cork(Js->{
585
+ if(self.webSocket.nullish()) {reject(Js->Error->("WebSocket is closed")?)} else:
586
+ if(!send()) {reject(Js->Error->("Dropped message due to backpressure")?)} else:
587
+ resolve(Unit)
588
+ })
589
+ }
590
+ }
591
+
592
+ internalChunksToChunk(chunks: Array[JsValue]): JsValue {
593
+ if(chunks.size() == 0) {Js.undefined()} else:
594
+ let firstChunk = chunks.grabFirst()
595
+ let firstIsString = firstChunk.typeof() == "string"
596
+ if(firstIsString && chunks.size() == 1) {firstChunk} else:
597
+ if(firstIsString && chunks.all {c => c.typeof() == "string"}) {
598
+ let strings: Array[String] = chunks!?
599
+ strings.join()!
600
+ } else:
601
+
602
+ mutable totalLength = 0
603
+ chunks.eachWithIndex {i, chunk =>
604
+ if(chunk.typeof() == "string") {
605
+ let uint8Array = Js->TextEncoder->()->encode(chunk)
606
+ chunks!->array.set(i, uint8Array)
607
+ totalLength += uint8Array->byteLength?
608
+ } else {
609
+ totalLength += chunk->byteLength?
610
+ }
611
+ }
612
+
613
+ let result = Js->Uint8Array->(totalLength)
614
+ mutable offset = 0
615
+ chunks.each {chunk =>
616
+ let uint8Array = Js->Uint8Array->(chunk->buffer, chunk->byteOffset, chunk->byteLength)
617
+ result->set(uint8Array, offset);
618
+ offset += chunk->byteLength?
619
+ }
620
+ result
621
+ }
622
+
623
+ parseMultipartFields(data: Buffer, contentType: String): Option[Array[MultipartField]] {
624
+ let uws = Js.import("uWebSockets.js")
625
+ let parts = uws->getParts(data, contentType)
626
+ if(!parts.nullish()) {parts?}
627
+ }
628
+
629
+ extend self: MultipartField {
630
+
631
+ data(): Buffer {
632
+ Js->DataView->(self!->data)?
633
+ }
634
+
635
+ name(): String {
636
+ self!->name?
637
+ }
638
+
639
+ type(): Option[String] {
640
+ if(!self!->type.nullish()) {self!->type?}
641
+ }
642
+
643
+ filename(): Option[String] {
644
+ if(!self!->filename.nullish()) {self!->filename?}
645
+ }
646
+
647
+ }