velocious 1.0.85 → 1.0.87

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 (95) hide show
  1. package/bin/velocious.js +10 -1
  2. package/eslint.config.js +33 -0
  3. package/package.json +7 -2
  4. package/peak_flow.yml +6 -0
  5. package/spec/cli/commands/db/create-spec.js +4 -0
  6. package/spec/cli/commands/db/migrate-spec.js +4 -2
  7. package/spec/cli/commands/db/rollback-spec.js +179 -0
  8. package/spec/cli/commands/destroy/migration-spec.js +4 -0
  9. package/spec/cli/commands/generate/migration-spec.js +4 -0
  10. package/spec/cli/commands/init-spec.js +4 -0
  11. package/spec/dummy/index.js +7 -4
  12. package/spec/dummy/src/config/configuration.example.js +2 -0
  13. package/spec/dummy/src/config/configuration.peakflow.mariadb.js +2 -0
  14. package/spec/dummy/src/config/configuration.peakflow.mssql.js +2 -0
  15. package/spec/dummy/src/config/configuration.peakflow.pgsql.js +2 -0
  16. package/spec/dummy/src/config/configuration.peakflow.sqlite.js +2 -0
  17. package/spec/dummy/src/database/migrations/20250921121002-create-project-details.js +3 -1
  18. package/src/application.js +12 -0
  19. package/src/cli/base-command.js +9 -5
  20. package/src/cli/browser-cli.js +37 -0
  21. package/src/cli/commands/db/create.js +5 -5
  22. package/src/cli/commands/db/drop.js +4 -5
  23. package/src/cli/commands/db/migrate.js +6 -10
  24. package/src/cli/commands/db/reset.js +8 -12
  25. package/src/cli/commands/db/rollback.js +15 -0
  26. package/src/cli/commands/destroy/migration.js +2 -2
  27. package/src/cli/commands/generate/migration.js +3 -6
  28. package/src/cli/commands/generate/model.js +3 -6
  29. package/src/cli/commands/init.js +5 -8
  30. package/src/cli/commands/server.js +3 -3
  31. package/src/cli/commands/test.js +1 -1
  32. package/src/cli/index.js +15 -63
  33. package/src/cli/use-browser-cli.js +25 -0
  34. package/src/configuration-resolver.js +1 -1
  35. package/src/configuration.js +118 -9
  36. package/src/controller.js +29 -0
  37. package/src/database/drivers/base-column.js +14 -0
  38. package/src/database/drivers/base-columns-index.js +3 -0
  39. package/src/database/drivers/base-foreign-key.js +3 -0
  40. package/src/database/drivers/base-table.js +3 -0
  41. package/src/database/drivers/base.js +55 -1
  42. package/src/database/drivers/mssql/index.js +64 -1
  43. package/src/database/drivers/mysql/columns-index.js +0 -1
  44. package/src/database/drivers/sqlite/base.js +39 -0
  45. package/src/database/drivers/sqlite/connection-remote.js +1 -1
  46. package/src/database/drivers/sqlite/sql/alter-table.js +1 -1
  47. package/src/database/drivers/sqlite/sql/delete.js +15 -10
  48. package/src/database/migration/index.js +122 -1
  49. package/src/database/migrator/files-finder.js +13 -1
  50. package/src/database/migrator.js +125 -24
  51. package/src/database/pool/single-multi-use.js +1 -1
  52. package/src/database/query/alter-table-base.js +11 -0
  53. package/src/database/query/base.js +7 -0
  54. package/src/database/query/create-database-base.js +3 -0
  55. package/src/database/query/create-index-base.js +3 -1
  56. package/src/database/query/create-table-base.js +3 -0
  57. package/src/database/query/drop-table-base.js +4 -1
  58. package/src/database/query/from-base.js +7 -0
  59. package/src/database/query/index.js +96 -6
  60. package/src/database/query/insert-base.js +6 -0
  61. package/src/database/query/join-base.js +3 -0
  62. package/src/database/query/order-base.js +3 -0
  63. package/src/database/query/select-base.js +3 -0
  64. package/src/database/query/update-base.js +3 -0
  65. package/src/database/query/where-base.js +3 -0
  66. package/src/database/record/index.js +272 -19
  67. package/src/database/record/instance-relationships/base.js +66 -1
  68. package/src/database/record/relationships/base.js +41 -1
  69. package/src/database/record/validators/base.js +10 -0
  70. package/src/database/record/validators/presence.js +1 -1
  71. package/src/database/table-data/table-column.js +37 -3
  72. package/src/database/table-data/table-index.js +1 -1
  73. package/src/database/use-database.js +2 -2
  74. package/src/environment-handlers/base.js +53 -0
  75. package/src/environment-handlers/browser.js +171 -0
  76. package/src/environment-handlers/node.js +162 -0
  77. package/src/http-server/client/request-buffer/index.js +9 -9
  78. package/src/http-server/index.js +6 -0
  79. package/src/http-server/worker-handler/index.js +20 -19
  80. package/src/initializer.js +17 -1
  81. package/src/logger.js +3 -0
  82. package/src/routes/app-routes.js +6 -2
  83. package/src/routes/base-route.js +1 -1
  84. package/src/routes/get-route.js +1 -1
  85. package/src/routes/namespace-route.js +1 -1
  86. package/src/routes/post-route.js +1 -1
  87. package/src/routes/resolver.js +1 -1
  88. package/src/routes/resource-route.js +1 -1
  89. package/src/templates/configuration.js +4 -0
  90. package/src/testing/request-client.js +26 -3
  91. package/src/testing/test-files-finder.js +2 -2
  92. package/src/testing/test-runner.js +74 -28
  93. package/src/testing/test.js +62 -0
  94. package/src/utils/file-exists.js +7 -5
  95. package/src/utils/rest-args-error.js +5 -3
