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.
- package/README.md +26 -0
- package/bin/velocious.mjs +8 -0
- package/index.mjs +17 -0
- package/package.json +13 -6
- package/peak_flow.yml +9 -5
- package/spec/cli/generate/migration-spec.mjs +9 -0
- package/spec/database/connection/drivers/mysql/{query-parser-spec.cjs → query-parser-spec.mjs} +12 -8
- package/spec/database/drivers/mysql/connection-spec.mjs +21 -0
- package/spec/database/record/create-spec.mjs +14 -0
- package/spec/database/record/destroy-spec.mjs +17 -0
- package/spec/database/record/find-spec.mjs +29 -0
- package/spec/database/record/update-spec.mjs +15 -0
- package/spec/dummy/index.mjs +69 -0
- package/spec/dummy/src/config/database.example.mjs +15 -0
- package/spec/dummy/src/config/database.peakflow.mjs +15 -0
- package/spec/dummy/src/{routes.cjs → config/routes.mjs} +3 -2
- package/spec/dummy/src/database/migrations/001-create-tasks.mjs +12 -0
- package/spec/dummy/src/models/task.mjs +4 -0
- package/spec/dummy/src/routes/tasks/controller.mjs +26 -0
- package/spec/http-server/{client-spec.cjs → client-spec.mjs} +12 -5
- package/spec/http-server/{get-spec.cjs → get-spec.mjs} +2 -2
- package/spec/http-server/post-spec.mjs +72 -0
- package/spec/support/jasmine.json +4 -3
- package/src/application.mjs +56 -0
- package/src/cli/commands/db/create.mjs +14 -0
- package/src/cli/commands/generate/migration.mjs +12 -0
- package/src/cli/index.mjs +39 -0
- package/src/configuration.mjs +27 -0
- package/src/{controller.cjs → controller.mjs} +21 -4
- package/src/database/drivers/base.mjs +9 -0
- package/src/database/drivers/index.mjs +5 -0
- package/src/database/drivers/mysql/connect-connection.mjs +12 -0
- package/src/database/drivers/mysql/index.mjs +77 -0
- package/src/database/drivers/mysql/options.mjs +17 -0
- package/src/database/drivers/mysql/query-parser.mjs +25 -0
- package/src/database/drivers/mysql/query.mjs +26 -0
- package/src/database/drivers/mysql/sql/delete.mjs +19 -0
- package/src/database/drivers/mysql/sql/insert.mjs +29 -0
- package/src/database/drivers/mysql/sql/update.mjs +31 -0
- package/src/database/handler.mjs +11 -0
- package/src/database/index.mjs +15 -0
- package/src/database/migration/index.mjs +5 -0
- package/src/database/migrator/index.mjs +15 -0
- package/src/database/pool/index.mjs +43 -0
- package/src/database/query/delete-base.mjs +15 -0
- package/src/database/query/from-base.mjs +9 -0
- package/src/database/query/from-plain.mjs +12 -0
- package/src/database/query/{from-table.cjs → from-table.mjs} +2 -2
- package/src/database/query/index.mjs +144 -0
- package/src/database/query/insert-base.mjs +15 -0
- package/src/database/query/join-base.mjs +9 -0
- package/src/database/query/join-plain.mjs +12 -0
- package/src/database/query/order-base.mjs +9 -0
- package/src/database/query/order-plain.mjs +21 -0
- package/src/database/query/select-base.mjs +9 -0
- package/src/database/query/select-plain.mjs +12 -0
- package/src/database/query/{select-table-and-column.cjs → select-table-and-column.mjs} +4 -4
- package/src/database/query/update-base.mjs +16 -0
- package/src/database/query-parser/{from-parser.cjs → from-parser.mjs} +3 -6
- package/src/database/query-parser/{joins-parser.cjs → joins-parser.mjs} +3 -6
- package/src/database/query-parser/{options.cjs → options.mjs} +13 -2
- package/src/database/query-parser/{select-parser.cjs → select-parser.mjs} +7 -6
- package/src/database/record/index.mjs +187 -0
- package/src/database/record/record-not-found-error.mjs +1 -0
- package/src/{error-logger.js → error-logger.mjs} +1 -1
- package/src/http-server/client/{index.cjs → index.mjs} +10 -11
- package/src/http-server/client/params-to-object.mjs +68 -0
- package/src/http-server/client/request-buffer/form-data-part.mjs +42 -0
- package/src/http-server/client/request-buffer/header.mjs +7 -0
- package/src/http-server/client/request-buffer/index.mjs +229 -0
- package/src/http-server/client/request-parser.mjs +47 -0
- package/src/http-server/client/{request-runner.cjs → request-runner.mjs} +5 -5
- package/src/http-server/client/request.mjs +15 -0
- package/src/http-server/client/{response.cjs → response.mjs} +1 -1
- package/src/http-server/index.mjs +137 -0
- package/src/http-server/server-client.mjs +47 -0
- package/src/http-server/worker-handler/index.mjs +79 -0
- package/src/http-server/worker-handler/worker-script.mjs +4 -0
- package/src/http-server/worker-handler/{worker-thread.cjs → worker-thread.mjs} +18 -11
- package/src/{logger.cjs → logger.mjs} +2 -2
- package/src/routes/base-route.mjs +34 -0
- package/src/routes/{get-route.cjs → get-route.mjs} +5 -3
- package/src/routes/index.mjs +9 -0
- package/src/routes/{resolver.cjs → resolver.mjs} +15 -9
- package/src/routes/{resource-route.cjs → resource-route.mjs} +9 -5
- package/src/routes/root-route.mjs +6 -0
- package/bin/velocious +0 -14
- package/index.cjs +0 -11
- package/spec/dummy/config/databases.example.json +0 -10
- package/spec/dummy/config/databases.json +0 -0
- package/spec/dummy/config/databases.peakflow.json +0 -11
- package/spec/dummy/index.cjs +0 -40
- package/spec/dummy/src/models/task.cjs +0 -4
- package/spec/dummy/src/routes/tasks/controller.cjs +0 -18
- package/src/application.cjs +0 -36
- package/src/configuration.cjs +0 -14
- package/src/database/connection/drivers/mysql/index.cjs +0 -5
- package/src/database/connection/drivers/mysql/options.cjs +0 -7
- package/src/database/connection/drivers/mysql/query-parser.cjs +0 -26
- package/src/database/connection/index.cjs +0 -2
- package/src/database/handler.cjs +0 -5
- package/src/database/index.cjs +0 -9
- package/src/database/pool/index.cjs +0 -2
- package/src/database/query/from-base.cjs +0 -15
- package/src/database/query/from-plain.cjs +0 -12
- package/src/database/query/index.cjs +0 -59
- package/src/database/query/join-base.cjs +0 -15
- package/src/database/query/join-plain.cjs +0 -12
- package/src/database/query/select-base.cjs +0 -15
- package/src/database/query/select-plain.cjs +0 -12
- package/src/database/record/index.cjs +0 -5
- package/src/http-server/client/request-parser.cjs +0 -92
- package/src/http-server/client/request.cjs +0 -25
- package/src/http-server/index.cjs +0 -78
- package/src/http-server/worker-handler/index.cjs +0 -78
- package/src/http-server/worker-handler/socket-handler.cjs +0 -35
- package/src/http-server/worker-handler/worker-script.cjs +0 -4
- package/src/routes/base-route.cjs +0 -25
- package/src/routes/index.cjs +0 -9
- 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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 =
|
|
15
|
+
this.configuration = this.application.configuration
|
|
13
16
|
this.parentPort = parentPort
|
|
14
17
|
this.workerCount = workerCount
|
|
15
18
|
|
|
16
|
-
parentPort.on("message", errorLogger(
|
|
19
|
+
parentPort.on("message", errorLogger(this.onCommand))
|
|
17
20
|
|
|
18
21
|
logger(this, `Worker ${workerCount} started`)
|
|
19
22
|
|
|
20
|
-
|
|
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
|
-
|
|
1
|
+
import {digg} from "diggerize"
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
import BaseRoute, {initBaseRoute} from "./base-route.mjs"
|
|
2
|
+
import escapeStringRegexp from "escape-string-regexp"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
initBaseRoute()
|
|
5
|
+
|
|
6
|
+
export default class VelociousRouteGetRoute extends BaseRoute {
|
|
5
7
|
constructor({name}) {
|
|
6
8
|
super()
|
|
7
9
|
this.name = name
|