velocious 1.0.97 → 1.0.99

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 (85) hide show
  1. package/eslint.config.js +1 -0
  2. package/package.json +2 -1
  3. package/spec/database/connection/drivers/mysql/query-parser-spec.js +4 -4
  4. package/spec/http-server/post-spec.js +2 -0
  5. package/src/application.js +27 -9
  6. package/src/configuration-resolver.js +29 -10
  7. package/src/configuration-types.js +44 -0
  8. package/src/configuration.js +63 -33
  9. package/src/database/drivers/base-column.js +6 -1
  10. package/src/database/drivers/base-columns-index.js +11 -1
  11. package/src/database/drivers/base-foreign-key.js +45 -0
  12. package/src/database/drivers/base-table.js +24 -2
  13. package/src/database/drivers/base.js +211 -39
  14. package/src/database/drivers/mssql/index.js +1 -3
  15. package/src/database/drivers/sqlite/sql/alter-table.js +4 -2
  16. package/src/database/handler.js +5 -0
  17. package/src/database/migration/index.js +79 -20
  18. package/src/database/migrator/files-finder.js +21 -22
  19. package/src/database/migrator/types.js +29 -0
  20. package/src/database/migrator.js +98 -59
  21. package/src/database/pool/async-tracked-multi-connection.js +42 -7
  22. package/src/database/pool/base-methods-forward.js +37 -0
  23. package/src/database/pool/base.js +79 -46
  24. package/src/database/pool/single-multi-use.js +18 -3
  25. package/src/database/query/alter-table-base.js +4 -4
  26. package/src/database/query/base.js +9 -2
  27. package/src/database/query/create-database-base.js +8 -0
  28. package/src/database/query/create-index-base.js +20 -5
  29. package/src/database/query/create-table-base.js +28 -9
  30. package/src/database/query/from-base.js +17 -0
  31. package/src/database/query/from-plain.js +8 -3
  32. package/src/database/query/from-table.js +8 -3
  33. package/src/database/query/index.js +43 -32
  34. package/src/database/query/join-base.js +28 -1
  35. package/src/database/query/join-object.js +67 -0
  36. package/src/database/query/join-plain.js +6 -1
  37. package/src/database/query/order-base.js +18 -0
  38. package/src/database/query/order-plain.js +8 -2
  39. package/src/database/query/select-base.js +15 -0
  40. package/src/database/query/select-plain.js +6 -1
  41. package/src/database/query/select-table-and-column.js +8 -2
  42. package/src/database/query/where-base.js +23 -1
  43. package/src/database/query/where-hash.js +15 -0
  44. package/src/database/query/where-plain.js +6 -0
  45. package/src/database/query-parser/base-query-parser.js +8 -2
  46. package/src/database/query-parser/from-parser.js +2 -0
  47. package/src/database/query-parser/joins-parser.js +10 -45
  48. package/src/database/query-parser/select-parser.js +2 -0
  49. package/src/database/record/index.js +1 -1
  50. package/src/database/table-data/index.js +39 -121
  51. package/src/database/table-data/table-column.js +54 -25
  52. package/src/database/table-data/table-foreign-key.js +5 -3
  53. package/src/database/table-data/table-index.js +12 -6
  54. package/src/database/table-data/table-reference.js +2 -0
  55. package/src/database/use-database.js +4 -2
  56. package/src/environment-handlers/base.js +41 -8
  57. package/src/environment-handlers/node/cli/commands/destroy/migration.js +3 -0
  58. package/src/environment-handlers/node/cli/commands/generate/migration.js +3 -0
  59. package/src/environment-handlers/node/cli/commands/generate/model.js +3 -0
  60. package/src/environment-handlers/node/cli/commands/init.js +3 -0
  61. package/src/environment-handlers/node.js +59 -28
  62. package/src/http-client/header.js +6 -0
  63. package/src/http-client/request.js +31 -5
  64. package/src/http-client/response.js +31 -7
  65. package/src/http-server/client/index.js +24 -4
  66. package/src/http-server/client/request-buffer/form-data-part.js +11 -0
  67. package/src/http-server/client/request-buffer/header.js +6 -0
  68. package/src/http-server/client/request-buffer/index.js +91 -13
  69. package/src/http-server/client/request-parser.js +26 -0
  70. package/src/http-server/client/request-runner.js +15 -3
  71. package/src/http-server/client/request.js +17 -0
  72. package/src/http-server/client/response.js +41 -1
  73. package/src/http-server/index.js +32 -4
  74. package/src/http-server/server-client.js +33 -2
  75. package/src/http-server/worker-handler/index.js +42 -9
  76. package/src/http-server/worker-handler/worker-script.js +2 -0
  77. package/src/http-server/worker-handler/worker-thread.js +34 -6
  78. package/src/logger.js +21 -15
  79. package/src/routes/app-routes.js +1 -1
  80. package/src/testing/test-files-finder.js +8 -4
  81. package/src/testing/test-runner.js +76 -24
  82. package/src/utils/backtrace-cleaner.js +6 -4
  83. package/src/utils/ensure-error.js +13 -0
  84. package/src/utils/file-exists.js +3 -1
  85. package/src/utils/rest-args-error.js +2 -0