@@ -6,12 +6,37 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
6
6
  this.relationship = relationship
7
7
  }
8
8
 
9
+ /**
10
+ * @returns {boolean} Whether the relationship should be auto-saved before saving the parent model
11
+ */
9
12
  getAutoSave() { return this._autoSave }
13
+
14
+ /**
15
+ * @param {boolean} newAutoSaveValue Whether the relationship should be auto-saved before saving the parent model
16
+ * @returns {void}
17
+ */
10
18
  setAutoSave(newAutoSaveValue) { this._autoSave = newAutoSaveValue }
19
+
20
+ /**
21
+ * @param {boolean} newValue Whether the relationship is dirty (has been modified)
22
+ * @returns {void}
23
+ */
11
24
  setDirty(newValue) { this._dirty = newValue }
25
+
26
+ /**
27
+ * @returns {boolean} Whether the relationship is dirty (has been modified)
28
+ */
12
29
  getDirty() { return this._dirty }
30
+
31
+ /**
32
+ * @returns {boolean} Whether the relationship has been preloaded
33
+ */
13
34
  isLoaded() { return Boolean(this._loaded) }
14
35
 
36
+ /**
37
+ * @template T extends import("../index.js").default
38
+ * @returns {T|Array<T>} The loaded model or models (depending on relationship type)
39
+ */
15
40
  loaded() {
16
41
  if (!this._preloaded && this.model.isPersisted()) {
17
42
  throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
@@ -20,13 +45,53 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
20
45
  return this._loaded
21
46
  }
22
47
 
48
+ /**
49
+ * @template T extends import("../index.js").default
50
+ * @param {T|Array<T>} model
51
+ */
23
52
  setLoaded(model) { this._loaded = model }
53
+
54
+ /**
55
+ * @template T extends import("../index.js").default
56
+ * @returns {T|Array<T>} The loaded model or models (depending on relationship type)
57
+ */
24
58
  getPreloaded() { return this._preloaded }
25
- setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
59
+
60
+ /**
61
+ * @template T extends import("../index.js").default
62
+ * @param {T|Array<T>} preloadedModelOrModels
63
+ */
64
+ setPreloaded(preloadedModelOrModels) { this._preloaded = preloadedModelOrModels }
65
+
66
+ /**
67
+ * @returns {string} The foreign key for this relationship
68
+ */
26
69
  getForeignKey() { return this.getRelationship().getForeignKey() }
70
+
71
+ /**
72
+ * @template T extends import("../index.js").default
73
+ * @returns {T} model
74
+ */
27
75
  getModel() { return this.model }
76
+
77
+ /**
78
+ * @returns {string} The primary key for this relationship's model
79
+ */
28
80
  getPrimaryKey() { return this.getRelationship().getPrimaryKey() }
81
+
82
+ /**
83
+ * @template T extends import("../relationships/base.js").default
84
+ * @returns {T} The relationship object that this instance relationship is based on
85
+ */
29
86
  getRelationship() { return this.relationship }
87
+
88
+ /**
89
+ * @returns {typeof import("../index.js").default} The model class that this instance relationship
90
+ */
30
91
  getTargetModelClass() { return this.getRelationship().getTargetModelClass() }
92
+
93
+ /**
94
+ * @returns {string} The type of relationship (e.g. "has_many", "belongs_to", etc.)
95
+ */
31
96
  getType() { return this.getRelationship().getType() }
32
97
  }
