velocious 1.0.0 → 1.0.2

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 (120) hide show
  1. package/README.md +26 -0
  2. package/bin/velocious.mjs +8 -0
  3. package/index.mjs +17 -0
  4. package/package.json +13 -6
  5. package/peak_flow.yml +9 -5
  6. package/spec/cli/generate/migration-spec.mjs +9 -0
  7. package/spec/database/connection/drivers/mysql/{query-parser-spec.cjs → query-parser-spec.mjs} +12 -8
  8. package/spec/database/drivers/mysql/connection-spec.mjs +21 -0
  9. package/spec/database/record/create-spec.mjs +14 -0
  10. package/spec/database/record/destroy-spec.mjs +17 -0
  11. package/spec/database/record/find-spec.mjs +29 -0
  12. package/spec/database/record/update-spec.mjs +15 -0
  13. package/spec/dummy/index.mjs +69 -0
  14. package/spec/dummy/src/config/database.example.mjs +15 -0
  15. package/spec/dummy/src/config/database.peakflow.mjs +15 -0
  16. package/spec/dummy/src/{routes.cjs → config/routes.mjs} +3 -2
  17. package/spec/dummy/src/database/migrations/001-create-tasks.mjs +12 -0
  18. package/spec/dummy/src/models/task.mjs +4 -0
  19. package/spec/dummy/src/routes/tasks/controller.mjs +26 -0
  20. package/spec/http-server/{client-spec.cjs → client-spec.mjs} +12 -5
  21. package/spec/http-server/{get-spec.cjs → get-spec.mjs} +2 -2
  22. package/spec/http-server/post-spec.mjs +72 -0
  23. package/spec/support/jasmine.json +4 -3
  24. package/src/application.mjs +56 -0
  25. package/src/cli/commands/db/create.mjs +14 -0
  26. package/src/cli/commands/generate/migration.mjs +12 -0
  27. package/src/cli/index.mjs +39 -0
  28. package/src/configuration.mjs +27 -0
  29. package/src/{controller.cjs → controller.mjs} +21 -4
  30. package/src/database/drivers/base.mjs +9 -0
  31. package/src/database/drivers/index.mjs +5 -0
  32. package/src/database/drivers/mysql/connect-connection.mjs +12 -0
  33. package/src/database/drivers/mysql/index.mjs +77 -0
  34. package/src/database/drivers/mysql/options.mjs +17 -0
  35. package/src/database/drivers/mysql/query-parser.mjs +25 -0
  36. package/src/database/drivers/mysql/query.mjs +26 -0
  37. package/src/database/drivers/mysql/sql/delete.mjs +19 -0
  38. package/src/database/drivers/mysql/sql/insert.mjs +29 -0
  39. package/src/database/drivers/mysql/sql/update.mjs +31 -0
  40. package/src/database/handler.mjs +11 -0
  41. package/src/database/index.mjs +15 -0
  42. package/src/database/migration/index.mjs +5 -0
  43. package/src/database/migrator/index.mjs +15 -0
  44. package/src/database/pool/index.mjs +43 -0
  45. package/src/database/query/delete-base.mjs +15 -0
  46. package/src/database/query/from-base.mjs +9 -0
  47. package/src/database/query/from-plain.mjs +12 -0
  48. package/src/database/query/{from-table.cjs → from-table.mjs} +2 -2
  49. package/src/database/query/index.mjs +144 -0
  50. package/src/database/query/insert-base.mjs +15 -0
  51. package/src/database/query/join-base.mjs +9 -0
  52. package/src/database/query/join-plain.mjs +12 -0
  53. package/src/database/query/order-base.mjs +9 -0
  54. package/src/database/query/order-plain.mjs +21 -0
  55. package/src/database/query/select-base.mjs +9 -0
  56. package/src/database/query/select-plain.mjs +12 -0
  57. package/src/database/query/{select-table-and-column.cjs → select-table-and-column.mjs} +4 -4
  58. package/src/database/query/update-base.mjs +16 -0
  59. package/src/database/query-parser/{from-parser.cjs → from-parser.mjs} +3 -6
  60. package/src/database/query-parser/{joins-parser.cjs → joins-parser.mjs} +3 -6
  61. package/src/database/query-parser/{options.cjs → options.mjs} +13 -2
  62. package/src/database/query-parser/{select-parser.cjs → select-parser.mjs} +7 -6
  63. package/src/database/record/index.mjs +187 -0
  64. package/src/database/record/record-not-found-error.mjs +1 -0
  65. package/src/{error-logger.js → error-logger.mjs} +1 -1
  66. package/src/http-server/client/{index.cjs → index.mjs} +10 -11
  67. package/src/http-server/client/params-to-object.mjs +68 -0
  68. package/src/http-server/client/request-buffer/form-data-part.mjs +42 -0
  69. package/src/http-server/client/request-buffer/header.mjs +7 -0
  70. package/src/http-server/client/request-buffer/index.mjs +229 -0
  71. package/src/http-server/client/request-parser.mjs +47 -0
  72. package/src/http-server/client/{request-runner.cjs → request-runner.mjs} +5 -5
  73. package/src/http-server/client/request.mjs +15 -0
  74. package/src/http-server/client/{response.cjs → response.mjs} +1 -1
  75. package/src/http-server/index.mjs +137 -0
  76. package/src/http-server/server-client.mjs +47 -0
  77. package/src/http-server/worker-handler/index.mjs +79 -0
  78. package/src/http-server/worker-handler/worker-script.mjs +4 -0
  79. package/src/http-server/worker-handler/{worker-thread.cjs → worker-thread.mjs} +18 -11
  80. package/src/{logger.cjs → logger.mjs} +2 -2
  81. package/src/routes/base-route.mjs +34 -0
  82. package/src/routes/{get-route.cjs → get-route.mjs} +5 -3
  83. package/src/routes/index.mjs +9 -0
  84. package/src/routes/{resolver.cjs → resolver.mjs} +15 -9
  85. package/src/routes/{resource-route.cjs → resource-route.mjs} +9 -5
  86. package/src/routes/root-route.mjs +6 -0
  87. package/bin/velocious +0 -14
  88. package/index.cjs +0 -11
  89. package/spec/dummy/config/databases.example.json +0 -10
  90. package/spec/dummy/config/databases.json +0 -0
  91. package/spec/dummy/config/databases.peakflow.json +0 -11
  92. package/spec/dummy/index.cjs +0 -40
  93. package/spec/dummy/src/models/task.cjs +0 -4
  94. package/spec/dummy/src/routes/tasks/controller.cjs +0 -18
  95. package/src/application.cjs +0 -36
  96. package/src/configuration.cjs +0 -14
  97. package/src/database/connection/drivers/mysql/index.cjs +0 -5
  98. package/src/database/connection/drivers/mysql/options.cjs +0 -7
  99. package/src/database/connection/drivers/mysql/query-parser.cjs +0 -26
  100. package/src/database/connection/index.cjs +0 -2
  101. package/src/database/handler.cjs +0 -5
  102. package/src/database/index.cjs +0 -9
  103. package/src/database/pool/index.cjs +0 -2
  104. package/src/database/query/from-base.cjs +0 -15
  105. package/src/database/query/from-plain.cjs +0 -12
  106. package/src/database/query/index.cjs +0 -59
  107. package/src/database/query/join-base.cjs +0 -15
  108. package/src/database/query/join-plain.cjs +0 -12
  109. package/src/database/query/select-base.cjs +0 -15
  110. package/src/database/query/select-plain.cjs +0 -12
  111. package/src/database/record/index.cjs +0 -5
  112. package/src/http-server/client/request-parser.cjs +0 -92
  113. package/src/http-server/client/request.cjs +0 -25
  114. package/src/http-server/index.cjs +0 -78
  115. package/src/http-server/worker-handler/index.cjs +0 -78
  116. package/src/http-server/worker-handler/socket-handler.cjs +0 -35
  117. package/src/http-server/worker-handler/worker-script.cjs +0 -4
  118. package/src/routes/base-route.cjs +0 -25
  119. package/src/routes/index.cjs +0 -9
  120. package/src/routes/root-route.cjs +0 -4
