velocious 1.0.26 → 1.0.28

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.26",
6
+ "version": "1.0.28",
7
7
  "main": "index.js",
8
8
  "scripts": {
9
9
  "test": "jasmine",
@@ -30,6 +30,7 @@
30
30
  "sqlite3": "^5.1.7"
31
31
  },
32
32
  "dependencies": {
33
+ "bcryptjs": "^3.0.2",
33
34
  "better-localstorage": "^1.0.7",
34
35
  "debounce": "^2.2.0",
35
36
  "diggerize": "^1.0.5",
@@ -40,6 +41,7 @@
40
41
  "inflection": "^3.0.0",
41
42
  "sql-escape-string": "^1.1.0",
42
43
  "sql.js": "^1.12.0",
43
- "strftime": "^0.10.2"
44
+ "strftime": "^0.10.2",
45
+ "uuid": "^11.1.0"
44
46
  }
45
47
  }
@@ -1,4 +1,5 @@
1
1
  import Dummy from "../../dummy/index.js"
2
+ import Project from "../../dummy/src/models/project.js"
2
3
  import Task from "../../dummy/src/models/task.js"
3
4
 
4
5
  describe("Record - create", () => {
@@ -20,4 +21,18 @@ describe("Record - create", () => {
20
21
  expect(project.nameEn()).toEqual("Test project")
21
22
  })
22
23
  })
24
+
25
+ it("creates a new task with an existing project", async () => {
26
+ await Dummy.run(async () => {
27
+ const project = await Project.create({name: "Test project"})
28
+ const task = new Task({name: "Test task", project})
29
+
30
+ await task.save()
31
+
32
+ expect(task.id()).not.toBeUndefined()
33
+ expect(task.name()).toEqual("Test task")
34
+ expect(task.project().id()).toEqual(project.id())
35
+ expect(task.project()).toEqual(project)
36
+ })
37
+ })
23
38
  })
@@ -34,4 +34,52 @@ describe("Record - query", () => {
34
34
  expect(newProject.nameEn()).toEqual("Test project")
35
35
  })
36
36
  })
37
+
38
+ it("finds the first record", async () => {
39
+ await Dummy.run(async () => {
40
+ const taskIDs = []
41
+
42
+ for (let i = 0; i < 5; i++) {
43
+ const task = await Task.create({name: `Task ${i}`})
44
+
45
+ taskIDs.push(task.id())
46
+ }
47
+
48
+ const lastTask = await Task.first()
49
+
50
+ expect(lastTask.id()).toEqual(taskIDs[0])
51
+ })
52
+ })
53
+
54
+ it("finds the last record", async () => {
55
+ await Dummy.run(async () => {
56
+ const taskIDs = []
57
+
58
+ for (let i = 0; i < 5; i++) {
59
+ const task = await Task.create({name: `Task ${i}`})
60
+
61
+ taskIDs.push(task.id())
62
+ }
63
+
64
+ const lastTask = await Task.last()
65
+
66
+ expect(lastTask.id()).toEqual(taskIDs[4])
67
+ })
68
+ })
69
+
70
+ it("counts the records", async () => {
71
+ await Dummy.run(async () => {
72
+ const taskIDs = []
73
+
74
+ for (let i = 0; i < 5; i++) {
75
+ const task = await Task.create({name: `Task ${i}`})
76
+
77
+ taskIDs.push(task.id())
78
+ }
79
+
80
+ const tasksCount = await Task.count()
81
+
82
+ expect(tasksCount).toEqual(5)
83
+ })
84
+ })
37
85
  })
@@ -1,12 +1,13 @@
1
1
  import AppRoutes from "../src/routes/app-routes.js"
2
2
  import {digs} from "diggerize"
3
- import logger from "./logger.js"
3
+ import {Logger} from "./logger.js"
4
4
  import HttpServer from "./http-server/index.js"
5
5
 
