velocious 1.0.95 → 1.0.97

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.95",
6
+ "version": "1.0.97",
7
7
  "main": "index.js",
8
8
  "scripts": {
9
9
  "lint": "eslint",
@@ -15,37 +15,51 @@ import restArgsError from "../../utils/rest-args-error.js"
15
15
  * @typedef {Record<string, boolean|NestedPreloadRecord>} NestedPreloadRecord
16
16
  */
17
17
 
18
+ /**
19
+ * A generic query over some model type.
20
+ */
18
21
  export default class VelociousDatabaseQuery {
19
22
  /**
20
23
  * @param {object} args
21
- * @template {import("../drivers/base.js").default} Tdriver
22
- * @param {Tdriver} args.driver
23
- * @template {import("./from-base.js").default} TfromBase
24
- * @param {TfromBase[]} args.froms
25
- * @param {string[]} args.groups
26
- * @template {import("./join-base.js").default} TjoinBase
27
- * @param {TjoinBase[]} args.joins
24
+ * @param {import("../drivers/base.js").default} args.driver
25
+ * @param {Array<import("./from-base.js").default>} [args.froms]
26
+ * @param {string[]} [args.groups]
27
+ * @param {Array<import("./join-base.js").default>} [args.joins]
28
28
  * @param {import("../handler.js").default} args.handler
29
- * @param {number} args.limit
30
- * @template {import("../record/index.js").default} Trecord
31
- * @param {typeof Trecord} args.modelClass
32
- * @param {number} args.offset
33
- * @template {import("./order-basejs").default} TorderBase
34
- * @param {TorderBase[]} args.orders
35
- * @param {number} args.page
29
+ * @param {number | null} [args.limit]
30
+ * @param {typeof import("../record/index.js").default} args.modelClass
31
+ * @param {number | null} [args.offset]
32
+ * @param {Array<import("./order-base.js").default>} [args.orders]
33
+ * @param {number | null} [args.page]
36
34
  * @param {number} args.perPage
37
- * @param {NestedPreloadRecord} args.preload
38
- * @param {Record<string, Array<string>>} args.selects
39
- * @template {import("./where-base.js").default} TwhereBase
40
- * @param {TwhereBase[]} args.wheres
35
+ * @param {NestedPreloadRecord} [args.preload]
36
+ * @param {Array<import("./select-base.js").default>} [args.selects]
37
+ * @param {Array<import("./where-base.js").default>} [args.wheres]
41
38
  */
42
- constructor({driver, froms = [], groups = [], joins = [], handler, limit = null, modelClass, offset = null, orders = [], page = null, perPage, preload = {}, selects = [], wheres = [], ...restArgs}) {
39
+ constructor({
40
+ driver,
41
+ froms = [],
42
+ groups = [],
43
+ joins = [],
44
+ handler,
45
+ limit = null,
46
+ modelClass,
47
+ offset = null,
48
+ orders = [],
49
+ page = null,
50
+ perPage,
51
+ preload = {},
52
+ selects = [],
53
+ wheres = [],
54
+ ...restArgs
55
+ }) {
43
56
  if (!driver) throw new Error("No driver given to query")
44
57
  if (!handler) throw new Error("No handler given to query")
45
58
 
46
59
  restArgsError(restArgs)
47
60
 
48
61
  this.driver = driver
62
+
49
63
  this.handler = handler
50
64
  this.logger = new Logger(this)
51
65
  this.modelClass = modelClass
@@ -147,6 +161,11 @@ export default class VelociousDatabaseQuery {
147
161
  */
148
162
  getOptions() { return this.driver.options() }
149
163
 
164
+ /**
165
+ * @returns {Array<import("./select-base.js").default>}
166
+ */
167
+ getSelects() { return this._selects }
168
+
150
169
  /**
151
170
  * @returns {Promise<void>}
152
171
  */
@@ -160,7 +179,7 @@ export default class VelociousDatabaseQuery {
160
179
 
161
180
  /**
162
181
  * @param {number|string} recordId
163
- * @returns {Promise<InstanceType<this["modelClass"]>>}
182
+ * @returns {Promise<import("../record/index.js").default>}
164
183
  */
165
184
  async find(recordId) {
166
185
  const conditions = {}
@@ -179,7 +198,7 @@ export default class VelociousDatabaseQuery {
179
198
 
180
199
  /**
181
200
  * @param {object} conditions
182
- * @returns {Promise<InstanceType<this["modelClass"]>>}
201
+ * @returns {Promise<import("../record/index.js").default|null>}
183
202
  */
184
203
  async findBy(conditions) {
185
204
  const newConditions = {}
@@ -195,7 +214,7 @@ export default class VelociousDatabaseQuery {
195
214
 
196
215
  /**
197
216
  * @param {...Parameters<this["findOrInitializeBy"]>} args
198
- * @returns {Promise<InstanceType<this["modelClass"]>>}
217
+ * @returns {Promise<import("../record/index.js").default>}
199
218
  */
200
219
  async findOrCreateBy(...args) {
201
220
  const record = await this.findOrInitializeBy(...args)
@@ -209,7 +228,7 @@ export default class VelociousDatabaseQuery {
209
228
 
210
229
  /**
211
230
  * @param {object} conditions
212
- * @returns {Promise<InstanceType<this["modelClass"]>>}
231
+ * @returns {Promise<import("../record/index.js").default>}
213
232
  */
214
233
  async findByOrFail(conditions) {
215
234
  const newConditions = {}
@@ -232,7 +251,7 @@ export default class VelociousDatabaseQuery {
232
251
  /**
233
252
  * @param {object} conditions
234
253
  * @param {function() : void} callback
235
- * @returns {Promise<InstanceType<this["modelClass"]>>}
254
+ * @returns {Promise<import("../record/index.js").default>}
236
255
  */
237
256
  async findOrInitializeBy(conditions, callback) {
238
257
  const record = await this.findBy(conditions)
@@ -249,7 +268,7 @@ export default class VelociousDatabaseQuery {
249
268
  }
250
269
 
251
270
  /**
252
- * @returns {Promise<InstanceType<this["modelClass"]>>}
271
+ * @returns {Promise<import("../record/index.js").default>}
253
272
  */
254
273
  async first() {
255
274
  const newQuery = this.clone().limit(1).reorder(`${this.driver.quoteTable(this.modelClass.tableName())}.${this.driver.quoteColumn(this.modelClass.orderableColumn())}`)
@@ -298,7 +317,7 @@ export default class VelociousDatabaseQuery {
298
317
  }
299
318
 
300
319
  /**
301
- * @returns {Promise<InstanceType<this["modelClass"]>>}
320
+ * @returns {Promise<import("../record/index.js").default>}
302
321
  */
303
322
  async last() {
304
323
  const primaryKey = this.modelClass.primaryKey()
@@ -435,7 +454,7 @@ export default class VelociousDatabaseQuery {
435
454
 
436
455
  /**
437
456
  * Converts query results to array of model instances
438
- * @returns {Promise<Array<InstanceType<this["modelClass"]>>>}
457
+ * @returns {Promise<Array<import("../record/index.js").default>>}
439
458
  */
440
459
  async toArray() {
441
460
  const models = []
@@ -1,7 +1,15 @@
1
1
  import {digs} from "diggerize"
2
+ import restArgsError from "../../utils/rest-args-error.js"
2
3
 
3
4
  export default class VelociousDatabaseQueryParserSelectParser {
4
- constructor({pretty, query}) {
5
+ /**
6
+ * @param {object} args
7
+ * @param {boolean} args.pretty
8
+ * @param {import("../query/index.js").default} args.query
9
+ */
10
+ constructor({pretty, query, ...restArgs}) {
11
+ restArgsError(restArgs)
12
+
5
13
  this.pretty = pretty
6
14
  this.query = query
7
15
  }
@@ -34,7 +42,7 @@ export default class VelociousDatabaseQueryParserSelectParser {
34
42
  }
35
43
  }
36
44
 
37
- if (query._selects.length == 0) {
45
+ if (query.getSelects().length == 0) {
38
46
  if (query.modelClass) {
39
47
  sql += `${query.modelClass.connection().quoteTable(query.modelClass.tableName())}.*`
40
48
  } else {
@@ -149,6 +149,10 @@ class VelociousDatabaseRecord {
149
149
  return this.getRelationshipByName(relationshipName)
150
150
  }
151
151
 
152
+ this.prototype[`${relationshipName}Loaded`] = function() {
153
+ return this.getRelationshipByName(relationshipName).loaded()
154
+ }
155
+
152
156
  this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
153
157
  await this.getRelationshipByName(relationshipName).load()
154
158
  }
@@ -87,27 +87,77 @@ export default class DbGenerateModel extends BaseCommand {
87
87
  }
88
88
 
89
89
  if (relationship.getType() == "belongsTo" || relationship.getType() == "hasOne") {
90
- fileContent += " /**\n"
90
+ let modelFilePath
91
91
 
92
92
  if (fullFilePath && await fileExists(fullFilePath)) {
93
- fileContent += ` * @returns {import("../models/${fileName}.js").default}\n`
93
+ modelFilePath = `../models/${fileName}.js`
94
94
  } else {
95
- fileContent += ` * @returns {import("velocious/src/database/record/index.js").default}\n`
95
+ modelFilePath = "velocious/src/database/record/index.js"
96
96
  }
97
97
 
98
+ fileContent += " /**\n"
99
+ fileContent += " * @interface\n"
100
+ fileContent += ` * @returns {import("${modelFilePath}").default}\n`
98
101
  fileContent += " */\n"
99
102
  fileContent += ` ${relationship.getRelationshipName()}() { return this.getRelationshipByName("${relationship.getRelationshipName()}").loaded() }\n`
100
- } else if (relationship.getType() == "hasMany") {
103
+
104
+ fileContent += "\n"
101
105
  fileContent += " /**\n"
106
+ fileContent += " * @interface\n"
107
+ fileContent += ` * @returns {import("${modelFilePath}").default}\n`
108
+ fileContent += " */\n"
109
+ fileContent += ` build${inflection.camelize(relationship.getRelationshipName())}() { throw new Error("Not implemented") }\n`
110
+
111
+ fileContent += "\n"
112
+ fileContent += " /**\n"
113
+ fileContent += " * @interface\n"
114
+ fileContent += " * @returns {Promise<void>}\n"
115
+ fileContent += " */\n"
116
+ fileContent += ` load${inflection.camelize(relationship.getRelationshipName())}() { throw new Error("Not implemented") }\n`
117
+
118
+ fileContent += "\n"
119
+ fileContent += " /**\n"
120
+ fileContent += " * @interface\n"
121
+ fileContent += ` * @param {import("${modelFilePath}").default} newModel\n`
122
+ fileContent += ` * @returns {void}\n`
123
+ fileContent += " */\n"
124
+ fileContent += ` set${inflection.camelize(relationship.getRelationshipName())}() { throw new Error("Not implemented") }\n`
125
+ } else if (relationship.getType() == "hasMany") {
126
+ let recordImport
102
127
 
103
128
  if (fullFilePath && await fileExists(fullFilePath)) {
104
- fileContent += ` * @returns {Array<import("../models/${fileName}.js").default>}\n`
129
+ recordImport = `../models/${fileName}.js`
105
130
  } else {
106
- fileContent += ` * @returns {Array<import("velocious/src/database/record/index.js").default>}\n`
131
+ recordImport = "velocious/src/database/record/index.js"
107
132
  }
108
133
 
134
+ fileContent += " /**\n"
135
+ fileContent += " * @interface\n"
136
+ fileContent += ` * @returns {import("velocious/src/database/query/index.js").default<import("${recordImport}").default>}\n`
109
137
  fileContent += " */\n"
110
- fileContent += ` ${relationship.getRelationshipName()}() { return this.getRelationshipByName("${relationship.getRelationshipName()}").loaded() }\n`
138
+ fileContent += ` ${relationship.getRelationshipName()}() { return this.getRelationshipByName("${relationship.getRelationshipName()}") }\n`
139
+
140
+ fileContent += "\n"
141
+ fileContent += " /**\n"
142
+ fileContent += " * @interface\n"
143
+ fileContent += ` * @returns {Array<import("${recordImport}").default>}\n`
144
+ fileContent += " */\n"
145
+ fileContent += ` ${relationship.getRelationshipName()}Loaded() { return this.getRelationshipByName("${relationship.getRelationshipName()}").loaded() }\n`
146
+
147
+ fileContent += "\n"
148
+ fileContent += " /**\n"
149
+ fileContent += " * @interface\n"
150
+ fileContent += " * @returns {Promise<void>}\n"
151
+ fileContent += " */\n"
152
+ fileContent += ` load${inflection.camelize(relationship.getRelationshipName())}() { throw new Error("Not implemented") }\n`
153
+
154
+ fileContent += "\n"
155
+ fileContent += " /**\n"
156
+ fileContent += " * @interface\n"
157
+ fileContent += ` * @param {Array<import("${recordImport}").default>} newModels\n`
158
+ fileContent += " * @returns {void>}\n"
159
+ fileContent += " */\n"
160
+ fileContent += ` set${inflection.camelize(relationship.getRelationshipName())}() { throw new Error("Not implemented") }\n`
111
161
  } else {
112
162
  throw new Error(`Unknown relationship type: ${relationship.getType()}`)
113
163
  }
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+
1
3
  import BaseCommand from "../../../../cli/base-command.js"
2
4
  import TestFilesFinder from "../../../../testing/test-files-finder.js"
3
5
  import TestRunner from "../../../../testing/test-runner.js"
@@ -6,15 +8,27 @@ export default class VelociousCliCommandsTest extends BaseCommand {
6
8
  async execute() {
7
9
  this.getConfiguration().setEnvironment("test")
8
10
 
9
- const directory = process.env.VELOCIOUS_TEST_DIR || this.directory()
10
- const testFilesFinder = new TestFilesFinder({directory, processArgs: this.processArgs})
11
+ let directory
12
+ const directories = []
13
+
14
+ if (process.env.VELOCIOUS_TEST_DIR) {
15
+ directory = process.env.VELOCIOUS_TEST_DIR
16
+ directories.push(process.env.VELOCIOUS_TEST_DIR)
17
+ } else {
18
+ directory = this.directory()
19
+ directories.push(`${this.directory()}/__tests__`)
20
+ directories.push(`${this.directory()}/tests`)
21
+ directories.push(`${this.directory()}/spec`)
22
+ }
23
+
24
+ const testFilesFinder = new TestFilesFinder({directory, directories, processArgs: this.processArgs})
11
25
  const testFiles = await testFilesFinder.findTestFiles()
12
26
  const testRunner = new TestRunner({configuration: this.getConfiguration(), testFiles})
13
27
 
14
28
  await testRunner.prepare()
15
29
 
16
30
  if (testRunner.getTestsCount() === 0) {
17
- throw new Error("No tests has been found")
31
+ throw new Error(`${testRunner.getTestsCount()} tests was found in ${testFiles.length} file(s)`)
18
32
  }
19
33
 
20
34
  await testRunner.run()
@@ -1,21 +1,75 @@
1
+ // @ts-check
2
+
1
3
  import fs from "fs/promises"
2
4
 
5
+ import fileExists from "../utils/file-exists.js"
6
+ import restArgsError from "../utils/rest-args-error.js"
7
+
3
8
  // Incredibly complex class to find files in multiple simultanious running promises to do it as fast as possible.
4
9
  export default class TestFilesFinder {
5
10
  static IGNORED_NAMES = [".git", "node_modules"]
6
11
 
7
- constructor({directory, processArgs}) {
12
+ /**
13
+ * @param {object} args
14
+ * @param {string} args.directory
15
+ * @param {string[]} args.directories
16
+ * @param {string[]} args.processArgs
17
+ */
18
+ constructor({directory, directories, processArgs, ...restArgs}) {
19
+ restArgsError(restArgs)
20
+
8
21
  this.directory = directory
9
- this.foundFiles = []
22
+
23
+ if (directories) {
24
+ this.directories = directories
25
+ } else {
26
+ this.directories = [
27
+ `${this.directory}/__tests__`,
28
+ `${this.directory}/tests`,
29
+ `${this.directory}/spec`
30
+ ]
31
+ }
32
+
10
33
  this.findingCount = 0
11
- this.findingPromises = {}
12
34
  this.processArgs = processArgs
35
+
36
+ /** @type {string[]} */
37
+ this.foundFiles = []
38
+
39
+ /** @type {Record<number, Promise<void>>} */
40
+ this.findingPromises = {}
41
+
42
+ /** @type {string[]} */
13
43
  this.testArgs = this.processArgs.filter((processArg, index) => index != 0)
44
+
45
+ /** @type {string[]} */
46
+ this.directoryArgs = []
47
+
48
+ /** @type {string[]} */
49
+ this.fileArgs = []
50
+
51
+ for (const testArg of this.testArgs) {
52
+ if (testArg.endsWith("/")) {
53
+ this.directoryArgs.push(testArg)
54
+ } else {
55
+ this.fileArgs.push(testArg)
56
+ }
57
+ }
14
58
  }
15
59
 
60
+ /**
61
+ * @returns {Promise<string[]>}
62
+ */
16
63
  async findTestFiles() {
17
64
  await this.withFindingCount(async () => {
18
- await this.findTestFilesInDir(this.directory)
65
+ for (const directory of this.directories) {
66
+ console.log({directory})
67
+
68
+ if (await fileExists(directory)) {
69
+ console.log("Exists!")
70
+ await this.findTestFilesInDir(directory)
71
+ }
72
+ }
19
73
  })
20
74
 
21
75
  await this.waitForFindingPromises()
@@ -23,6 +77,9 @@ export default class TestFilesFinder {
23
77
  return this.foundFiles
24
78
  }
25
79
 
80
+ /**
81
+ * @returns {number}
82
+ */
26
83
  findingPromisesLength() { return Object.keys(this.findingPromises).length }
27
84
 
28
85
  async waitForFindingPromises() {
@@ -31,6 +88,9 @@ export default class TestFilesFinder {
31
88
  }
32
89
  }
33
90
 
91
+ /**
92
+ * @returns {Promise<void>}
93
+ */
34
94
  async waitForFindingPromisesIteration() {
35
95
  const unfinishedPromises = []
36
96
 
@@ -43,6 +103,9 @@ export default class TestFilesFinder {
43
103
  await Promise.all(unfinishedPromises)
44
104
  }
45
105
 
106
+ /**
107
+ * @param {function() : Promise<void>} callback
108
+ */
46
109
  withFindingCount(callback) {
47
110
  return new Promise((resolve) => {
48
111
  const findingPromise = callback()
@@ -54,11 +117,15 @@ export default class TestFilesFinder {
54
117
  findingPromise.finally(() => {
55
118
  delete this.findingPromises[findingCount]
56
119
 
57
- resolve()
120
+ resolve(undefined)
58
121
  })
59
122
  })
60
123
  }
61
124
 
125
+ /**
126
+ * @param {string} dir
127
+ * @returns {Promise<void>}
128
+ */
62
129
  async findTestFilesInDir(dir) {
63
130
  await this.withFindingCount(async () => {
64
131
  const files = await fs.readdir(dir)
@@ -89,16 +156,32 @@ export default class TestFilesFinder {
89
156
  * @returns {boolean}
90
157
  */
91
158
  isFileMatchingRequirements(file, localPath) {
92
- if (this.testArgs.length > 0) {
93
- for (const testArg of this.testArgs) {
94
- if (testArg == localPath) {
159
+ if (this.directoryArgs.length > 0) {
160
+ for (const directoryArg of this.directoryArgs) {
161
+ if (localPath.startsWith(directoryArg) && this.looksLikeTestFile(file)) {
95
162
  return true
96
163
  }
97
164
  }
98
- } else if (file.match(/-(spec|test)\.(m|)js$/)) {
165
+ }
166
+
167
+ if (this.fileArgs.length > 0) {
168
+ for (const fileArg of this.fileArgs) {
169
+ if (fileArg == localPath) {
170
+ return true
171
+ }
172
+ }
173
+ } else if (this.looksLikeTestFile(file)) {
99
174
  return true
100
175
  }
101
176
 
102
177
  return false
103
178
  }
179
+
180
+ /**
181
+ * @param {string} file
182
+ * @returns {boolean}
183
+ */
184
+ looksLikeTestFile(file) {
185
+ return Boolean(file.match(/-(spec|test)\.(m|)js$/))
186
+ }
104
187
  }
@@ -26,7 +26,7 @@ export default class TestRunner {
26
26
  getConfiguration() { return this._configuration }
27
27
 
28
28
  /**
29
- * @returns {Array<string>}
29
+ * @returns {string[]}
30
30
  */
31
31
  getTestFiles() { return this._testFiles }
32
32