@@ -0,0 +1,229 @@
1
+ import {EventEmitter} from "events"
2
+ import FormDataPart from "./form-data-part.mjs"
3
+ import Header from "./header.mjs"
4
+ import Incorporator from "incorporator"
5
+ import logger from "../../../logger.mjs"
6
+ import ParamsToObject from "../params-to-object.mjs"
7
+ import querystring from "querystring"
8
+
9
+ export default class RequestBuffer {
10
+ bodyLength = 0
11
+ data = []
12
+ events = new EventEmitter()
13
+ headers = []
14
+ headersByName = {}
15
+ params = {}
16
+ postBody = ""
17
+ postBodyChars = []
18
+ readingBody = false
19
+ state = "status"
20
+
21
+ constructor({configuration}) {
22
+ this.configuration = configuration
23
+ }
24
+
25
+ feed(data) {
26
+ for (const char of data) {
27
+ if (this.readingBody) this.bodyLength += 1
28
+
29
+ switch(this.state) {
30
+ case "status":
31
+ case "headers":
32
+ case "multi-part-form-data":
33
+ case "multi-part-form-data-header":
34
+ this.data.push(char)
35
+
36
+ if (char == 10) {
37
+ const line = String.fromCharCode.apply(null, this.data)
38
+
39
+ this.data = []
40
+ this.parse(line)
41
+ }
42
+
43
+ break
44
+ case "multi-part-form-data-body":
45
+ const body = this.formDataPart.body
46
+
47
+ body.push(char)
48
+
49
+ const possibleBoundaryEndPosition = body.length - this.boundaryLineEnd.length
50
+ const possibleBoundaryEndChars = body.slice(possibleBoundaryEndPosition, body.length)
51
+ const possibleBoundaryEnd = String.fromCharCode.apply(null, possibleBoundaryEndChars)
52
+
53
+ const possibleBoundaryNextPosition = body.length - this.boundaryLineNext.length
54
+ const possibleBoundaryNextChars = body.slice(possibleBoundaryNextPosition, body.length)
55
+ const possibleBoundaryNext = String.fromCharCode.apply(null, possibleBoundaryNextChars)
56
+
57
+ if (possibleBoundaryEnd == this.boundaryLineEnd) {
58
+ this.formDataPart.removeFromBody(possibleBoundaryEnd)
59
+ this.formDataPartDone()
60
+ this.completeRequest()
61
+ } else if (possibleBoundaryNext == this.boundaryLineNext) {
62
+ this.formDataPart.removeFromBody(possibleBoundaryNext)
63
+ this.formDataPartDone()
64
+ this.newFormDataPart()
65
+ } else if (this.contentLength && this.bodyLength >= this.contentLength) {
66
+ this.formDataPartDone()
67
+ this.completeRequest()
68
+ } else if (this.formDataPart.contentLength && this.bodyLength >= this.formDataPart.contentLength) {
69
+ this.formDataPartDone()
70
+
71
+ throw new Error("stub")
72
+ }
73
+
74
+ break
75
+ case "post-body":
76
+ this.bodyLength += 1
77
+ this.postBodyChars.push(char)
78
+
79
+ if (this.contentLength && this.postBodyChars.length >= this.contentLength) {
80
+ this.postRequestDone()
81
+ }
82
+
83
+ break
84
+ default:
85
+ console.error(`Unknown state: ${this.state}`)
86
+ }
87
+ }
88
+ }
89
+
90
+ getHeader = (name) => this.headersByName[name.toLowerCase().trim()]
91
+
92
+ formDataPartDone() {
93
+ const formDataPart = this.formDataPart
94
+
95
+ this.formDataPart = undefined
96
+ formDataPart.finish()
97
+
98
+ this.events.emit("form-data-part", formDataPart)
99
+ }
100
+
101
+ isMultiPartyFormData = () => this.multiPartyFormData
102
+
103
+ newFormDataPart() {
104
+ this.formDataPart = new FormDataPart()
105
+ this.setState("multi-part-form-data-header")
106
+ }
107
+
108
+ parse(line) {
109
+ if (this.state == "status") {
110
+ this.parseStatusLine(line)
111
+ } else if (this.state == "headers") {
112
+ this.parseHeader(line)
113
+ } else if (this.state == "multi-part-form-data") {
114
+ if (line == this.boundaryLine) {
115
+ this.newFormDataPart()
116
+ } else if (line == "\r\n") {
117
+ this.setState("done")
118
+ } else {
119
+ throw new Error(`Expected boundary line but didn't get it: ${line}`)
120
+ }
121
+ } else if (this.state == "multi-part-form-data-header") {
122
+ const header = this.readHeaderFromLine(line)
123
+
124
+ if (header) {
125
+ this.formDataPart.addHeader(header)
126
+ this.state == "multi-part-form-data"
127
+ } else if (line == "\r\n") {
128
+ this.setState("multi-part-form-data-body")
129
+ }
130
+ } else {
131
+ throw new Error(`Unknown state: ${this.state}`)
132
+ }
133
+ }
134
+
135
+ readHeaderFromLine(line) {
136
+ let match
137
+
138
+ if (match = line.match(/^(.+): (.+)\r\n/)) {
139
+ const header = new Header(match[1], match[2])
140
+
141
+ return header
142
+ }
143
+ }
144
+
145
+ parseHeader(line) {
146
+ const header = this.readHeaderFromLine(line)
147
+
148
+ if (header) {
149
+ this.headersByName[header.formattedName] = header
150
+
151
+ if (header.formattedName == "content-length") this.contentLength = parseInt(header.value)
152
+
153
+ this.events.emit("header", header)
154
+ } else if (line == "\r\n") {
155
+ if (this.httpMethod.toUpperCase() == "GET") {
156
+ this.completeRequest()
157
+ } else if (this.httpMethod.toUpperCase() == "POST") {
158
+ this.readingBody = true
159
+
160
+ const match = this.getHeader("content-type").value.match(/^multipart\/form-data;\s*boundary=(.+)$/i)
161
+
162
+ if (match) {
163
+ this.boundary = match[1]
164
+ this.boundaryLine = `--${this.boundary}\r\n`
165
+ this.boundaryLineNext = `\r\n--${this.boundary}\r\n`
166
+ this.boundaryLineEnd = `\r\n--${this.boundary}--`
167
+ this.multiPartyFormData = true
168
+ this.setState("multi-part-form-data")
169
+ } else {
170
+ this.setState("post-body")
171
+ }
172
+ } else {
173
+ throw new Error(`Unknown HTTP method: ${this.httpMethod}`)
174
+ }
175
+ }
176
+ }
177
+
178
+ parseStatusLine(line) {
179
+ const match = line.match(/^(GET|POST) (.+?) HTTP\/1\.1\r\n/)
180
+
181
+ if (!match) {
182
+ throw new Error(`Couldn't match status line from: ${line}`)
183
+ }
184
+
185
+ this.httpMethod = match[1]
186
+ this.path = match[2]
187
+ this.setState("headers")
188
+ }
189
+
190
+ postRequestDone() {
191
+ this.postBody += String.fromCharCode.apply(null, this.postBodyChars)
192
+ this.parseQueryStringPostParams()
193
+ this.completeRequest()
194
+ }
195
+
196
+ setState(newState) {
197
+ logger(this, `Changing state from ${this.state} to ${newState}`)
198
+
199
+ this.state = newState
200
+ }
201
+
202
+ completeRequest = () => {
203
+ this.state = "completed"
204
+
205
+ if (this.getHeader("content-type")?.value?.startsWith("application/json")) {
206
+ this.parseApplicationJsonParams()
207
+ } else if (this.multiPartyFormData) {
208
+ // Done after each new form data part
209
+ }
210
+
211
+ this.events.emit("completed")
212
+ }
213
+
214
+ parseApplicationJsonParams() {
215
+ const newParams = JSON.parse(this.postBody)
216
+ const incorporator = new Incorporator({objects: [this.params, newParams]})
217
+
218
+ incorporator.merge()
219
+ }
220
+
221
+ parseQueryStringPostParams() {
222
+ const unparsedParams = querystring.parse(this.postBody)
223
+ const paramsToObject = new ParamsToObject(unparsedParams)
224
+ const newParams = paramsToObject.toObject()
225
+ const incorporator = new Incorporator({objects: [this.params, newParams]})
226
+
227
+ incorporator.merge()
228
+ }
229
+ }
@@ -0,0 +1,47 @@
1
+ import {digg} from "diggerize"
2
+ import {EventEmitter} from "events"
3
+ import Incorporator from "incorporator"
4
+ import ParamsToObject from "./params-to-object.mjs"
5
+ import RequestBuffer from "./request-buffer/index.mjs"
6
+
7
+ export default class VelociousHttpServerClientRequestParser {
8
+ constructor({configuration}) {
9
+ if (!configuration) throw new Error("No configuration given")
10
+
11
+ this.configuration = configuration
12
+ this.data = []
13
+ this.events = new EventEmitter()
14
+ this.params = {}
15
+
16
+ this.requestBuffer = new RequestBuffer({configuration})
17
+ this.requestBuffer.events.on("completed", this.requestDone)
18
+ this.requestBuffer.events.on("form-data-part", this.onFormDataPart)
19
+ this.requestBuffer.events.on("request-done", this.requestDone)
20
+ }
21
+
22
+ onFormDataPart = (formDataPart) => {
23
+ const unorderedParams = {}
24
+
25
+ unorderedParams[formDataPart.getName()] = formDataPart.getValue()
26
+
27
+ const paramsToObject = new ParamsToObject(unorderedParams)
28
+ const newParams = paramsToObject.toObject()
29
+ const incorporator = new Incorporator({objects: [this.params, newParams]})
30
+
31
+ incorporator.merge()
32
+ }
33
+
34
+ feed = (data) => this.requestBuffer.feed(data)
35
+ getHeader = (name) => this.requestBuffer.getHeader(name)?.value
36
+ getHttpMethod = () => digg(this, "requestBuffer", "httpMethod")
37
+ getHost = () => this.requestBuffer.getHeader("host")?.value
38
+ getPath = () => digg(this, "requestBuffer", "path")
39
+
40
+ requestDone = () => {
41
+ const incorporator = new Incorporator({objects: [this.params, this.requestBuffer.params]})
42
+
43
+ incorporator.merge()
44
+ this.state = "done"
45
+ this.events.emit("done")
46
+ }
47
+ }
@@ -1,9 +1,9 @@
1
- const EventEmitter = require("events")
2
- const logger = require("../../logger.cjs")
3
- const Response = require("./response.cjs")
4
- const RoutesResolver = require("../../routes/resolver.cjs")
1
+ import EventEmitter from "events"
2
+ import logger from "../../logger.mjs"
3
+ import Response from "./response.mjs"
4
+ import RoutesResolver from "../../routes/resolver.mjs"
5
5
 