@@ -1,11 +1,21 @@
1
- import {digg, digs} from "diggerize"
1
+ // @ts-check
2
+
2
3
  import {Logger} from "../../logger.js"
3
4
  import {Worker} from "worker_threads"
5
+ import ensureError from "../../utils/ensure-error.js"
4
6
 
5
7
  export default class VelociousHttpServerWorker {
8
+ /**
9
+ * @param {object} args
10
+ * @param {import("../../configuration.js").default} args.configuration
11
+ * @param {number} args.workerCount
12
+ */
6
13
  constructor({configuration, workerCount}) {
7
14
  this.configuration = configuration
15
+
16
+ /** @type {Record<number, import("../server-client.js").default>} */
8
17
  this.clients = {}
18
+
9
19
  this.logger = new Logger(this)
10
20
  this.workerCount = workerCount
11
21
  }
@@ -18,7 +28,7 @@ export default class VelociousHttpServerWorker {
18
28
  }
19
29
 
20
30
  async _spawnWorker() {
21
- const {debug} = digs(this.configuration, "debug")
31
+ const debug = this.configuration.debug
22
32
  const directory = this.configuration.getDirectory()
23
33
  const velociousPath = await this.configuration.getEnvironmentHandler().getVelociousPath()
24
34
 
@@ -35,25 +45,38 @@ export default class VelociousHttpServerWorker {
35
45
  this.worker.on("message", this.onWorkerMessage)
36
46
  }
37
47
 
48
+ /**
49
+ * @param {import("../server-client.js").default} client
50
+ * @returns {void}
51
+ */
38
52
  addSocketConnection(client) {
39
- const clientCount = digg(client, "clientCount")
53
+ const clientCount = client.clientCount
40
54
 
41
55
  client.socket.on("end", () => {
42
56
  this.logger.debug(`Removing ${clientCount} from clients`)
43
57
  delete this.clients[clientCount]
44
58
  })
45
59
 
46
- client.worker = this.worker
60
+ if (!this.worker) throw new Error("Worker not initialized")
61
+
62
+ client.setWorker(this.worker)
47
63
  client.listen()
48
64
 
49
65
  this.clients[clientCount] = client
50
66
  this.worker.postMessage({command: "newClient", clientCount})
51
67
  }
52
68
 
69
+ /**
70
+ * @param {any} error
71
+ */
53
72
  onWorkerError = (error) => {
54
- throw error // Throws original error with backtrace and everything into the console
73
+ throw ensureError(error) // Throws original error with backtrace and everything into the console
55
74
  }
56
75
 
76
+ /**
77
+ * @param {number} code
78
+ * @returns {void}
79
+ */
57
80
  onWorkerExit = (code) => {
58
81
  if (code !== 0) {
59
82
  throw new Error(`Client worker stopped with exit code ${code}`)
@@ -62,22 +85,32 @@ export default class VelociousHttpServerWorker {
62
85
  }
63
86
  }
64
87
 
88
+ /**
89
+ * @param {object} data
90
+ * @param {string} data.command
91
+ * @param {number} data.clientCount
92
+ * @param {string} data.output
93
+ * @returns {void}
94
+ */
65
95
  onWorkerMessage = (data) => {
66
96
  this.logger.debug(`Worker message`, data)
67
97
 
68
- const {command} = digs(data, "command")
98
+ const {command} = data
69
99
 
70
100
  if (command == "started") {
71
- this.onStartCallback()
101
+ if (this.onStartCallback) {
102
+ this.onStartCallback(null)
103
+ }
104
+
72
105
  this.onStartCallback = null
73
106
  } else if (command == "clientOutput") {
74
107
  this.logger.debug("CLIENT OUTPUT", data)
75
108
 
76
- const {clientCount, output} = digs(data, "clientCount", "output")
109
+ const {clientCount, output} = data
77
110
 
78
111
  this.clients[clientCount]?.send(output)
79
112
  } else if (command == "clientClose") {
80
- const {clientCount} = digs(data, "clientCount")
113
+ const {clientCount} = data
81
114
 
82
115
  this.clients[clientCount]?.end()
83
116
  } else {
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import {workerData, parentPort} from "worker_threads"
2
4
  import WorkerThread from "./worker-thread.js"
3
5
 
@@ -1,14 +1,25 @@
1
+ // @ts-check
2
+
1
3
  import Application from "../../application.js"
2
4
  import Client from "../client/index.js"
3
- import {digg, digs} from "diggerize"
5
+ import {digg} from "diggerize"
4
6
  import errorLogger from "../../error-logger.js"
5
7
  import {Logger} from "../../logger.js"
6
8
 
7
9
  export default class VelociousHttpServerWorkerHandlerWorkerThread {
10
+ /**
11
+ * @param {object} args
12
+ * @param {import("worker_threads").parentPort} args.parentPort
13
+ * @param {{directory: string, environment: string, workerCount: number}} args.workerData
14
+ */
8
15
  constructor({parentPort, workerData}) {
9
- const {workerCount} = digs(workerData, "workerCount")
16
+ if (!parentPort) throw new Error("parentPort is required")
17
+
18
+ const {workerCount} = workerData
10
19
 
20
+ /** @type {Record<number, Client>} */
11
21
  this.clients = {}
22
+
12
23
  this.logger = new Logger(this, {debug: false})
13
24
  this.parentPort = parentPort
14
25
  this.workerData = workerData
@@ -17,6 +28,8 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
17
28
  parentPort.on("message", errorLogger(this.onCommand))
18
29
 
19
30
  this.initialize().then(() => {
31
+ if (!this.application) throw new Error("Application not initialized")
32
+
20
33
  this.application.initialize().then(() => {
21
34
  this.logger.debug(`Worker ${workerCount} started`)
22
35
  parentPort.postMessage({command: "started"})
@@ -24,29 +37,44 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
24
37
  })
25
38
  }
26
39
 
40
+ /**
41
+ * @returns {Promise<void>}
42
+ */
27
43
  async initialize() {
28
- const {debug, directory, environment} = digs(this.workerData, "debug", "directory", "environment")
44
+ const {directory, environment} = this.workerData
29
45
  const configurationPath = `${directory}/src/config/configuration.js`
30
46
  const configurationImport = await import(configurationPath)
31
47
 
48
+ /** @type {import("../../configuration.js").default} */
32
49
  this.configuration = configurationImport.default
50
+
51
+ if (!this.configuration) throw new Error(`Configuration couldn't be loaded from: ${configurationPath}`)
52
+
33
53
  this.configuration.setEnvironment(environment)
34
54
  this.configuration.setCurrent()
35
55
 
36
- this.application = new Application({configuration: this.configuration, debug, directory, type: "worker-handler"})
56
+ this.application = new Application({configuration: this.configuration, type: "worker-handler"})
37
57
 
38
58
  if (this.configuration.isInitialized()) {
39
59
  await this.configuration.initialize({type: "worker-handler"})
40
60
  }
41
61
  }
42
62
 
63
+ /**
64
+ * @param {object} data
65
+ * @param {string} data.command
66
+ * @param {string} [data.chunk]
67
+ * @param {number} data.clientCount
68
+ */
43
69
  onCommand = async (data) => {
44
70
  await this.logger.debug(() => [`Worker ${this.workerCount} received command`, data])
45
71
 
46
72
  const command = data.command
47
73
 
48
74
  if (command == "newClient") {
49
- const {clientCount} = digs(data, "clientCount")
75
+ if (!this.configuration) throw new Error("Configuration not initialized")
76
+
77
+ const {clientCount} = data
50
78
  const client = new Client({
51
79
  clientCount,
52
80
  configuration: this.configuration
@@ -65,7 +93,7 @@ export default class VelociousHttpServerWorkerHandlerWorkerThread {
65
93
  } else if (command == "clientWrite") {
66
94
  await this.logger.debug("Looking up client")
67
95
 
68
- const {chunk, clientCount} = digs(data, "chunk", "clientCount")
96
+ const {chunk, clientCount} = data
69
97
  const client = digg(this.clients, clientCount)
70
98
 
71
99
  await this.logger.debug(`Sending to client ${clientCount}`)
package/src/logger.js CHANGED
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import Configuration from "./configuration.js"
2
4
  import restArgsError from "./utils/rest-args-error.js"
3
5
 
@@ -8,9 +10,10 @@ import restArgsError from "./utils/rest-args-error.js"
8
10
  function consoleLog(message) {
9
11
  return new Promise((resolve) => {
10
12
  if (process.stdout) {
11
- process.stdout.write(`${message}\n`, "utf8", resolve)
13
+ process.stdout.write(`${message}\n`, "utf8", () => resolve())
12
14
  } else {
13
15
  console.log(message)
16
+ resolve()
14
17
  }
15
18
  })
16
19
  }
@@ -22,9 +25,10 @@ function consoleLog(message) {
22
25
  function consoleError(message) {
23
26
  return new Promise((resolve) => {
24
27
  if (process.stderr) {
25
- process.stderr.write(`${message}\n`, "utf8", resolve)
28
+ process.stderr.write(`${message}\n`, "utf8", () => resolve())
26
29
  } else {
27
30
  console.error(message)
31
+ resolve()
28
32
  }
29
33
  })
30
34
  }
@@ -36,17 +40,19 @@ function consoleError(message) {
36
40
  function consoleWarn(message) {
37
41
  return new Promise((resolve) => {
38
42
  if (process.stderr) {
39
- process.stderr.write(`${message}\n`, "utf8", resolve)
43
+ process.stderr.write(`${message}\n`, "utf8", () => resolve())
40
44
  } else {
41
45
  console.warn(message)
46
+ resolve()
42
47
  }
43
48
  })
44
49
  }
45
50
 
46
51
  /**
47
- * @param {Array} messages
52
+ * @param {...any|function() : Array<any>} messages
53
+ * @returns {Array<any>} - Either the function result or the messages
48
54
  */
49
- function functionOrMessages(messages) {
55
+ function functionOrMessages(...messages) {
50
56
  if (messages.length === 1 && typeof messages[0] == "function") {
51
57
  messages = messages[0]()
52
58
  }
@@ -65,7 +71,7 @@ function messagesToMessage(...messages) {
65
71
  for (const messagePartIndex in messages) {
66
72
  const messagePart = messages[messagePartIndex]
67
73
 
68
- if (messagePartIndex > 0) {
74
+ if (typeof messagePartIndex == "number" && messagePartIndex > 0) {
69
75
  message += " "
70
76
  }
71
77
 
@@ -85,7 +91,7 @@ class Logger {
85
91
  * @param {object} args
86
92
  * @param {boolean} args.debug
87
93
  */
88
- constructor(object, {debug, ...restArgs} = {}) {
94
+ constructor(object, {debug, ...restArgs} = {debug: false}) {
89
95
  restArgsError(restArgs)
90
96
 
91
97
  this._debug = debug
@@ -114,7 +120,7 @@ class Logger {
114
120
  }
115
121
 
116
122
  /**
117
- * @param {...Parameters<typeof consoleLog>} messages - forwarded args
123
+ * @type {(...args: Parameters<typeof functionOrMessages>) => Promise<void>}
118
124
  */
119
125
  async debug(...messages) {
120
126
  if (this._debug || this.getConfiguration()?.debug) {
@@ -123,17 +129,17 @@ class Logger {
123
129
  }
124
130
 
125
131
  /**
126
- * @param {...Parameters<typeof functionOrMessages>} messages - forwarded args
132
+ * @type {(...args: Parameters<typeof functionOrMessages>) => Promise<void>}
127
133
  */
128
134
  async log(...messages) {
129
- await consoleLog(messagesToMessage(this._subject, ...functionOrMessages(messages)))
135
+ await consoleLog(messagesToMessage(this._subject, ...functionOrMessages(...messages)))
130
136
  }
131
137
 
132
138
  /**
133
- * @param {...Parameters<typeof functionOrMessages>} messages - forwarded args
139
+ * @type {(...args: Parameters<typeof functionOrMessages>) => Promise<void>}
134
140
  */
135
141
  async error(...messages) {
136
- await consoleError(messagesToMessage(this._subject, ...functionOrMessages(messages)))
142
+ await consoleError(messagesToMessage(this._subject, ...functionOrMessages(...messages)))
137
143
  }
138
144
 
139
145
  /**
@@ -145,10 +151,10 @@ class Logger {
145
151
  }
146
152
 
147
153
  /**
148
- * @param {...Parameters<typeof functionOrMessages>} messages - forwarded args
154
+ * @type {(...args: Parameters<typeof functionOrMessages>) => Promise<void>}
149
155
  */
150
156
  async warn(...messages) {
151
- await consoleWarn(messagesToMessage(this._subject, ...functionOrMessages(messages)))
157
+ await consoleWarn(messagesToMessage(this._subject, ...functionOrMessages(...messages)))
152
158
  }
153
159
  }
154
160
 
@@ -163,6 +169,6 @@ export default async function logger(object, ...messages) {
163
169
  const configuration = object.configuration || Configuration.current()
164
170
 
165
171
  if (configuration.debug) {
166
- await consoleLog(messagesToMessage(className, ...functionOrMessages(messages)))
172
+ await consoleLog(messagesToMessage(className, ...functionOrMessages(...messages)))
167
173
  }
168
174
  }
@@ -2,7 +2,7 @@ import {digg} from "diggerize"
2
2
 
3
3
  export default class VelociousRoutesAppRoutes {
4
4
  /**
5
- * @param {import("../configuration.js")} configuration
5
+ * @param {import("../configuration.js").default} configuration
6
6
  * @returns {import("./index.js").default}
7
7
  */
8
8
  static async getRoutes(configuration) {
@@ -3,6 +3,7 @@
3
3
  import fs from "fs/promises"
4
4
 
5
5
  import fileExists from "../utils/file-exists.js"
6
+ import {Logger} from "../logger.js"
6
7
  import restArgsError from "../utils/rest-args-error.js"
7
8
 
8
9
  // Incredibly complex class to find files in multiple simultanious running promises to do it as fast as possible.
@@ -19,6 +20,7 @@ export default class TestFilesFinder {
19
20
  restArgsError(restArgs)
20
21
 
21
22
  this.directory = directory
23
+ this.logger = new Logger(this)
22
24
 
23
25
  if (directories) {
24
26
  this.directories = directories
@@ -63,10 +65,7 @@ export default class TestFilesFinder {
63
65
  async findTestFiles() {
64
66
  await this.withFindingCount(async () => {
65
67
  for (const directory of this.directories) {
66
- console.log({directory})
67
-
68
68
  if (await fileExists(directory)) {
69
- console.log("Exists!")
70
69
  await this.findTestFilesInDir(directory)
71
70
  }
72
71
  }
@@ -159,6 +158,7 @@ export default class TestFilesFinder {
159
158
  if (this.directoryArgs.length > 0) {
160
159
  for (const directoryArg of this.directoryArgs) {
161
160
  if (localPath.startsWith(directoryArg) && this.looksLikeTestFile(file)) {
161
+ this.logger.debug("Found test file because matching dir and looks like this file:", file)
162
162
  return true
163
163
  }
164
164
  }
@@ -167,10 +167,14 @@ export default class TestFilesFinder {
167
167
  if (this.fileArgs.length > 0) {
168
168
  for (const fileArg of this.fileArgs) {
169
169
  if (fileArg == localPath) {
170
+ this.logger.debug("Found test file because matching file arg:", file)
170
171
  return true
171
172
  }
172
173
  }
173
- } else if (this.looksLikeTestFile(file)) {
174
+ }
175
+
176
+ if (this.fileArgs.length == 0 && this.directoryArgs.length == 0 && this.looksLikeTestFile(file)) {
177
+ this.logger.debug("Found test file because looks like this file:", file)
174
178
  return true
175
179
  }
176
180
 
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import {addTrackedStackToError} from "../utils/with-tracked-stack.js"
2
4
  import Application from "../../src/application.js"
3
5
  import BacktraceCleaner from "../utils/backtrace-cleaner.js"
@@ -5,6 +7,34 @@ import RequestClient from "./request-client.js"
5
7
  import restArgsError from "../utils/rest-args-error.js"
6
8
  import {tests} from "./test.js"
7
9
 
10
+ /**
11
+ * @typedef {object} TestArgs
12
+ * @property {Application} application
13
+ * @property {RequestClient} client
14
+ * @property {boolean} focus
15
+ * @property {string} type
16
+ */
17
+
18
+ /**
19
+ * @typedef {object} TestData
20
+ * @property {TestArgs} args
21
+ * @property {function(TestArgs) : Promise<void>} function
22
+ */
23
+
24
+ /**
25
+ * @typedef {object} AfterBeforeEachCallback
26
+ * @property {function({configuration: import("../configuration.js").default, testArgs: TestArgs, testData: TestData}) : Promise<void>} callback
27
+ */
28
+
29
+ /**
30
+ * @typedef {object} TestsArgument
31
+ * @property {boolean} [anyTestsFocussed]
32
+ * @property {AfterBeforeEachCallback[]} afterEaches
33
+ * @property {AfterBeforeEachCallback[]} beforeEaches
34
+ * @property {Record<string, TestData>} tests - A unique identifier for the node.
35
+ * @property {Record<string, TestsArgument>} subs - Optional child nodes. Each item is another `Node`, allowing recursion.
36
+ */
37
+
8
38
  export default class TestRunner {
9
39
  /**
10
40
  * @param {object} args
@@ -18,6 +48,10 @@ export default class TestRunner {
18
48
 
19
49
  this._configuration = configuration
20
50
  this._testFiles = testFiles
51
+
52
+ this._failedTests = 0
53
+ this._successfulTests = 0
54
+ this._testsCount = 0
21
55
  }
22
56
 
23
57
  /**
@@ -37,13 +71,6 @@ export default class TestRunner {
37
71
  if (!this._application) {
38
72
  this._application = new Application({
39
73
  configuration: this.getConfiguration(),
40
- databases: {
41
- default: {
42
- host: "mysql",
43
- username: "user",
44
- password: ""
45
- }
46
- },
47
74
  httpServer: {port: 31006},
48
75
  type: "test-runner"
49
76
  })
@@ -56,7 +83,7 @@ export default class TestRunner {
56
83
  }
57
84
 
58
85
  /**
59
- * @returns {RequestClient}
86
+ * @returns {Promise<RequestClient>}
60
87
  */
61
88
  async requestClient() {
62
89
  if (!this._requestClient) {
@@ -67,7 +94,7 @@ export default class TestRunner {
67
94
  }
68
95
 
69
96
  /**
70
- * @returns {void}
97
+ * @returns {Promise<void>}
71
98
  */
72
99
  async importTestFiles() {
73
100
  await this.getConfiguration().getEnvironmentHandler().importTestFiles(this.getTestFiles())
@@ -76,25 +103,37 @@ export default class TestRunner {
76
103
  /**
77
104
  * @returns {boolean}
78
105
  */
79
- isFailed() { return this._failedTests > 0 }
106
+ isFailed() { return this._failedTests !== undefined && this._failedTests > 0 }
80
107
 
81
108
  /**
82
109
  * @returns {number}
83
110
  */
84
- getFailedTests() { return this._failedTests }
111
+ getFailedTests() {
112
+ if (this._failedTests === undefined) throw new Error("Tests hasn't been run yet")
113
+
114
+ return this._failedTests
115
+ }
85
116
 
86
117
  /**
87
118
  * @returns {number}
88
119
  */
89
- getSuccessfulTests() { return this._successfulTests }
120
+ getSuccessfulTests() {
121
+ if (this._successfulTests === undefined) throw new Error("Tests hasn't been run yet")
122
+
123
+ return this._successfulTests
124
+ }
90
125
 
91
126
  /**
92
127
  * @returns {number}
93
128
  */
94
- getTestsCount() { return this._testsCount }
129
+ getTestsCount() {
130
+ if (this._testsCount === undefined) throw new Error("Tests hasn't been run yet")
131
+
132
+ return this._testsCount
133
+ }
95
134
 
96
135
  /**
97
- * @returns {void}
136
+ * @returns {Promise<void>}
98
137
  */
99
138
  async prepare() {
100
139
  this.anyTestsFocussed = false
@@ -124,7 +163,7 @@ export default class TestRunner {
124
163
  }
125
164
 
126
165
  /**
127
- * @returns {void}
166
+ * @returns {Promise<void>}
128
167
  */
129
168
  async run() {
130
169
  await this.getConfiguration().ensureConnections(async () => {
@@ -139,7 +178,8 @@ export default class TestRunner {
139
178
  }
140
179
 
141
180
  /**
142
- * @returns {object}
181
+ * @param {TestsArgument} tests
182
+ * @returns {{anyTestsFocussed: boolean}}
143
183
  */
144
184
  analyzeTests(tests) {
145
185
  let anyTestsFocussedFound = false
@@ -171,6 +211,12 @@ export default class TestRunner {
171
211
  }
172
212
 
173
213
  /**
214
+ * @param {object} args
215
+ * @param {Array<AfterBeforeEachCallback>} args.afterEaches
216
+ * @param {Array<AfterBeforeEachCallback>} args.beforeEaches
217
+ * @param {TestsArgument} args.tests
218
+ * @param {string[]} args.descriptions
219
+ * @param {number} args.indentLevel
174
220
  * @returns {Promise<void>}
175
221
  */
176
222
  async runTests({afterEaches, beforeEaches, tests, descriptions, indentLevel}) {
@@ -180,7 +226,7 @@ export default class TestRunner {
180
226
 
181
227
  for (const testDescription in tests.tests) {
182
228
  const testData = tests.tests[testDescription]
183
- const testArgs = Object.assign({}, testData.args)
229
+ const testArgs = /** @type {TestArgs} */ (Object.assign({}, testData.args))
184
230
 
185
231
  if (this._onlyFocussed && !testArgs.focus) continue
186
232
 
@@ -204,15 +250,21 @@ export default class TestRunner {
204
250
  } catch (error) {
205
251
  this._failedTests++
206
252
 
207
- console.error(`${leftPadding} Test failed: ${error.message}`)
208
- addTrackedStackToError(error)
253
+ if (error instanceof Error) {
254
+ console.error(`${leftPadding} Test failed:`, error.message)
255
+ addTrackedStackToError(error)
209
256
 
210
- const backtraceCleaner = new BacktraceCleaner(error)
211
- const cleanedStack = backtraceCleaner.getCleanedStack()
212
- const stackLines = cleanedStack.split("\n")
257
+ const backtraceCleaner = new BacktraceCleaner(error)
258
+ const cleanedStack = backtraceCleaner.getCleanedStack()
259
+ const stackLines = cleanedStack?.split("\n")
213
260
 
214
- for (const stackLine of stackLines) {
215
- console.error(`${leftPadding} ${stackLine}`)
261
+ if (stackLines) {
262
+ for (const stackLine of stackLines) {
263
+ console.error(`${leftPadding} ${stackLine}`)
264
+ }
265
+ }
266
+ } else {
267
+ console.error(`${leftPadding} Test failed with a ${typeof error}:`, error)
216
268
  }
217
269
  } finally {
218
270
  for (const afterEachData of newAfterEaches) {
@@ -1,7 +1,9 @@
1
+ // @ts-check
2
+
1
3
  export default class BacktraceCleaner {
2
4
  /**
3
5
  * @param {Error} error
4
- * @returns {string}
6
+ * @returns {string | undefined}
5
7
  */
6
8
  static getCleanedStack(error) {
7
9
  return new BacktraceCleaner(error).getCleanedStack()
@@ -15,11 +17,11 @@ export default class BacktraceCleaner {
15
17
  }
16
18
 
17
19
  /**
18
- * @returns {string}
20
+ * @returns {string | undefined}
19
21
  */
20
22
  getCleanedStack() {
21
- const backtrace = this.error.stack.split("\n")
23
+ const backtrace = this.error?.stack?.split("\n")
22
24
 
23
- return backtrace.filter((line) => !line.includes("node_modules") && !line.includes("(node:internal/process/")).join("\n")
25
+ return backtrace?.filter((line) => !line.includes("node_modules") && !line.includes("(node:internal/process/")).join("\n")
24
26
  }
25
27
  }
@@ -0,0 +1,13 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {any} error
5
+ * @returns {Error}
6
+ */
7
+ export default function ensureError(error) {
8
+ if (error instanceof Error) {
9
+ return error
10
+ } else {
11
+ return new Error(`Unknown error type ${typeof error}: ${error}`)
12
+ }
13
+ }
@@ -1,8 +1,10 @@
1
+ // @ts-check
2
+
1
3
  import fs from "fs/promises"
2
4
 
3
5
  /**
4
6
  * @param {string} path
5
- * @returns {boolean}
7
+ * @returns {Promise<boolean>}
6
8
  */
7
9
  export default async function fileExists(path) {
8
10
  try {
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  /**
2
4
  * @param {object} restArgs
3
5
  * @returns {void}