velocious 1.0.96 → 1.0.98

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 (86) 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 +77 -19
  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/cli/commands/test.js +17 -3
  62. package/src/environment-handlers/node.js +59 -28
  63. package/src/http-client/header.js +6 -0
  64. package/src/http-client/request.js +31 -5
  65. package/src/http-client/response.js +31 -7
  66. package/src/http-server/client/index.js +24 -4
  67. package/src/http-server/client/request-buffer/form-data-part.js +11 -0
  68. package/src/http-server/client/request-buffer/header.js +6 -0
  69. package/src/http-server/client/request-buffer/index.js +91 -13
  70. package/src/http-server/client/request-parser.js +26 -0
  71. package/src/http-server/client/request-runner.js +15 -3
  72. package/src/http-server/client/request.js +17 -0
  73. package/src/http-server/client/response.js +41 -1
  74. package/src/http-server/index.js +32 -4
  75. package/src/http-server/server-client.js +33 -2
  76. package/src/http-server/worker-handler/index.js +42 -9
  77. package/src/http-server/worker-handler/worker-script.js +2 -0
  78. package/src/http-server/worker-handler/worker-thread.js +34 -6
  79. package/src/logger.js +21 -15
  80. package/src/routes/app-routes.js +1 -1
  81. package/src/testing/test-files-finder.js +96 -9
  82. package/src/testing/test-runner.js +77 -25
  83. package/src/utils/backtrace-cleaner.js +6 -4
  84. package/src/utils/ensure-error.js +13 -0
  85. package/src/utils/file-exists.js +3 -1
  86. 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) {
@@ -1,21 +1,74 @@
1
+ // @ts-check
2
+
1
3
  import fs from "fs/promises"
2
4
 
5
+ import fileExists from "../utils/file-exists.js"
6
+ import {Logger} from "../logger.js"
7
+ import restArgsError from "../utils/rest-args-error.js"
8
+
3
9
  // Incredibly complex class to find files in multiple simultanious running promises to do it as fast as possible.
4
10
  export default class TestFilesFinder {
5
11
  static IGNORED_NAMES = [".git", "node_modules"]
6
12
 
7
- constructor({directory, processArgs}) {
13
+ /**
14
+ * @param {object} args
15
+ * @param {string} args.directory
16
+ * @param {string[]} args.directories
17
+ * @param {string[]} args.processArgs
18
+ */
19
+ constructor({directory, directories, processArgs, ...restArgs}) {
20
+ restArgsError(restArgs)
21
+
8
22
  this.directory = directory
9
- this.foundFiles = []
23
+ this.logger = new Logger(this)
24
+
25
+ if (directories) {
26
+ this.directories = directories
27
+ } else {
28
+ this.directories = [
29
+ `${this.directory}/__tests__`,
30
+ `${this.directory}/tests`,
31
+ `${this.directory}/spec`
32
+ ]
33
+ }
34
+
10
35
  this.findingCount = 0
11
- this.findingPromises = {}
12
36
  this.processArgs = processArgs
37
+
38
+ /** @type {string[]} */
39
+ this.foundFiles = []
40
+
41
+ /** @type {Record<number, Promise<void>>} */
42
+ this.findingPromises = {}
43
+
44
+ /** @type {string[]} */
13
45
  this.testArgs = this.processArgs.filter((processArg, index) => index != 0)
46
+
47
+ /** @type {string[]} */
48
+ this.directoryArgs = []
49
+
50
+ /** @type {string[]} */
51
+ this.fileArgs = []
52
+
53
+ for (const testArg of this.testArgs) {
54
+ if (testArg.endsWith("/")) {
55
+ this.directoryArgs.push(testArg)
56
+ } else {
57
+ this.fileArgs.push(testArg)
58
+ }
59
+ }
14
60
  }
15
61
 
62
+ /**
63
+ * @returns {Promise<string[]>}
64
+ */
16
65
  async findTestFiles() {
17
66
  await this.withFindingCount(async () => {
18
- await this.findTestFilesInDir(this.directory)
67
+ for (const directory of this.directories) {
68
+ if (await fileExists(directory)) {
69
+ await this.findTestFilesInDir(directory)
70
+ }
71
+ }
19
72
  })
20
73
 
21
74
  await this.waitForFindingPromises()
@@ -23,6 +76,9 @@ export default class TestFilesFinder {
23
76
  return this.foundFiles
24
77
  }
25
78
 
79
+ /**
80
+ * @returns {number}
81
+ */
26
82
  findingPromisesLength() { return Object.keys(this.findingPromises).length }
27
83
 
28
84
  async waitForFindingPromises() {
@@ -31,6 +87,9 @@ export default class TestFilesFinder {
31
87
  }
32
88
  }
33
89
 
90
+ /**
91
+ * @returns {Promise<void>}
92
+ */
34
93
  async waitForFindingPromisesIteration() {
35
94
  const unfinishedPromises = []
36
95
 
@@ -43,6 +102,9 @@ export default class TestFilesFinder {
43
102
  await Promise.all(unfinishedPromises)
44
103
  }
45
104
 
105
+ /**
106
+ * @param {function() : Promise<void>} callback
107
+ */
46
108
  withFindingCount(callback) {
47
109
  return new Promise((resolve) => {
48
110
  const findingPromise = callback()
@@ -54,11 +116,15 @@ export default class TestFilesFinder {
54
116
  findingPromise.finally(() => {
55
117
  delete this.findingPromises[findingCount]
56
118
 
57
- resolve()
119
+ resolve(undefined)
58
120
  })
59
121
  })
60
122
  }
61
123
 
124
+ /**
125
+ * @param {string} dir
126
+ * @returns {Promise<void>}
127
+ */
62
128
  async findTestFilesInDir(dir) {
63
129
  await this.withFindingCount(async () => {
64
130
  const files = await fs.readdir(dir)
@@ -89,16 +155,37 @@ export default class TestFilesFinder {
89
155
  * @returns {boolean}
90
156
  */
91
157
  isFileMatchingRequirements(file, localPath) {
92
- if (this.testArgs.length > 0) {
93
- for (const testArg of this.testArgs) {
94
- if (testArg == localPath) {
158
+ if (this.directoryArgs.length > 0) {
159
+ for (const directoryArg of this.directoryArgs) {
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
+ return true
163
+ }
164
+ }
165
+ }
166
+
167
+ if (this.fileArgs.length > 0) {
168
+ for (const fileArg of this.fileArgs) {
169
+ if (fileArg == localPath) {
170
+ this.logger.debug("Found test file because matching file arg:", file)
95
171
  return true
96
172
  }
97
173
  }
98
- } else if (file.match(/-(spec|test)\.(m|)js$/)) {
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)
99
178
  return true
100
179
  }
101
180
 
102
181
  return false
103
182
  }
183
+
184
+ /**
185
+ * @param {string} file
186
+ * @returns {boolean}
187
+ */
188
+ looksLikeTestFile(file) {
189
+ return Boolean(file.match(/-(spec|test)\.(m|)js$/))
190
+ }
104
191
  }