6
- module.exports = class VelociousHttpServerClientRequestRunner {
6
+ export default class VelociousHttpServerClientRequestRunner {
7
7
  events = new EventEmitter()
8
8
 
9
9
  constructor({configuration, request}) {
@@ -0,0 +1,15 @@
1
+ import {digg} from "diggerize"
2
+ import RequestParser from "./request-parser.mjs"
3
+
4
+ export default class VelociousHttpServerClientRequest {
5
+ constructor({configuration}) {
6
+ this.configuration = configuration
7
+ this.requestParser = new RequestParser({configuration})
8
+ }
9
+
10
+ feed = (data) => this.requestParser.feed(data)
11
+ httpMethod = () => this.requestParser.getHttpMethod()
12
+ host = () => this.requestParser.getHost()
13
+ path = () => this.requestParser.getPath()
14
+ params = () => digg(this, "requestParser", "params")
15
+ }
@@ -1,4 +1,4 @@
1
- module.exports = class VelociousHttpServerClientResponse {
1
+ export default class VelociousHttpServerClientResponse {
2
2
  body = undefined
3
3
  headers = {}
4
4
 
@@ -0,0 +1,137 @@
1
+ import {digg} from "diggerize"
2
+ import logger from "../logger.mjs"
3
+ import Net from "net"
4
+ import ServerClient from "./server-client.mjs"
5
+ import WorkerHandler from "./worker-handler/index.mjs"
6
+
7
+ export default class VelociousHttpServer {
8
+ clientCount = 0
9
+ clients = {}
10
+ workerCount = 0
11
+ workerHandlers = []
12
+
13
+ constructor({configuration, host, maxWorkers, port}) {
14
+ this.configuration = configuration
15
+ this.host = host || "0.0.0.0"
16
+ this.port = port || 3006
17
+ this.maxWorkers = maxWorkers || 16
18
+ }
19
+
20
+ async start() {
21
+ await this._ensureAtLeastOneWorker()
22
+ this.netServer = new Net.Server()
23
+ this.netServer.on("connection", this.onConnection)
24
+ await this._netServerListen()
25
+ }
26
+
27
+ _netServerListen() {
28
+ return new Promise((resolve, reject) => {
29
+ try {
30
+ this.netServer.listen(this.port, this.host, () => {
31
+ logger(this, `Velocious listening on ${this.host}:${this.port}`)
32
+ resolve()
33
+ })
34
+ } catch (error) {
35
+ reject(error)
36
+ }
37
+ })
38
+ }
39
+
40
+ async _ensureAtLeastOneWorker() {
41
+ if (this.workerHandlers.length == 0) {
42
+ await this.spawnWorker()
43
+ }
44
+ }
45
+
46
+ isActive() {
47
+ return this.netServer.listening
48
+ }
49
+
50
+ async stopClients() {
51
+ for (const clientCount in this.clients) {
52
+ const client = this.clients[clientCount]
53
+
54
+ await client.close()
55
+ }
56
+ }
57
+
58
+ stopServer() {
59
+ return new Promise((resolve, reject) => {
60
+ this.netServer.close((error) => {
61
+ if (error) {
62
+ reject(error)
63
+ } else {
64
+ resolve()
65
+ }
66
+ })
67
+ })
68
+ }
69
+
70
+ async stop() {
71
+ await this.stopClients()
72
+ await this.stopServer()
73
+ }
74
+
75
+ onConnection = (socket) => {
76
+ const clientCount = this.clientCount
77
+
78
+ logger(this, `New client ${clientCount}`)
79
+
80
+ this.clientCount++
81
+
82
+ const workerHandler = this.workerHandlerToUse()
83
+ const client = new ServerClient({
84
+ clientCount,
85
+ configuration: this.configuration,
86
+ socket
87
+ })
88
+
89
+ client.events.on("close", this.onClientClose)
90
+
91
+ logger(this, `Gave client ${clientCount} to worker ${workerHandler.workerCount}`)
92
+
93
+ workerHandler.addSocketConnection(client)
94
+
95
+ this.clients[clientCount] = client
96
+ }
97
+
98
+ onClientClose = (client) => {
99
+ const clientCount = digg(client, "clientCount")
100
+ const oldClientsLength = Object.keys(this.clients).length
101
+
102
+ delete this.clients[clientCount]
103
+
104
+ const newClientsLength = Object.keys(this.clients).length
105
+
106
+ if (newClientsLength != (oldClientsLength - 1)) {
107
+ console.error(`Expected client to have been removed but length didn't change from ${oldClientsLength} to ${oldClientsLength - 1}`)
108
+ }
109
+ }
110
+
111
+ async spawnWorker() {
112
+ const workerCount = this.workerCount
113
+
114
+ this.workerCount++
115
+
116
+ const workerHandler = new WorkerHandler({
117
+ configuration: this.configuration,
118
+ workerCount
119
+ })
120
+
121
+ await workerHandler.start()
122
+ this.workerHandlers.push(workerHandler)
123
+ }
124
+
125
+ workerHandlerToUse() {
126
+ logger(this, `Worker handlers length: ${this.workerHandlers.length}`)
127
+
128
+ const randomWorkerNumber = parseInt(Math.random() * this.workerHandlers.length)
129
+ const workerHandler = this.workerHandlers[randomWorkerNumber]
130
+
131
+ if (!workerHandler) {
132
+ throw new Error(`No workerHandler by that number: ${randomWorkerNumber}`)
133
+ }
134
+
135
+ return workerHandler
136
+ }
137
+ }
@@ -0,0 +1,47 @@
1
+ import EventEmitter from "events"
2
+ import logger from "../logger.mjs"
3
+
4
+ export default class ServerClient {
5
+ events = new EventEmitter()
6
+
7
+ constructor({configuration, socket, clientCount}) {
8
+ if (!configuration) throw new Error("No configuration given")
9
+
10
+ this.configuration = configuration
11
+ this.socket = socket
12
+ this.clientCount = clientCount
13
+
14
+ socket.on("end", this.onSocketEnd)
15
+ }
16
+
17
+ listen = () => this.socket.on("data", this.onSocketData)
18
+
19
+ close() {
20
+ return new Promise((resolve, reject) => {
21
+ this.socket.destroy()
22
+ this.events.emit("close", this)
23
+ resolve()
24
+ })
25
+ }
26
+
27
+ onSocketData = (chunk) => {
28
+ logger(this, `Socket ${this.clientCount}: ${chunk}`)
29
+
30
+ this.worker.postMessage({
31
+ command: "clientWrite",
32
+ chunk,
33
+ clientCount: this.clientCount
34
+ })
35
+ }
36
+
37
+ onSocketEnd = () => {
38
+ logger(this, `Socket ${this.clientCount} end`)
39
+ this.events.emit("close", this)
40
+ }
41
+
42
+ send(data) {
43
+ logger(this, "Send", data)
44
+
45
+ this.socket.write(data)
46
+ }
47
+ }
@@ -0,0 +1,79 @@
1
+ import {digg, digs} from "diggerize"
2
+ import {dirname} from "path"
3
+ import {fileURLToPath} from "url"
4
+ import logger from "../../logger.mjs"
5
+ import {Worker} from "worker_threads"
6
+
7
+ export default class VelociousHttpServerWorker {
8
+ constructor({configuration, workerCount}) {
9
+ this.configuration = configuration
10
+ this.clients = {}
11
+ this.workerCount = workerCount
12
+ }
13
+
14
+ async start() {
15
+ return new Promise((resolve) => {
16
+ const {debug, directory} = digs(this.configuration, "debug", "directory")
17
+ const __filename = fileURLToPath(import.meta.url)
18
+ const __dirname = dirname(__filename)
19
+
20
+ this.onStartCallback = resolve
21
+ this.worker = new Worker(`${__dirname}/worker-script.mjs`, {
22
+ workerData: {
23
+ debug,
24
+ directory,
25
+ workerCount: this.workerCount
26
+ }
27
+ })
28
+ this.worker.on("error", this.onWorkerError)
29
+ this.worker.on("exit", this.onWorkerExit)
30
+ this.worker.on("message", this.onWorkerMessage)
31
+ })
32
+ }
33
+
34
+ addSocketConnection(client) {
35
+ const clientCount = digg(client, "clientCount")
36
+
37
+ client.socket.on("end", () => {
38
+ logger(this, `Removing ${clientCount} from clients`)
39
+ delete this.clients[clientCount]
40
+ })
41
+
42
+ client.worker = this.worker
43
+ client.listen()
44
+
45
+ this.clients[clientCount] = client
46
+ this.worker.postMessage({command: "newClient", clientCount})
47
+ }
48
+
49
+ onWorkerError = (error) => {
50
+ throw error // Throws original error with backtrace and everything into the console
51
+ }
52
+
53
+ onWorkerExit = (code) => {
54
+ if (code !== 0) {
55
+ throw new Error(`Client worker stopped with exit code ${code}`)
56
+ }
57
+ }
58
+
59
+ onWorkerMessage = (data) => {
60
+ logger(this, `Worker message`, data)
61
+
62
+ const {command} = digs(data, "command")
63
+
64
+ if (command == "started") {
65
+ this.onStartCallback()
66
+ this.onStartCallback = null
67
+ } else if (command == "clientOutput") {
68
+ logger(this, "CLIENT OUTPUT", data)
69
+
70
+ const {clientCount, output} = digs(data, "clientCount", "output")
71
+
72
+ logger(this, "CLIENT OUTPUT", data)
73
+
74
+ this.clients[clientCount].send(output)
75
+ } else {
76
+ throw new Error(`Unknown command: ${command}`)
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,4 @@
1
+ import {workerData, parentPort} from "worker_threads"
2
+ import WorkerThread from "./worker-thread.mjs"
3
+
4
+ new WorkerThread({parentPort, workerData})
@@ -1,26 +1,33 @@
1
- const Client = require("../client/index.cjs")
2
- const Configuration = require("../../configuration.cjs")
3
- const {digg, digs} = require("diggerize")
4
- const errorLogger = require("../../error-logger")
5
- const logger = require("../../logger.cjs")
6
-
7
- module.exports = class VelociousHttpServerWorkerHandlerWorkerThread {
1
+ import Application from "../../application.mjs"
2
+ import Client from "../client/index.mjs"
3
+ import DatabasePool from "../../database/pool/index.mjs"
4
+ import {digg, digs} from "diggerize"
5
+ import errorLogger from "../../error-logger.mjs"
6
+ import logger from "../../logger.mjs"
7
+
8
+ export default class VelociousHttpServerWorkerHandlerWorkerThread {
8
9
  constructor({parentPort, workerData}) {
9
10
  const {debug, directory, workerCount} = digs(workerData, "debug", "directory", "workerCount")
10
11
 
12
+ this.application = new Application({debug, directory})
13
+ this.databasePool = DatabasePool.current()
11
14
  this.clients = {}
12
- this.configuration = new Configuration({debug, directory})
15
+ this.configuration = this.application.configuration
13
16
  this.parentPort = parentPort
14
17
  this.workerCount = workerCount
15
18
 
16
- parentPort.on("message", errorLogger((data) => this.onCommand(data)))
19
+ parentPort.on("message", errorLogger(this.onCommand))
17
20
 
18
21
  logger(this, `Worker ${workerCount} started`)
19
22
 
20
- parentPort.postMessage({command: "started"})
23
+ this.application.initialize().then(() => {
24
+ this.databasePool.connect().then(() => {
25
+ parentPort.postMessage({command: "started"})
26
+ })
27
+ })
21
28
  }
22
29
 
23
- onCommand(data) {
30
+ onCommand = (data) => {
24
31
  logger(this, `Worker ${this.workerCount} received command`, data)
25
32
 
26
33
  const {command} = data
@@ -1,6 +1,6 @@
1
- const {digg} = require("diggerize")
1
+ import {digg} from "diggerize"
2
2
 
3
- module.exports = function log(object, ...messages) {
3
+ export default function log(object, ...messages) {
4
4
  if (!object.configuration) console.error(`No configuration on ${object.constructor.name}`)
5
5
 
6
6
  if (object.configuration?.debug) {
@@ -0,0 +1,34 @@
1
+ import GetRoute from "./get-route.mjs"
2
+ import ResourceRoute from "./resource-route.mjs"
3
+
4
+ var VelociousBaseRoute
5
+
6
+ export function initBaseRoute() {
7
+ if (VelociousBaseRoute) return
8
+
9
+ VelociousBaseRoute = class VelociousBaseRoute {
10
+ routes = []
11
+
12
+ get(name, args) {
13
+ const route = new GetRoute({name, args})
14
+
15
+ this.routes.push(route)
16
+ }
17
+
18
+ matchWithPath(_path) {
19
+ throw new Error(`No 'matchWithPath' implemented on ${this.constructor.name}`)
20
+ }
21
+
22
+ resources(name, callback) {
23
+ const route = new ResourceRoute({name})
24
+
25
+ this.routes.push(route)
26
+
27
+ if (callback) {
28
+ callback(route)
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ export {VelociousBaseRoute as default}
@@ -1,7 +1,9 @@
1
- const BaseRoute = require("./base-route.cjs")
2
- const escapeStringRegexp = require("escape-string-regexp")
1
+ import BaseRoute, {initBaseRoute} from "./base-route.mjs"
2
+ import escapeStringRegexp from "escape-string-regexp"
3
3
 
4
- module.exports = class VelociousRouteGetRoute extends BaseRoute {
4
+ initBaseRoute()
5
+
6
+ export default class VelociousRouteGetRoute extends BaseRoute {
5
7
  constructor({name}) {
6
8
  super()
7
9
  this.name = name