@@ -1,7 +1,7 @@
1
1
  import restArgsError from "../../../utils/rest-args-error.js"
2
2
 
3
3
  export default class VelociousDatabaseRecordBaseRelationship {
4
- constructor({className, configuration, dependent, foreignKey, inverseOf, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
4
+ constructor({className, configuration, dependent, foreignKey, inverseOf, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) { // eslint-disable-line no-unused-vars
5
5
  restArgsError(restArgs)
6
6
 
7
7
  if (!modelClass) throw new Error(`'modelClass' wasn't given for ${relationshipName}`)
@@ -20,12 +20,52 @@ export default class VelociousDatabaseRecordBaseRelationship {
20
20
  this.type = type
21
21
  }
22
22
 
23
+ /**
24
+ * @returns {string} What will be done when the parent record is destroyed. E.g. "destroy", "nullify", "restrict" etc.
25
+ */
23
26
  getDependent() { return this._dependent }
27
+
28
+ /**
29
+ * @interface
30
+ * @returns {string} The name of the foreign key, e.g. "user_id", "post_id" etc.
31
+ */
32
+ getForeignKey() {
33
+ throw new Error("getForeignKey not implemented")
34
+ }
35
+
36
+ /**
37
+ * @interface
38
+ * @returns {string} The name of the inverse relationship, e.g. "posts", "comments" etc.
39
+ */
40
+ getInverseOf() {
41
+ throw new Error("getInverseOf not implemented")
42
+ }
43
+
44
+ /**
45
+ * @template T extends import("../index.js").default
46
+ * @returns {typeof T}
47
+ */
24
48
  getModelClass() { return this.modelClass }
49
+
50
+ /**
51
+ * @returns {string} The name of the relationship, e.g. "posts", "user", "comments" etc.
52
+ */
25
53
  getRelationshipName() { return this.relationshipName }
54
+
55
+ /**
56
+ * @returns {string} The name of the foreign key, e.g. "id" etc.
57
+ */
26
58
  getPrimaryKey() { return this._primaryKey }
59
+
60
+ /**
61
+ * @returns {string} The type of the relationship, e.g. "has_many", "belongs_to", "has_one", "has_and_belongs_to_many" etc.
62
+ */
27
63
  getType() { return this.type }
28
64
 
65
+ /**
66
+ * @template T extends import("../index.js").default
67
+ * @returns {typeof T} The target model class for this relationship, e.g. if the relationship is "posts" then the target model class is the Post class.
68
+ */
29
69
  getTargetModelClass() {
30
70
  if (this.className) {
31
71
  return this.modelClass._getConfiguration().getModelClass(this.className)
@@ -1,2 +1,12 @@
1
1
  export default class VelociousDatabaseRecordValidatorsBase {
2
+ /**
3
+ * @interface
4
+ * @template T extends import("../index.js").default
5
+ * @param {object} args
6
+ * @param {T} args.model
7
+ * @param {string} args.attributeName
8
+ */
9
+ async validate({model, attributeName}) { // eslint-disable-line no-unused-vars
10
+ throw new Error("validate not implemented")
11
+ }
2
12
  }
@@ -1,7 +1,7 @@
1
1
  import Base from "./base.js"
2
2
 
3
3
  export default class VelociousDatabaseRecordValidatorsPresence extends Base {
4
- validate({model, attributeName}) {
4
+ async validate({model, attributeName}) {
5
5
  const attributeValue = model.readAttribute(attributeName)?.trim()
6
6
 
7
7
  if (!attributeValue) {
@@ -5,7 +5,7 @@ import TableForeignKey from "./table-foreign-key.js"
5
5
  export default class TableColumn {
6
6
  constructor(name, args) {
7
7
  if (args) {
8
- const {autoIncrement, default: columnDefault, foreignKey, index, isNewColumn, maxLength, name, null: argsNull, primaryKey, type, ...restArgs} = args
8
+ const {autoIncrement, default: columnDefault, dropColumn, foreignKey, index, isNewColumn, maxLength, name, null: argsNull, primaryKey, type, ...restArgs} = args // eslint-disable-line no-unused-vars
9
9
 
10
10
  if (Object.keys(args).length == 0) {
11
11
  throw new Error("Empty args given")
@@ -18,19 +18,50 @@ export default class TableColumn {
18
18
  this.name = name
19
19
  }
20
20
 
21
+ /**
22
+ * @returns {string} name
23
+ */
21
24
  getName() { return this.name }
22
25
 
26
+ /**
27
+ * @returns {string}
28
+ */
23
29
  getNewName() { return this._newName }
30
+
31
+ /**
32
+ * @param {string} newName
33
+ * @returns {void}
34
+ */
24
35
  setNewName(newName) { this._newName = newName }
25
36
 
37
+ /**
38
+ * @returns {string}
39
+ */
26
40
  getActualName() { return this.getNewName() || this.getName() }
27
41
 
28
- getAutoIncrement() { return this.args?.autoIncrement }
42
+ /**
43
+ * @returns {boolean}
44
+ */
45
+ getAutoIncrement() { return this.args?.autoIncrement || false }
46
+
47
+ /**
48
+ * @param {boolean} newAutoIncrement
49
+ * @returns {void}
50
+ */
29
51
  setAutoIncrement(newAutoIncrement) { this.args.autoIncrement = newAutoIncrement }
30
52
 
31
53
  getDefault() { return this.args?.default }
54
+
55
+ /**
56
+ * @returns {void}
57
+ */
32
58
  setDefault(newDefault) { this.args.default = newDefault }
33
59
 
60
+ /**
61
+ * @returns {boolean}
62
+ */
63
+ getDropColumn() { return this.args?.dropColumn || false }
64
+
34
65
  getForeignKey() { return this.args?.foreignKey }
35
66
  setForeignKey(newForeignKey) { this.args.foreignKey = newForeignKey }
36
67
 
@@ -49,7 +80,10 @@ export default class TableColumn {
49
80
  getType() { return this.args?.type }
50
81
  setType(newType) { this.args.type = newType }
51
82
 
52
- isNewColumn() { return this.args?.isNewColumn }
83
+ /**
84
+ * @returns {boolean}
85
+ */
86
+ isNewColumn() { return this.args?.isNewColumn || false }
53
87
 
54
88
  getSQL({forAlterTable, driver}) {
55
89
  const databaseType = driver.getType()
@@ -3,7 +3,7 @@ import restArgsError from "../../utils/rest-args-error.js"
3
3
  export default class TableIndex {
4
4
  constructor(columns, args) {
5
5
  if (args) {
6
- const {name, unique, ...restArgs} = args
6
+ const {name, unique, ...restArgs} = args // eslint-disable-line no-unused-vars
7
7
 
8
8
  restArgsError(restArgs)
9
9
  }
@@ -5,7 +5,7 @@ import Configuration from "../configuration.js"
5
5
  import Migrator from "./migrator.js"
6
6
  import restArgsError from "../utils/rest-args-error.js"
7
7
 
8
- const loadMigrations = function loadMigrations({migrationsRequireContext, ...restArgs}) {
8
+ const loadMigrations = function loadMigrations({migrationsRequireContextCallback, ...restArgs}) {
9
9
  const instance = React.useMemo(() => ({running: false}), [])
10
10
  const {isServer} = useEnvSense()
11
11
  const [loaded, setLoaded] = React.useState(false)
@@ -18,7 +18,7 @@ const loadMigrations = function loadMigrations({migrationsRequireContext, ...res
18
18
  const migrator = new Migrator({configuration: Configuration.current()})
19
19
 
20
20
  await migrator.prepare()
21
- await migrator.migrateFilesFromRequireContext(migrationsRequireContext)
21
+ await migrator.migrateFilesFromRequireContext(await migrationsRequireContextCallback())
22
22
  })
23
23
 
24
24
  await Configuration.current().initialize()
@@ -0,0 +1,53 @@
1
+ export default class VelociousEnvironmentHandlerBase {
2
+ /**
3
+ * @interface
4
+ */
5
+ async findCommands() { throw new Error("findCommands not implemented") }
6
+
7
+ /**
8
+ * @interface
9
+ */
10
+ async findMigrations() { throw new Error("findMigrations not implemneted") }
11
+
12
+ /**
13
+ * @interface
14
+ */
15
+ async getVelociousPath() { throw new Error("getVelociousPath not implemented") }
16
+
17
+ /**
18
+ * @interface
19
+ */
20
+ async importApplicationRoutes() { throw new Error("importApplicationRoutes not implemented") }
21
+
22
+ /**
23
+ * @interface
24
+ */
25
+ async requireCommand({commandParts}) { throw new Error("requireCommand not implemented") } // eslint-disable-line no-unused-vars
26
+
27
+ /**
28
+ * @param {object} newArgs
29
+ * @returns {void}
30
+ */
31
+ setArgs(newArgs) { this.args = newArgs }
32
+
33
+ /**
34
+ * @param {import("../configuration.js").default} newConfiguration
35
+ * @returns {void}
36
+ */
37
+ setConfiguration(newConfiguration) { this.configuration = newConfiguration }
38
+
39
+ /**
40
+ * @returns {import("../configuration.js").default}
41
+ */
42
+ getConfiguration() {
43
+ if (!this.configuration) throw new Error("Configuration hasn't been set")
44
+
45
+ return this.configuration
46
+ }
47
+
48
+ /**
49
+ * @param {string[]} newProcessArgs
50
+ * @returns {void}
51
+ */
52
+ setProcessArgs(newProcessArgs) { this.processArgs = newProcessArgs }
53
+ }
@@ -0,0 +1,171 @@
1
+ import Base from "./base.js"
2
+ import {digg} from "diggerize"
3
+ import * as inflection from "inflection"
4
+ import restArgsError from "../utils/rest-args-error.js"
5
+
6
+ export default class VelociousEnvironmentsHandlerBrowser extends Base {
7
+ /**
8
+ * @param {object} args
9
+ * @param {function() : void} args.migrationsRequireContextCallback
10
+ */
11
+ constructor({migrationsRequireContextCallback, ...restArgs} = {}) {
12
+ super()
13
+ restArgsError(restArgs)
14
+
15
+ this.migrationsRequireContextCallback = migrationsRequireContextCallback
16
+ }
17
+
18
+ /**
19
+ * @returns {object}
20
+ */
21
+ migrationsRequireContext() {
22
+ const migrationsRequireContextCallback = digg(this, "migrationsRequireContextCallback")
23
+
24
+ if (!migrationsRequireContextCallback) throw new Error("migrationsRequireContextCallback is required")
25
+
26
+ this._migrationsRequireContextResult ||= migrationsRequireContextCallback()
27
+
28
+ return this._migrationsRequireContextResult
29
+ }
30
+
31
+ /**
32
+ * @returns {Promise<Array<{name: string, file: string}>>}
33
+ */
34
+ findCommands() {
35
+ this._findCommandsResult = this._actualFindCommands()
36
+
37
+ return this._findCommandsResult
38
+ }
39
+
40
+ _findCommandsRequireContext() {
41
+ this.findCommandsRequireContextResult ||= require.context("../cli/commands", true, /\.js$/)
42
+
43
+ return this.findCommandsRequireContextResult
44
+ }
45
+
46
+ _actualFindCommands() {
47
+ const commandFiles = this._findCommandsRequireContext()
48
+ const commands = []
49
+
50
+ for (const aFilePath of commandFiles.keys()) {
51
+ const aFilePathParts = aFilePath.split("/")
52
+ const lastPart = aFilePathParts[aFilePathParts.length - 1]
53
+ let name
54
+
55
+ if (lastPart == "index.js") {
56
+ name = aFilePathParts[aFilePathParts.length - 2]
57
+ } else {
58
+ name = lastPart.replace(".js", "")
59
+ }
60
+
61
+ commands.push({name, file: aFilePath})
62
+ }
63
+
64
+ return commands
65
+ }
66
+
67
+ /**
68
+ * @param {Array<string>} commandParts
69
+ * @template T extends import("./base-command.js").default
70
+ * @returns {Promise<T>}
71
+ */
72
+ async requireCommand({commandParts, ...restArgs}) {
73
+ restArgsError(restArgs)
74
+
75
+ let filePath = "."
76
+
77
+ for (let commandPart of commandParts) {
78
+ if (commandPart == "d") commandPart = "destroy"
79
+ if (commandPart == "g") commandPart = "generate"
80
+ if (commandPart == "s") commandPart = "server"
81
+
82
+ filePath += `/${commandPart}`
83
+ }
84
+
85
+ const filePaths = []
86
+
87
+ filePaths.push(`${filePath}/index.js`)
88
+ filePath += ".js"
89
+ filePaths.push(filePath)
90
+
91
+ const commandsRequireContext = await this._findCommandsRequireContext()
92
+ let commandClassImport
93
+
94
+ for (const aFilePath of filePaths) {
95
+ commandClassImport = commandsRequireContext(aFilePath)
96
+
97
+ if (commandClassImport) {
98
+ break
99
+ }
100
+ }
101
+
102
+ if (!commandClassImport) {
103
+ throw new Error(`Unknown command: ${commandParts.join(":")}. Possible commands: ${commandsRequireContext.keys()}`)
104
+ }
105
+
106
+ const CommandClass = commandClassImport.default
107
+
108
+ return CommandClass
109
+ }
110
+
111
+ /**
112
+ * @returns {Promise<Array<{name: string, file: string}>>}
113
+ */
114
+ async findMigrations() {
115
+ const migrationsRequireContext = await this.migrationsRequireContext()
116
+ const files = migrationsRequireContext
117
+ .keys()
118
+ .map((file) => {
119
+ // "13,14" because somes "require-context"-npm-module deletes first character!?
120
+ const match = file.match(/(\d{13,14})-(.+)\.js$/)
121
+
122
+ if (!match) return null
123
+
124
+ // Fix require-context-npm-module deletes first character
125
+ let fileName = file
126
+ let dateNumber = match[1]
127
+
128
+ if (dateNumber.length == 13) {
129
+ dateNumber = `2${dateNumber}`
130
+ fileName = `2${fileName}`
131
+ }
132
+
133
+ // Parse regex
134
+ const date = parseInt(dateNumber)
135
+ const migrationName = match[2]
136
+ const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
137
+
138
+ return {
139
+ file: fileName,
140
+ fullPath: file,
141
+ date,
142
+ migrationClassName
143
+ }
144
+ })
145
+ .filter((migration) => Boolean(migration))
146
+ .sort((migration1, migration2) => migration1.date - migration2.date)
147
+
148
+ return files
149
+ }
150
+
151
+ /**
152
+ * @param {string} filePath
153
+ * @template T extends import("../migration/index.js").default
154
+ * @returns {Promise<T>}
155
+ */
156
+ requireMigration = async (filePath) => {
157
+ if (!filePath) throw new Error("filePath is required")
158
+
159
+ const migrationsRequireContext = await this.migrationsRequireContext()
160
+ const migrationImport = migrationsRequireContext(filePath)
161
+
162
+ if (!migrationImport) throw new Error(`Migration file ${filePath} not found`)
163
+
164
+ const migrationImportDefault = migrationImport.default
165
+
166
+ if (!migrationImportDefault) throw new Error("Migration file must export a default migration class")
167
+ if (typeof migrationImportDefault !== "function") throw new Error("Migration default export isn't a function (should be a class which is a function in JS)")
168
+
169
+ return migrationImportDefault
170
+ }
171
+ }
@@ -0,0 +1,162 @@
1
+ import Base from "./base.js"
2
+ import {dirname} from "path"
3
+ import {fileURLToPath} from "url"
4
+ import fs from "fs/promises"
5
+ import * as inflection from "inflection"
6
+ import path from "path"
7
+
8
+ export default class VelociousEnvironmentHandlerNode extends Base{
9
+ /**
10
+ * @returns {Promise<Array<{name: string, file: string}>>}
11
+ */
12
+ async findCommands() {
13
+ this._findCommandsResult ||= this._actualFindCommands()
14
+
15
+ return this._findCommandsResult
16
+ }
17
+
18
+ async _actualFindCommands() {
19
+ const basePath = await this.getBasePath()
20
+ const commandFiles = fs.glob(`${basePath}/src/cli/commands/**/*.js`)
21
+ const commands = []
22
+
23
+ for await (const aFilePath of commandFiles) {
24
+ const aFilePathParts = aFilePath.split("/")
25
+ const commandPathLocation = aFilePathParts.indexOf("commands") + 1
26
+ const lastPart = aFilePathParts[aFilePathParts.length - 1]
27
+ let name, paths
28
+
29
+ if (lastPart == "index.js") {
30
+ name = aFilePathParts[aFilePathParts.length - 2]
31
+ paths = aFilePathParts.slice(commandPathLocation, -2)
32
+ } else {
33
+ name = lastPart.replace(".js", "")
34
+ paths = aFilePathParts.slice(commandPathLocation, -1)
35
+ }
36
+
37
+ const commandName = `${paths.join(":")}${paths.length > 0 ? ":" : ""}${name}`
38
+
39
+ commands.push({name: commandName, file: aFilePath})
40
+ }
41
+
42
+ return commands
43
+ }
44
+
45
+ /**
46
+ * @param {Array<string>} commandParts
47
+ * @template T extends import ("./base-command.js").default
48
+ * @returns {Promise<T>}
49
+ */
50
+ async requireCommand({commandParts}) {
51
+ const commands = await this.findCommands()
52
+ const command = commands.find((aCommand) => aCommand.name === commandParts.join(":"))
53
+
54
+ if (!command) {
55
+ const possibleCommands = commands.map(aCommand => aCommand.name)
56
+
57
+ throw new Error(`Unknown command: ${this.args.processArgs[0]} which should have been one of: ${possibleCommands.sort().join(", ")}`)
58
+ }
59
+
60
+ const commandClassImport = await import(command.file)
61
+ const CommandClass = commandClassImport.default
62
+
63
+ return CommandClass
64
+ }
65
+
66
+ /**
67
+ * @returns {Promise<Array<{name: string, file: string}>>}
68
+ */
69
+ async findMigrations() {
70
+ const migrationsPath = `${this.getConfiguration().getDirectory()}/src/database/migrations`
71
+ const glob = await fs.glob(`${migrationsPath}/**/*.js`)
72
+ const files = []
73
+
74
+ for await (const fullPath of glob) {
75
+ const file = await path.basename(fullPath)
76
+
77
+ files.push(file)
78
+ }
79
+
80
+ const migrationFiles = files
81
+ .map((file) => {
82
+ const match = file.match(/^(\d{14})-(.+)\.js$/)
83
+
84
+ if (!match) return null
85
+
86
+ const date = parseInt(match[1])
87
+ const migrationName = match[2]
88
+ const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
89
+
90
+ return {
91
+ file,
92
+ fullPath: `${migrationsPath}/${file}`,
93
+ date,
94
+ migrationClassName
95
+ }
96
+ })
97
+ .filter((migration) => Boolean(migration))
98
+ .sort((migration1, migration2) => migration1.date - migration2.date)
99
+
100
+ return migrationFiles
101
+ }
102
+
103
+ /**
104
+ * @returns {Promise<import("../routes/index.js").default>}
105
+ */
106
+ async importApplicationRoutes() {
107
+ const routesImport = await import(`${this.getConfiguration().getDirectory()}/src/config/routes.js`)
108
+
109
+ return routesImport.default
110
+ }
111
+
112
+ /**
113
+ * @returns {string}
114
+ */
115
+ async getVelociousPath() {
116
+ if (!this._velociousPath) {
117
+ const __filename = fileURLToPath(import.meta.url)
118
+ const __dirname = dirname(__filename)
119
+
120
+ this._velociousPath = await fs.realpath(`${__dirname}/../..`)
121
+ }
122
+
123
+ return this._velociousPath
124
+ }
125
+
126
+ /**
127
+ * @returns {Promise<void>}
128
+ */
129
+ async importTestFiles(testFiles) {
130
+ for (const testFile of testFiles) {
131
+ await import(testFile)
132
+ }
133
+ }
134
+
135
+ async importTestingConfigPath() {
136
+ const testingConfigPath = this.getConfiguration().getTesting()
137
+
138
+ await import(testingConfigPath)
139
+ }
140
+
141
+ /**
142
+ * @param {string} filePath
143
+ * @template T extends import ("../migration/index.js").default
144
+ * @returns {Promise<T>}
145
+ */
146
+ async requireMigration(filePath) {
147
+ const migrationImport = await import(filePath)
148
+ const migrationImportDefault = migrationImport.default
149
+
150
+ if (!migrationImportDefault) throw new Error("Migration file must export a default migration class")
151
+ if (typeof migrationImportDefault !== "function") throw new Error("Migration default export isn't a function (should be a class which is a function in JS)")
152
+
153
+ return migrationImportDefault
154
+ }
155
+
156
+ async getBasePath() {
157
+ const __filename = fileURLToPath(import.meta.url)
158
+ const basePath = await fs.realpath(`${dirname(__filename)}/../..`)
159
+
160
+ return basePath
161
+ }
162
+ }