6
6
  export default class VelociousApplication {
7
7
  constructor({configuration, httpServer}) {
8
8
  this.configuration = configuration
9
9
  this.httpServerConfiguration = httpServer ?? {}
10
+ this.logger = new Logger(this)
10
11
  }
11
12
 
12
13
  async initialize() {
@@ -40,7 +41,7 @@ export default class VelociousApplication {
40
41
 
41
42
  const port = httpServerConfiguration.port || 3006
42
43
 
43
- logger(this, `Starting server on port ${port}`)
44
+ await this.logger.debug(`Starting server on port ${port}`)
44
45
 
45
46
  this.httpServer = new HttpServer({configuration, port})
46
47
  this.httpServer.events.on("close", this.onHttpServerClose)
@@ -49,13 +50,12 @@ export default class VelociousApplication {
49
50
  }
50
51
 
51
52
  async stop() {
52
- logger(this, "Stopping server")
53
-
53
+ await this.logger.debug("Stopping server")
54
54
  await this.httpServer.stop()
55
55
  }
56
56
 
57
57
  onHttpServerClose = () => {
58
- logger(this, "HTTP server closed")
58
+ this.logger.debug("HTTP server closed")
59
59
 
60
60
  if (this.waitResolve) {
61
61
  this.waitResolve()
package/src/controller.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {digs} from "diggerize"
2
2
  import ejs from "ejs"
3
3
  import * as inflection from "inflection"
4
- import logger from "./logger.js"
4
+ import {Logger} from "./logger.js"
5
5
  import restArgsError from "./utils/rest-args-error.js"
6
6
 
7
7
  export default class VelociousController {
@@ -23,6 +23,7 @@ export default class VelociousController {
23
23
  this._action = action
24
24
  this._controller = controller
25
25
  this._configuration = configuration
26
+ this.logger = new Logger(this)
26
27
  this._params = params
27
28
  this._request = request
28
29
  this._response = response
@@ -31,12 +32,12 @@ export default class VelociousController {
31
32
  }
32
33
 
33
34
  async _runBeforeCallbacks() {
34
- await logger(this, "_runBeforeCallbacks")
35
+ await this.logger.debug("_runBeforeCallbacks")
35
36
 
36
37
  let currentControllerClass = this.constructor
37
38
 
38
39
  while (currentControllerClass) {
39
- await logger(this, `Running callbacks for ${currentControllerClass.name}`)
40
+ await this.logger.debug(`Running callbacks for ${currentControllerClass.name}`)
40
41
 
41
42
  const beforeActions = currentControllerClass._beforeActions
42
43
 
@@ -57,7 +58,7 @@ export default class VelociousController {
57
58
  if (!currentControllerClass?.name?.endsWith("Controller")) break
58
59
  }
59
60
 
60
- await logger(this, "After runBeforeCallbacks")
61
+ await this.logger.debug("After runBeforeCallbacks")
61
62
  }
62
63
 
63
64
  params = () => this._params
@@ -1,10 +1,12 @@
1
1
  import Query from "../query/index.js"
2
2
  import Handler from "../handler.js"
3
+ import {v4 as uuidv4} from "uuid"
3
4
 
4
5
  export default class VelociousDatabaseDriversBase {
5
6
  constructor(config, configuration) {
6
7
  this._args = config
7
8
  this.configuration = configuration
9
+ this._transactionsCount = 0
8
10
  }
9
11
 
10
12
  async createTable(...args) {
@@ -81,16 +83,41 @@ export default class VelociousDatabaseDriversBase {
81
83
  return false
82
84
  }
83
85
 
86
+ async startTransaction() {
87
+ return await this.query("BEGIN TRANSACTION")
88
+ }
89
+
84
90
  async transaction(callback) {
85
- await this.query("BEGIN TRANSACTION")
91
+ const savePointName = `sp${uuidv4().replaceAll("-", "")}`
92
+ let transactionStarted = false
93
+
94
+ if (this._transactionsCount == 0) {
95
+ await this.startTransaction()
96
+ transactionStarted = true
97
+ this._transactionsCount++
98
+ }
99
+
100
+ await this.query(`SAVEPOINT ${savePointName}`)
86
101
 
87
102
  let result
88
103
 
89
104
  try {
90
105
  result = await callback()
91
- await this.query("COMMIT")
106
+
107
+ await this.query(`RELEASE SAVEPOINT ${savePointName}`)
108
+
109
+ if (transactionStarted) {
110
+ await this.query("COMMIT")
111
+ this._transactionsCount--
112
+ }
92
113
  } catch (error) {
93
- this.query("ROLLBACK")
114
+ await this.query(`ROLLBACK TO SAVEPOINT ${savePointName}`)
115
+
116
+ if (transactionStarted) {
117
+ await this.query("ROLLBACK")
118
+ this._transactionsCount--
119
+ }
120
+
94
121
  throw error
95
122
  }
96
123
 
@@ -140,6 +140,10 @@ export default class VelociousDatabaseDriversMysql extends Base{
140
140
  return this._options
141
141
  }
142
142
 
143
+ async startTransaction() {
144
+ return await this.query("START TRANSACTION")
145
+ }
146
+
143
147
  updateSql({conditions, data, tableName}) {
144
148
  const update = new Update({conditions, data, driver: this, tableName})
145
149
 
@@ -2,6 +2,7 @@ import FromPlain from "./from-plain.js"
2
2
  import {incorporate} from "incorporator"
3
3
  import * as inflection from "inflection"
4
4
  import JoinPlain from "./join-plain.js"
5
+ import {Logger} from "../../logger.js"
5
6
  import OrderPlain from "./order-plain.js"
6
7
  import Preloader from "./preloader.js"
7
8
  import RecordNotFoundError from "../record/record-not-found-error.js"
@@ -16,6 +17,7 @@ export default class VelociousDatabaseQuery {
16
17
 
17
18
  this.driver = driver
18
19
  this.handler = handler
20
+ this.logger = new Logger(this)
19
21
  this.modelClass = modelClass
20
22
  this._froms = froms
21
23
  this._groups = groups
@@ -45,6 +47,21 @@ export default class VelociousDatabaseQuery {
45
47
  return newQuery
46
48
  }
47
49
 
50
+ async count() {
51
+ const countQuery = this.clone()
52
+
53
+ countQuery._selects = []
54
+ countQuery.select("COUNT(id) AS count")
55
+
56
+ const results = await countQuery._executeQuery()
57
+
58
+ if (results.length == 1) {
59
+ return results[0].count
60
+ }
61
+
62
+ throw new Error("count multiple stub")
63
+ }
64
+
48
65
  getOptions = () => this.driver.options()
49
66
 
50
67
  async destroyAll() {
@@ -136,7 +153,11 @@ export default class VelociousDatabaseQuery {
136
153
  return this
137
154
  }
138
155
 
139
- last = async () => await this.clone().reverseOrder().first()
156
+ last = async () => {
157
+ const results = await this.clone().reorder("id DESC").limit(1).toArray()
158
+
159
+ return results[0]
160
+ }
140
161
 
141
162
  limit(value) {
142
163
  this._limits.push(value)
@@ -188,10 +209,18 @@ export default class VelociousDatabaseQuery {
188
209
  return this
189
210
  }
190
211
 
191
- async toArray() {
212
+ async _executeQuery() {
192
213
  const sql = this.toSql()
193
214
  const results = await this.driver.query(sql)
215
+
216
+ this.logger.debug("SQL: ", sql)
217
+
218
+ return results
219
+ }
220
+
221
+ async toArray() {
194
222
  const models = []
223
+ const results = await this._executeQuery()
195
224
 
196
225
  for (const result of results) {
197
226
  const model = new this.modelClass()
@@ -41,6 +41,7 @@ export default class VelociousDatabaseRecord {
41
41
  relationship = new BelongsToRelationship(actualData)
42
42
 
43
43
  const buildMethodName = `build${inflection.camelize(relationshipName)}`
44
+ const setMethodName = `set${inflection.camelize(relationshipName)}`
44
45
 
45
46
  this.prototype[relationshipName] = function () {
46
47
  const relationship = this.getRelationshipByName(relationshipName)
@@ -54,6 +55,13 @@ export default class VelociousDatabaseRecord {
54
55
 
55
56
  return record
56
57
  }
58
+
59
+ this.prototype[setMethodName] = function (model) {
60
+ const relationship = this.getRelationshipByName(relationshipName)
61
+
62
+ relationship.setLoaded(model)
63
+ relationship.setDirty(true)
64
+ }
57
65
  } else if (actualData.type == "hasMany") {
58
66
  relationship = new HasManyRelationship(actualData)
59
67
 
@@ -276,19 +284,20 @@ export default class VelociousDatabaseRecord {
276
284
  }
277
285
 
278
286
  async save() {
279
- let result
280
-
281
287
  const isNewRecord = this.isNewRecord()
288
+ let result
282
289
 
283
- await this._autoSaveBelongsToRelationships()
290
+ await this.constructor.transaction(async () => {
291
+ await this._autoSaveBelongsToRelationships()
284
292
 
285
- if (this.isPersisted()) {
286
- result = await this._updateRecordWithChanges()
287
- } else {
288
- result = await this._createNewRecord()
289
- }
293
+ if (this.isPersisted()) {
294
+ result = await this._updateRecordWithChanges()
295
+ } else {
296
+ result = await this._createNewRecord()
297
+ }
290
298
 
291
- await this._autoSaveHasManyRelationships({isNewRecord})
299
+ await this._autoSaveHasManyRelationships({isNewRecord})
300
+ })
292
301
 
293
302
  return result
294
303
  }
@@ -310,6 +319,7 @@ export default class VelociousDatabaseRecord {
310
319
 
311
320
  this.setAttribute(foreignKey, model.id())
312
321
  instanceRelationship.setPreloaded(true)
322
+ instanceRelationship.setDirty(false)
313
323
  }
314
324
  }
315
325
  }
@@ -350,6 +360,10 @@ export default class VelociousDatabaseRecord {
350
360
  this._tableName = tableName
351
361
  }
352
362
 
363
+ static async transaction(callback) {
364
+ return await this.connection().transaction(callback)
365
+ }
366
+
353
367
  static translates(...names) {
354
368
  for (const name of names) {
355
369
  if (!this._translations) this._translations = {}
@@ -457,6 +471,10 @@ export default class VelociousDatabaseRecord {
457
471
  return this._newQuery()
458
472
  }
459
473
 
474
+ static async count() {
475
+ return this._newQuery().count()
476
+ }
477
+
460
478
  static async destroyAll(...args) {
461
479
  return this._newQuery().destroyAll(...args)
462
480
  }
@@ -477,6 +495,10 @@ export default class VelociousDatabaseRecord {
477
495
  return this._newQuery().findOrInitializeBy(...args)
478
496
  }
479
497
 
498
+ static async first() {
499
+ return this._newQuery().first()
500
+ }
501
+
480
502
  static joins(...args) {
481
503
  return this._newQuery().joins(...args)
482
504
  }
@@ -636,14 +658,31 @@ export default class VelociousDatabaseRecord {
636
658
  return this._attributes[attributeNameUnderscore]
637
659
  }
638
660
 
661
+ _belongsToChanges() {
662
+ const belongsToChanges = {}
663
+
664
+ if (this._instanceRelationships) {
665
+ for (const relationshipName in this._instanceRelationships) {
666
+ const relationship = this._instanceRelationships[relationshipName]
667
+
668
+ if (relationship.getType() == "belongsTo" && relationship.getDirty()) {
669
+ belongsToChanges[relationship.getForeignKey()] = relationship.loaded()?.id()
670
+ }
671
+ }
672
+ }
673
+
674
+ return belongsToChanges
675
+ }
676
+
639
677
  async _createNewRecord() {
640
678
  if (!this.constructor.connection()["insertSql"]) {
641
679
  throw new Error(`No insertSql on ${this.constructor.connection().constructor.name}`)
642
680
  }
643
681
 
682
+ const data = Object.assign({}, this._belongsToChanges(), this.attributes())
644
683
  const sql = this._connection().insertSql({
645
684
  tableName: this._tableName(),
646
- data: this.attributes()
685
+ data
647
686
  })
648
687
  await this._connection().query(sql)
649
688
  const id = await this._connection().lastInsertID()
@@ -666,9 +705,11 @@ export default class VelociousDatabaseRecord {
666
705
 
667
706
  conditions[this.constructor.primaryKey()] = this.id()
668
707
 
708
+ const changes = Object.assign({}, this._belongsToChanges(), this._changes)
709
+
669
710
  const sql = this._connection().updateSql({
670
711
  tableName: this._tableName(),
671
- data: this._changes,
712
+ data: changes,
672
713
  conditions
673
714
  })
674
715
  await this._connection().query(sql)
@@ -1,9 +1,16 @@
1
1
  export default class VelociousDatabaseRecordBaseInstanceRelationship {
2
2
  constructor({model, relationship}) {
3
+ this._dirty = false
3
4
  this.model = model
4
5
  this.relationship = relationship
5
6
  }
6
7
 
8
+ setDirty(newValue) {
9
+ this._dirty = newValue
10
+ }
11
+
12
+ getDirty = () => this._dirty
13
+
7
14
  loaded() {
8
15
  if (!this._preloaded && this.model.isPersisted()) {
9
16
  throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
@@ -0,0 +1,28 @@
1
+ import bcryptjs from "bcryptjs"
2
+ import restArgsError from "../../utils/rest-args-error.js"
3
+
4
+ export default class UserModule {
5
+ constructor({secretKey, ...restArgs}) {
6
+ restArgsError(restArgs)
7
+
8
+ if (!secretKey) throw new Error(`Invalid secret key given: ${secretKey}`)
9
+
10
+ this.secretKey = secretKey
11
+ }
12
+
13
+ attachTo(UserClass) {
14
+ UserClass.prototype.setPassword = function(newPassword) {
15
+ const salt = bcryptjs.genSaltSync(10)
16
+ const encryptedPassword = bcryptjs.hashSync(newPassword, salt)
17
+
18
+ this.setEncryptedPassword(encryptedPassword)
19
+ }
20
+
21
+ UserClass.prototype.setPasswordConfirmation = function(newPasswordConfirmation) {
22
+ const salt = bcryptjs.genSaltSync(10)
23
+ const encryptedPassword = bcryptjs.hashSync(newPasswordConfirmation, salt)
24
+
25
+ this._encryptedPasswordConfirmation = encryptedPassword
26
+ }
27
+ }
28
+ }
@@ -1,6 +1,6 @@
1
1
  import {digg} from "diggerize"
2
2
  import {EventEmitter} from "events"
3
- import logger from "../../logger.js"
3
+ import {Logger} from "../../logger.js"
4
4
  import Request from "./request.js"
5
5
  import RequestRunner from "./request-runner.js"
6
6
 
@@ -11,6 +11,7 @@ export default class VeoliciousHttpServerClient {
11
11
  constructor({clientCount, configuration, onExecuteRequest}) {
12
12
  if (!configuration) throw new Error("No configuration given")
13
13
 
14
+ this.logger = new Logger(this)
14
15
  this.clientCount = clientCount
15
16
  this.configuration = configuration
16
17
  this.onExecuteRequest = onExecuteRequest
@@ -18,7 +19,7 @@ export default class VeoliciousHttpServerClient {
18
19
  }
19
20
 
20
21
  executeCurrentRequest = () => {
21
- logger(this, "executeCurrentRequest")
22
+ this.logger.debug("executeCurrentRequest")
22
23
 
23
24
  // We are done parsing the given request and can theoretically start parsing a new one, before the current request is done - so reset the state.
24
25
  this.state = "initial"
@@ -2,7 +2,7 @@ import {EventEmitter} from "events"
2
2
  import FormDataPart from "./form-data-part.js"
3
3
  import Header from "./header.js"
4
4
  import Incorporator from "incorporator"
5
- import logger from "../../../logger.js"
5
+ import {Logger} from "../../../logger.js"
6
6
  import ParamsToObject from "../params-to-object.js"
7
7
  import querystring from "querystring"
8
8
 
@@ -20,6 +20,7 @@ export default class RequestBuffer {
20
20
 
21
21
  constructor({configuration}) {
22
22
  this.configuration = configuration
23
+ this.logger = new Logger(this)
23
24
  }
24
25
 
25
26
  feed(data) {
@@ -185,8 +186,7 @@ export default class RequestBuffer {
185
186
  this.httpMethod = match[1]
186
187
  this.path = match[2]
187
188
  this.setState("headers")
188
-
189
- logger(this, () => ["Parsed status line", {httpMethod: this.httpMethod, path: this.path}])
189
+ this.logger.debug(() => ["Parsed status line", {httpMethod: this.httpMethod, path: this.path}])
190
190
  }
191
191
 
192
192
  postRequestDone() {
@@ -196,8 +196,7 @@ export default class RequestBuffer {
196
196
  }
197
197
 
198
198
  setState(newState) {
199
- logger(this, () => [`Changing state from ${this.state} to ${newState}`])
200
-
199
+ this.logger.debug(() => [`Changing state from ${this.state} to ${newState}`])
201
200
  this.state = newState
202
201
  }
203
202
 
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "events"
2
- import logger from "../../logger.js"
2
+ import {Logger} from "../../logger.js"
3
3
  import Response from "./response.js"
4
4
  import RoutesResolver from "../../routes/resolver.js"
5
5
 
@@ -10,6 +10,7 @@ export default class VelociousHttpServerClientRequestRunner {
10
10
  if (!configuration) throw new Error("No configuration given")
11
11
  if (!request) throw new Error("No request given")
12
12
 
13
+ this.logger = new Logger(this)
13
14
  this.configuration = configuration
14
15
  this.request = request
15
16
  this.response = new Response({configuration})
@@ -25,7 +26,7 @@ export default class VelociousHttpServerClientRequestRunner {
25
26
 
26
27
  try {
27
28
  if (request.header("sec-fetch-mode") == "cors") {
28
- await logger(this, () => ["Run CORS", {httpMethod: request.httpMethod(), secFetchMode: request.header("sec-fetch-mode")}])
29
+ await this.logger.debug(() => ["Run CORS", {httpMethod: request.httpMethod(), secFetchMode: request.header("sec-fetch-mode")}])
29
30
  await configuration.cors({request, response})
30
31
  }
31
32
 
@@ -33,13 +34,13 @@ export default class VelociousHttpServerClientRequestRunner {
33
34
  response.setStatus(200)
34
35
  response.setBody("")
35
36
  } else {
36
- await logger(this, "Run request")
37
+ await this.logger.debug("Run request")
37
38
  const routesResolver = new RoutesResolver({configuration, request, response})
38
39
 
39
40
  await routesResolver.resolve()
40
41
  }
41
42
  } catch (error) {
42
- await logger(this, `Error while running request: ${error.message}`)
43
+ await this.logger.error(() => [`Error while running request: ${error.message}\n\n${error.stack}`])
43
44
 
44
45
  response.setStatus(500)
45
46
  response.setErrorBody(error)
@@ -1,6 +1,6 @@
1
1
  import {digg} from "diggerize"
2
2
  import EventEmitter from "events"
3
- import logger from "../logger.js"
3
+ import {Logger} from "../logger.js"
4
4
  import Net from "net"
5
5
  import ServerClient from "./server-client.js"
6
6
  import WorkerHandler from "./worker-handler/index.js"
@@ -14,6 +14,7 @@ export default class VelociousHttpServer {
14
14
 
15
15
  constructor({configuration, host, maxWorkers, port}) {
16
16
  this.configuration = configuration
17
+ this.logger = new Logger(this)
17
18
  this.host = host || "0.0.0.0"
18
19
  this.port = port || 3006
19
20
  this.maxWorkers = maxWorkers || 16
@@ -31,7 +32,7 @@ export default class VelociousHttpServer {
31
32
  return new Promise((resolve, reject) => {
32
33
  try {
33
34
  this.netServer.listen(this.port, this.host, () => {
34
- logger(this, `Velocious listening on ${this.host}:${this.port}`)
35
+ this.logger.debug(`Velocious listening on ${this.host}:${this.port}`)
35
36
  resolve()
36
37
  })
37
38
  } catch (error) {
@@ -82,8 +83,7 @@ export default class VelociousHttpServer {
82
83
  onConnection = (socket) => {
83
84
  const clientCount = this.clientCount
84
85
 
85
- logger(this, `New client ${clientCount}`)
86
-
86
+ this.logger.debug(`New client ${clientCount}`)
87
87
  this.clientCount++
88
88
 
89
89
  const workerHandler = this.workerHandlerToUse()
@@ -95,10 +95,8 @@ export default class VelociousHttpServer {
95
95
 
96
96
  client.events.on("close", this.onClientClose)
97
97
 
98
- logger(this, `Gave client ${clientCount} to worker ${workerHandler.workerCount}`)
99
-
98
+ this.logger.debug(`Gave client ${clientCount} to worker ${workerHandler.workerCount}`)
100
99
  workerHandler.addSocketConnection(client)
101
-
102
100
  this.clients[clientCount] = client
103
101
  }
104
102
 
@@ -130,7 +128,7 @@ export default class VelociousHttpServer {
130
128
  }
131
129
 
132
130
  workerHandlerToUse() {
133
- logger(this, `Worker handlers length: ${this.workerHandlers.length}`)
131
+ this.logger.debug(`Worker handlers length: ${this.workerHandlers.length}`)
134
132
 
135
133
  const randomWorkerNumber = parseInt(Math.random() * this.workerHandlers.length)
136
134
  const workerHandler = this.workerHandlers[randomWorkerNumber]
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "events"
2
- import logger from "../logger.js"
2
+ import {Logger} from "../logger.js"
3
3
 
4
4
  export default class ServerClient {
5
5
  events = new EventEmitter()
@@ -8,6 +8,7 @@ export default class ServerClient {
8
8
  if (!configuration) throw new Error("No configuration given")
9
9
 
10
10
  this.configuration = configuration
11
+ this.logger = new Logger(this)
11
12
  this.socket = socket
12
13
  this.clientCount = clientCount
13
14
 
@@ -25,7 +26,7 @@ export default class ServerClient {
25
26
  }
26
27
 
27
28
  onSocketData = (chunk) => {
28
- logger(this, () => [`Socket ${this.clientCount}: ${chunk}`])
29
+ this.logger.debug(() => [`Socket ${this.clientCount}: ${chunk}`])
29
30
 
30
31
  this.worker.postMessage({
31
32
  command: "clientWrite",
@@ -35,13 +36,12 @@ export default class ServerClient {
35
36
  }
36
37
 
37
38
  onSocketEnd = () => {
38
- logger(this, `Socket ${this.clientCount} end`)
39
+ this.logger.debug(`Socket ${this.clientCount} end`)
39
40
  this.events.emit("close", this)
40
41
  }
41
42
 
42
43
  send(data) {
43
- logger(this, "Send", data)
44
-
44
+ this.logger.debug("Send", data)
45
45
  this.socket.write(data)
46
46
  }
47
47
  }
@@ -1,13 +1,14 @@
1
1
  import {digg, digs} from "diggerize"
2
2
  import {dirname} from "path"
3
3
  import {fileURLToPath} from "url"
4
- import logger from "../../logger.js"
4
+ import {Logger} from "../../logger.js"
5
5
  import {Worker} from "worker_threads"
6
6
 
7
7
  export default class VelociousHttpServerWorker {
8
8
  constructor({configuration, workerCount}) {
9
9
  this.configuration = configuration
10
10
  this.clients = {}
11
+ this.logger = new Logger(this)
11
12
  this.workerCount = workerCount
12
13
  }
13
14
 
@@ -36,7 +37,7 @@ export default class VelociousHttpServerWorker {
36
37
  const clientCount = digg(client, "clientCount")
37
38
 
38
39
  client.socket.on("end", () => {
39
- logger(this, `Removing ${clientCount} from clients`)
40
+ this.logger.debug(`Removing ${clientCount} from clients`)
40
41
  delete this.clients[clientCount]
41
42
  })
42
43
 
@@ -55,12 +56,12 @@ export default class VelociousHttpServerWorker {
55
56
  if (code !== 0) {
56
57
  throw new Error(`Client worker stopped with exit code ${code}`)
57
58
  } else {
58
- logger(this, () => [`Client worker stopped with exit code ${code}`])
59
+ this.logger.debug(() => [`Client worker stopped with exit code ${code}`])
59
60
  }
60
61
  }
61
62
 
62
63
  onWorkerMessage = (data) => {
63
- logger(this, `Worker message`, data)
64
+ this.logger.debug(`Worker message`, data)
64
65
 
65
66
  const {command} = digs(data, "command")
66
67
 
@@ -68,7 +69,7 @@ export default class VelociousHttpServerWorker {
68
69
  this.onStartCallback()
69
70
  this.onStartCallback = null
70
71
  } else if (command == "clientOutput") {
71
- logger(this, () => ["CLIENT OUTPUT", data])
72
+ this.logger.debug("CLIENT OUTPUT", data)
72
73
 
73
74
  const {clientCount, output} = digs(data, "clientCount", "output")
74
75
 
@@ -2,13 +2,14 @@ import Application from "../../application.js"
2
2
  import Client from "../client/index.js"
3
3
  import {digg, digs} from "diggerize"
4
4
  import errorLogger from "../../error-logger.js"
5
- import logger from "../../logger.js"
5
+ import {Logger} from "../../logger.js"
6
6
 
7
7
  export default class VelociousHttpServerWorkerHandlerWorkerThread {
8
8
  constructor({parentPort, workerData}) {
9
9
  const {workerCount} = digs(workerData, "workerCount")
10
10
 
11
11
  this.clients = {}
12
+ this.logger = new Logger(this)
12
13
  this.parentPort = parentPort
13
14
  this.workerData = workerData
14
15
  this.workerCount = workerCount
@@ -17,7 +18,7 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
17
18
 
18
19
  this.initialize().then(() => {
19
20
  this.application.initialize().then(() => {
20
- logger(this, `Worker ${workerCount} started`)
21
+ this.logger.debug(`Worker ${workerCount} started`)
21
22
  parentPort.postMessage({command: "started"})
22
23
  })
23
24
  })
@@ -39,7 +40,7 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
39
40
  }
40
41
 
41
42
  onCommand = async (data) => {
42
- await logger(this, () => [`Worker ${this.workerCount} received command`, data])
43
+ await this.logger.debug(() => [`Worker ${this.workerCount} received command`, data])
43
44
 
44
45
  const command = data.command
45
46
 
@@ -56,12 +57,12 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
56
57
 
57
58
  this.clients[clientCount] = client
58
59
  } else if (command == "clientWrite") {
59
- await logger(this, "Looking up client")
60
+ await this.logger.debug("Looking up client")
60
61
 
61
62
  const {chunk, clientCount} = digs(data, "chunk", "clientCount")
62
63
  const client = digg(this.clients, clientCount)
63
64
 
64
- await logger(this, `Sending to client ${clientCount}`)
65
+ await this.logger.debug(`Sending to client ${clientCount}`)
65
66
 
66
67
  client.onWrite(chunk)
67
68
  } else {
package/src/logger.js CHANGED
@@ -2,50 +2,88 @@ import Configuration from "./configuration.js"
2
2
 
3
3
  function consoleLog(message) {
4
4
  return new Promise((resolve) => {
5
- process.stdout.write(message, "utf8", resolve)
5
+ process.stdout.write(`${message}\n`, "utf8", resolve)
6
6
  })
7
7
  }
8
8
 
9
- export default async function log(object, ...messages) {
10
- let configuration
9
+ function consoleError(message) {
10
+ return new Promise((resolve) => {
11
+ process.stderr.write(`${message}\n`, "utf8", resolve)
12
+ })
13
+ }
11
14
 
12
- if (object.configuration) {
13
- configuration = object.configuration
14
- } else {
15
- configuration = Configuration.current()
15
+ function functionOrMessages(messages) {
16
+ if (messages.length === 1 && typeof messages[0] == "function") {
17
+ messages = messages[0]()
16
18
  }
17
19
 
18
- if (configuration?.debug) {
19
- try {
20
- if (!object.constructor.name) {
21
- throw new Error(`No constructor name for object`)
22
- }
20
+ return messages
21
+ }
23
22
 
24
- const className = object.constructor.name
23
+ function messagesToMessage(...messages) {
24
+ let message = ""
25
25
 
26
- if (messages.length === 1 && typeof messages[0] == "function") {
27
- messages = messages[0]()
28
- }
26
+ for (const messagePartIndex in messages) {
27
+ const messagePart = messages[messagePartIndex]
29
28
 
30
- let message = ""
29
+ if (messagePartIndex > 0) {
30
+ message += " "
31
+ }
31
32
 
32
- for (const messagePartIndex in messages) {
33
- const messagePart = messages[messagePartIndex]
33
+ if (typeof messagePart == "object") {
34
+ message += JSON.stringify(messagePart)
35
+ } else {
36
+ message += messagePart
37
+ }
38
+ }
34
39
 
35
- if (messagePartIndex > 0) {
36
- message += " "
37
- }
40
+ return message
41
+ }
42
+
43
+ class Logger {
44
+ constructor(object) {
45
+ if (typeof object == "string") {
46
+ this._subject = object
47
+ } else {
48
+ this._object = object
49
+ this._subject = object.constructor.name
50
+ }
38
51
 
39
- if (typeof messagePart == "object") {
40
- message += JSON.stringify(messagePart)
41
- } else {
42
- message += messagePart
43
- }
44
- }
52
+ if (!this._subject) {
53
+ throw new Error(`No subject given`)
54
+ }
55
+ }
45
56
 
46
- await consoleLog(`${className} ${message}\n`)
47
- } catch (error) {
48
- console.error(`ERROR ${error.message}`)
57
+ getConfiguration() {
58
+ if (!this._configuration) {
59
+ this._configuration = this._object?.configuration || Configuration.current()
49
60
  }
61
+
62
+ return this._configuration
63
+ }
64
+
65
+ async debug(...messages) {
66
+ if (this.getConfiguration()?.debug) {
67
+ await this.log(...messages)
68
+ }
69
+ }
70
+
71
+ async log(...messages) {
72
+ await consoleLog(messagesToMessage(this._subject, ...functionOrMessages(messages)))
73
+ }
74
+
75
+ async error(...messages) {
76
+ await consoleError(messagesToMessage(this._subject, ...functionOrMessages(messages)))
77
+ }
78
+ }
79
+
80
+ export {Logger}
81
+
82
+ export default async function logger(object, ...messages) {
83
+ const className = object.constructor.name
84
+ const configuration = object.configuration || Configuration.current()
85
+
86
+ if (configuration.debug) {
87
+ await consoleLog(messagesToMessage(className, ...functionOrMessages(messages)))
50
88
  }
51
89
  }
@@ -1,3 +1,5 @@
1
+ import restArgsError from "../utils/rest-args-error.js"
2
+
1
3
  const tests = {
2
4
  args: {},
3
5
  subs: {},
@@ -6,9 +8,53 @@ const tests = {
6
8
 
7
9
  let currentPath = [tests]
8
10
 
11
+ class ExpectToChange {
12
+ constructor({changeCallback, expect, ...restArgs}) {
13
+ restArgsError(restArgs)
14
+
15
+ this.expect = expect
16
+ this.changeCallback = changeCallback
17
+ }
18
+
19
+ by(count) {
20
+ this.count = count
21
+
22
+ return this.expect
23
+ }
24
+
25
+ async runBefore() {
26
+ this.oldCount = await this.changeCallback()
27
+ }
28
+
29
+ async runAfter() {
30
+ this.newCount = await this.changeCallback()
31
+ }
32
+
33
+ async execute() {
34
+ const difference = this.newCount - this.oldCount
35
+
36
+ if (difference != this.count) {
37
+ throw new Error(`Expected to change by ${this.count} but changed by ${difference}`)
38
+ }
39
+ }
40
+ }
41
+
9
42
  class Expect {
10
43
  constructor(object) {
11
44
  this._object = object
45
+ this.expectations = []
46
+ }
47
+
48
+ toChange(changeCallback) {
49
+ const expectToChange = new ExpectToChange({changeCallback, expect: this})
50
+
51
+ this.expectations.push(expectToChange)
52
+
53
+ return expectToChange
54
+ }
55
+
56
+ andChange(...args) {
57
+ return this.toChange(...args)
12
58
  }
13
59
 
14
60
  toEqual(result) {
@@ -17,6 +63,24 @@ class Expect {
17
63
  }
18
64
  }
19
65
 
66
+ async execute() {
67
+ for (const expectation of this.expectations) {
68
+ await expectation.runBefore()
69
+ }
70
+
71
+ const result = await this._object()
72
+
73
+ for (const expectation of this.expectations) {
74
+ await expectation.runAfter()
75
+ }
76
+
77
+ for (const expectation of this.expectations) {
78
+ await expectation.execute()
79
+ }
80
+
81
+ return result
82
+ }
83
+
20
84
  toHaveAttributes(result) {
21
85
  const differences = {}
22
86