velocious 1.0.29 → 1.0.31

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 (63) hide show
  1. package/package.json +5 -2
  2. package/peak_flow.yml +21 -0
  3. package/spec/cli/commands/db/create-spec.js +14 -2
  4. package/spec/cli/commands/db/migrate-spec.js +56 -25
  5. package/spec/database/drivers/mysql/connection-spec.js +2 -2
  6. package/spec/dummy/index.js +8 -7
  7. package/spec/dummy/src/config/configuration.example.js +25 -3
  8. package/spec/dummy/src/config/configuration.mariadb.js +97 -0
  9. package/spec/dummy/src/config/configuration.peakflow.mariadb.js +26 -3
  10. package/spec/dummy/src/config/configuration.peakflow.mssql.js +75 -0
  11. package/spec/dummy/src/config/configuration.peakflow.sqlite.js +27 -3
  12. package/spec/dummy/src/config/configuration.sqlite.js +58 -4
  13. package/spec/dummy/src/database/migrations/20250903112845-create-accounts.js +18 -0
  14. package/src/cli/commands/db/create.js +25 -15
  15. package/src/cli/commands/db/migrate.js +4 -82
  16. package/src/cli/commands/db/reset.js +40 -0
  17. package/src/configuration.js +71 -18
  18. package/src/database/drivers/base.js +97 -13
  19. package/src/database/drivers/mssql/column.js +10 -0
  20. package/src/database/drivers/mssql/connect-connection.js +12 -0
  21. package/src/database/drivers/mssql/foreign-key.js +13 -0
  22. package/src/database/drivers/mssql/index.js +216 -0
  23. package/src/database/drivers/mssql/options.js +43 -0
  24. package/src/database/drivers/mssql/query-parser.js +4 -0
  25. package/src/database/drivers/mssql/sql/create-database.js +28 -0
  26. package/src/database/drivers/mssql/sql/create-index.js +4 -0
  27. package/src/database/drivers/mssql/sql/create-table.js +4 -0
  28. package/src/database/drivers/mssql/sql/delete.js +19 -0
  29. package/src/database/drivers/mssql/sql/drop-table.js +4 -0
  30. package/src/database/drivers/mssql/sql/insert.js +4 -0
  31. package/src/database/drivers/mssql/sql/update.js +31 -0
  32. package/src/database/drivers/mssql/table.js +65 -0
  33. package/src/database/drivers/mysql/index.js +29 -18
  34. package/src/database/drivers/mysql/query.js +1 -1
  35. package/src/database/drivers/mysql/sql/drop-table.js +4 -0
  36. package/src/database/drivers/sqlite/base.js +25 -23
  37. package/src/database/drivers/sqlite/index.native.js +10 -1
  38. package/src/database/drivers/sqlite/sql/drop-table.js +4 -0
  39. package/src/database/initializer-from-require-context.js +3 -1
  40. package/src/database/migration/index.js +32 -23
  41. package/src/database/migrator.js +177 -28
  42. package/src/database/pool/async-tracked-multi-connection.js +2 -3
  43. package/src/database/pool/base.js +15 -3
  44. package/src/database/query/base.js +24 -4
  45. package/src/database/query/create-database-base.js +1 -3
  46. package/src/database/query/create-index-base.js +13 -4
  47. package/src/database/query/create-table-base.js +27 -11
  48. package/src/database/query/drop-table-base.js +39 -0
  49. package/src/database/query/index.js +5 -1
  50. package/src/database/query/insert-base.js +31 -6
  51. package/src/database/query-parser/limit-parser.js +11 -2
  52. package/src/database/query-parser/options.js +20 -8
  53. package/src/database/record/index.js +19 -3
  54. package/src/database/use-database.js +5 -4
  55. package/src/routes/base-route.js +7 -0
  56. package/src/routes/post-route.js +24 -0
  57. package/src/routes/resolver.js +1 -1
  58. package/src/templates/configuration.js +36 -3
  59. package/src/testing/test-runner.js +1 -1
  60. package/src/utils/nest-callbacks.js +15 -0
  61. package/src/big-brother.js +0 -37
  62. package/src/database/migrate-from-require-context.js +0 -72
  63. package/src/spec/index.js +0 -5
@@ -1,7 +1,7 @@
1
1
  import restArgsError from "../../utils/rest-args-error.js"
2
2
 
3
3
  export default class VelociousDatabaseQueryInsertBase {
4
- constructor({columns, data, driver, multiple, tableName, rows, ...restArgs}) {
4
+ constructor({columns, data, driver, multiple, tableName, returnLastInsertedColumnName, rows, ...restArgs}) {
5
5
  if (!driver) throw new Error("No driver given to insert base")
6
6
  if (!tableName) throw new Error(`Invalid table name given to insert base: ${tableName}`)
7
7
 
@@ -11,6 +11,7 @@ export default class VelociousDatabaseQueryInsertBase {
11
11
  this.data = data
12
12
  this.driver = driver
13
13
  this.multiple = multiple
14
+ this.returnLastInsertedColumnName = returnLastInsertedColumnName
14
15
  this.rows = rows
15
16
  this.tableName = tableName
16
17
  }
@@ -20,9 +21,23 @@ export default class VelociousDatabaseQueryInsertBase {
20
21
  }
21
22
 
22
23
  toSql() {
23
- let sql = `INSERT INTO ${this.getOptions().quoteTableName(this.tableName)}`
24
+ const {driver} = this
25
+
26
+ let sql = `INSERT INTO ${driver.quoteTable(this.tableName)}`
24
27
  let count = 0
25
- let columns
28
+ let columns, lastInsertedSQL
29
+
30
+ if (this.returnLastInsertedColumnName) {
31
+ if (driver.getType() == "mssql") {
32
+ lastInsertedSQL = ` OUTPUT INSERTED.${driver.quoteColumn(this.returnLastInsertedColumnName)} AS lastInsertID`
33
+
34
+ if (Object.keys(this.data).length <= 0) {
35
+ sql += lastInsertedSQL
36
+ }
37
+ } else if (driver.getType() == "mysql") {
38
+ lastInsertedSQL = ` RETURNING ${driver.quoteColumn(this.returnLastInsertedColumnName)} AS lastInsertID`
39
+ }
40
+ }
26
41
 
27
42
  if (this.columns && this.rows) {
28
43
  columns = this.columns
@@ -38,13 +53,17 @@ export default class VelociousDatabaseQueryInsertBase {
38
53
  for (const columnName of columns) {
39
54
  if (count > 0) sql += ", "
40
55
 
41
- sql += this.getOptions().quoteColumnName(columnName)
56
+ sql += driver.quoteColumn(columnName)
42
57
  count++
43
58
  }
44
59
 
45
60
  sql += ")"
46
61
  }
47
62
 
63
+ if (this.returnLastInsertedColumnName && driver.getType() == "mssql" && Object.keys(this.data).length > 0) {
64
+ sql += lastInsertedSQL
65
+ }
66
+
48
67
  if (this.columns && this.rows) {
49
68
  if (this.rows.length > 0) {
50
69
  sql += " VALUES "
@@ -62,13 +81,19 @@ export default class VelociousDatabaseQueryInsertBase {
62
81
  if (Object.keys(this.data).length > 0) {
63
82
  sql += " VALUES "
64
83
  sql += this._valuesSql(Object.values(this.data))
65
- } else if (this.driver.getType() == "sqlite") {
84
+ } else if (driver.getType() == "sqlite" || driver.getType() == "mssql") {
66
85
  sql += " DEFAULT VALUES"
67
- } else if (this.driver.getType() == "mysql") {
86
+ } else if (driver.getType() == "mysql") {
68
87
  sql += " () VALUES ()"
69
88
  }
70
89
  }
71
90
 
91
+ if (this.returnLastInsertedColumnName) {
92
+ if (driver.getType() == "mysql") {
93
+ sql += lastInsertedSQL
94
+ }
95
+ }
96
+
72
97
  return sql
73
98
  }
74
99
 
@@ -8,6 +8,7 @@ export default class VelocuiousDatabaseQueryParserLimitParser {
8
8
 
9
9
  toSql() {
10
10
  const {pretty, query} = digs(this, "pretty", "query")
11
+ const driver = query.driver
11
12
  let sql = ""
12
13
 
13
14
  if (query._limits.length == 0) return sql
@@ -19,7 +20,11 @@ export default class VelocuiousDatabaseQueryParserLimitParser {
19
20
  sql += " "
20
21
  }
21
22
 
22
- sql += "LIMIT"
23
+ if (driver.getType() == "mssql") {
24
+ // sql += "BETWEEN"
25
+ } else {
26
+ sql += "LIMIT"
27
+ }
23
28
 
24
29
  for (const limitKey in query._limits) {
25
30
  const limit = query._limits[limitKey]
@@ -32,7 +37,11 @@ export default class VelocuiousDatabaseQueryParserLimitParser {
32
37
  sql += " "
33
38
  }
34
39
 
35
- sql += this.query.getOptions().quote(limit)
40
+ if (driver.getType() == "mssql") {
41
+ sql += `OFFSET 0 ROWS FETCH FIRST ${this.query.getOptions().quote(limit)} ROWS ONLY`
42
+ } else {
43
+ sql += this.query.getOptions().quote(limit)
44
+ }
36
45
  }
37
46
 
38
47
  return sql
@@ -11,22 +11,34 @@ export default class VelociousDatabaseQueryParserOptions {
11
11
  if (!this.driver) throw new Error("No driver given to parser options")
12
12
  }
13
13
 
14
+ quote(value) {
15
+ if (typeof value == "number") return value
16
+
17
+ return this.quoteString(value)
18
+ }
19
+
20
+ quoteDatabaseName(databaseName) {
21
+ if (databaseName.includes(this.tableQuote)) throw new Error(`Possible SQL injection in database name: ${databaseName}`)
22
+
23
+ return `${this.tableQuote}${databaseName}${this.tableQuote}`
24
+ }
25
+
14
26
  quoteColumnName(columnName) {
15
- if (!columnName || columnName.includes(this.columnQuote)) throw new Error(`Invalid column name: ${columnName}`)
27
+ if (!columnName) throw new Error("No column name was given")
28
+ if (columnName.includes(this.columnQuote)) throw new Error(`Invalid column name: ${columnName}`)
16
29
 
17
30
  return `${this.columnQuote}${columnName}${this.columnQuote}`
18
31
  }
19
32
 
20
- quoteTableName(tableName) {
21
- if (!tableName || tableName.includes(this.tableQuote)) throw new Error(`Invalid table name: ${tableName}`)
33
+ quoteIndexName(indexName) {
34
+ if (!indexName || indexName.includes(this.columnQuote)) throw new Error(`Invalid column name: ${indexName}`)
22
35
 
23
- return `${this.tableQuote}${tableName}${this.tableQuote}`
36
+ return `${this.columnQuote}${indexName}${this.columnQuote}`
24
37
  }
25
38
 
26
- quote(value) {
27
- if (typeof value == "number")
28
- return value
39
+ quoteTableName(tableName) {
40
+ if (!tableName || tableName.includes(this.tableQuote)) throw new Error(`Invalid table name: ${tableName}`)
29
41
 
30
- return this.quoteString(value)
42
+ return `${this.tableQuote}${tableName}${this.tableQuote}`
31
43
  }
32
44
  }
@@ -117,7 +117,8 @@ export default class VelociousDatabaseRecord {
117
117
  }
118
118
 
119
119
  static connection() {
120
- const connection = this._getConfiguration().getDatabasePoolType().current().getCurrentConnection()
120
+ const databasePool = this._getConfiguration().getDatabasePool(this.getDatabaseIdentifier())
121
+ const connection = databasePool.getCurrentConnection()
121
122
 
122
123
  if (!connection) throw new Error("No connection?")
123
124
 
@@ -229,6 +230,14 @@ export default class VelociousDatabaseRecord {
229
230
  }
230
231
  }
231
232
 
233
+ static getDatabaseIdentifier() {
234
+ return this._databaseIdentifier || "default"
235
+ }
236
+
237
+ static setDatabaseIdentifier(databaseIdentifier) {
238
+ this._databaseIdentifier = databaseIdentifier
239
+ }
240
+
232
241
  getAttribute(name) {
233
242
  const columnName = inflection.underscore(name)
234
243
 
@@ -681,11 +690,18 @@ export default class VelociousDatabaseRecord {
681
690
 
682
691
  const data = Object.assign({}, this._belongsToChanges(), this.attributes())
683
692
  const sql = this._connection().insertSql({
693
+ returnLastInsertedColumnName: this.constructor.primaryKey(),
684
694
  tableName: this._tableName(),
685
695
  data
686
696
  })
687
- await this._connection().query(sql)
688
- const id = await this._connection().lastInsertID()
697
+ const insertResult = await this._connection().query(sql)
698
+ let id
699
+
700
+ if (insertResult && insertResult[0]?.lastInsertID) {
701
+ id = insertResult[0]?.lastInsertID
702
+ } else {
703
+ id = await this._connection().lastInsertID()
704
+ }
689
705
 
690
706
  await this._reloadWithId(id)
691
707
  this.setIsNewRecord(false)
@@ -1,8 +1,8 @@
1
- import DatabaseMigrateFromRequireContext from "./migrate-from-require-context.js"
2
1
  import React from "react"
3
2
  import useEnvSense from "env-sense/src/use-env-sense.js"
4
3
 
5
4
  import Configuration from "../configuration.js"
5
+ import Migrator from "./migrator.js"
6
6
  import restArgsError from "../utils/rest-args-error.js"
7
7
 
8
8
  const loadMigrations = function loadMigrations({migrationsRequireContext, ...restArgs}) {
@@ -14,10 +14,11 @@ const loadMigrations = function loadMigrations({migrationsRequireContext, ...res
14
14
  instance.running = true
15
15
 
16
16
  try {
17
- await Configuration.current().getDatabasePool().withConnection(async () => {
18
- const databaseMigrateFromRequireContext = new DatabaseMigrateFromRequireContext()
17
+ await Configuration.current().withConnections(async () => {
18
+ const migrator = new Migrator({configuration: Configuration.current()})
19
19
 
20
- await databaseMigrateFromRequireContext.execute(migrationsRequireContext)
20
+ await migrator.prepare()
21
+ await migrator.migrateFilesFromRequireContext(migrationsRequireContext)
21
22
  })
22
23
 
23
24
  await Configuration.current().initialize()
@@ -1,4 +1,5 @@
1
1
  import GetRoute from "./get-route.js"
2
+ import PostRoute from "./post-route.js"
2
3
  import ResourceRoute from "./resource-route.js"
3
4
 
4
5
  var VelociousBaseRoute
@@ -19,6 +20,12 @@ export function initBaseRoute() {
19
20
  throw new Error(`No 'matchWithPath' implemented on ${this.constructor.name}`)
20
21
  }
21
22
 
23
+ post(name, args) {
24
+ const route = new PostRoute({name, args})
25
+
26
+ this.routes.push(route)
27
+ }
28
+
22
29
  resources(name, callback) {
23
30
  const route = new ResourceRoute({name})
24
31
 
@@ -0,0 +1,24 @@
1
+ import BaseRoute, {initBaseRoute} from "./base-route.js"
2
+ import escapeStringRegexp from "escape-string-regexp"
3
+
4
+ initBaseRoute()
5
+
6
+ export default class VelociousRoutePostRoute extends BaseRoute {
7
+ constructor({name}) {
8
+ super()
9
+ this.name = name
10
+ this.regExp = new RegExp(`^(${escapeStringRegexp(name)})(.*)$`)
11
+ }
12
+
13
+ matchWithPath({params, path}) {
14
+ const match = path.match(this.regExp)
15
+
16
+ if (match) {
17
+ const [_beginnigSlash, _matchedName, restPath] = match
18
+
19
+ params.action = this.name
20
+
21
+ return {restPath}
22
+ }
23
+ }
24
+ }
@@ -58,7 +58,7 @@ export default class VelociousRoutesResolver {
58
58
  throw new Error(`Missing action on controller: ${controller}#${action}`)
59
59
  }
60
60
 
61
- await this.configuration.getDatabasePool().withConnection(async () => {
61
+ await this.configuration.withConnections(async () => {
62
62
  await controllerInstance._runBeforeCallbacks()
63
63
  await controllerInstance[action]()
64
64
  })
@@ -4,18 +4,51 @@ import MysqlDriver from "velocious/src/database/drivers/mysql/index.js"
4
4
 
5
5
  export default new Configuration({
6
6
  database: {
7
- default: {
8
- master: {
7
+ development: {
8
+ default: {
9
9
  driver: MysqlDriver,
10
10
  poolType: AsyncTrackedMultiConnection,
11
11
  type: "mysql",
12
12
  host: "mariadb",
13
13
  username: "username",
14
14
  password: "password",
15
- database: "database"
15
+ database: "database_development"
16
+ }
17
+ },
18
+ production: {
19
+ default: {
20
+ driver: MysqlDriver,
21
+ poolType: AsyncTrackedMultiConnection,
22
+ type: "mysql",
23
+ host: "mariadb",
24
+ username: "username",
25
+ password: "password",
26
+ database: "database_production"
27
+ }
28
+ },
29
+ test: {
30
+ default: {
31
+ driver: MysqlDriver,
32
+ poolType: AsyncTrackedMultiConnection,
33
+ type: "mysql",
34
+ host: "mariadb",
35
+ username: "username",
36
+ password: "password",
37
+ database: "database_test"
16
38
  }
17
39
  }
18
40
  },
41
+ initializeModels: async ({configuration}) => {
42
+ const modelsPath = await fs.realpath(`${path.dirname(import.meta.dirname)}/../src/models`)
43
+ const requireContextModels = requireContext(modelsPath, true, /^(.+)\.js$/)
44
+ const initializerFromRequireContext = new InitializerFromRequireContext({requireContext: requireContextModels})
45
+
46
+ await configuration.withConnections(async () => {
47
+ await initializerFromRequireContext.initialize({configuration})
48
+ })
49
+ },
50
+ locale: () => "en",
51
+ locales: ["de", "en"],
19
52
  localeFallbacks: {
20
53
  de: ["de", "en"],
21
54
  en: ["en", "de"]
@@ -71,7 +71,7 @@ export default class TestRunner {
71
71
  }
72
72
  }
73
73
 
74
- await this.configuration.getDatabasePool().withConnection(async () => {
74
+ await this.configuration.withConnections(async () => {
75
75
  for (const subDescription in tests.subs) {
76
76
  const subTest = tests.subs[subDescription]
77
77
  const newDecriptions = descriptions.concat([subDescription])
@@ -0,0 +1,15 @@
1
+ export default async function nestCallbacks(callbacksToNestInside, callback) {
2
+ let runCallback = callback
3
+
4
+ for (const callbackToNestInside of callbacksToNestInside) {
5
+ let actualRunCallback = runCallback
6
+
7
+ const nextRunRequest = async () => {
8
+ await callbackToNestInside(actualRunCallback)
9
+ }
10
+
11
+ runCallback = nextRunRequest
12
+ }
13
+
14
+ await runCallback()
15
+ }
@@ -1,37 +0,0 @@
1
- export default class VelociousBigBrother {
2
- constructor() {
3
- this.enabledLoggers = {
4
- databaseQuery: false
5
- }
6
- }
7
-
8
- checkExists(name) {
9
- if (!(name in this.enabledLoggers)) throw new Error(`Invalid logger name: ${name}`)
10
- }
11
-
12
- isEnabled(name) {
13
- this.checkExists(name)
14
-
15
- return this.enabledLoggers[name]
16
- }
17
-
18
- async run({after, before, name}, callback) {
19
- this.checkExists(name)
20
-
21
- if (!this.enabledLoggers[name]) {
22
- return await callback()
23
- }
24
-
25
- if (before) {
26
- before()
27
- }
28
-
29
- const startTime = new Date()
30
- const result = await callback()
31
- const endTime = new Date()
32
-
33
- if (after) {
34
- after({result})
35
- }
36
- }
37
- }
@@ -1,72 +0,0 @@
1
- import Configuration from "../configuration.js"
2
- import * as inflection from "inflection"
3
- import Migrator from "./migrator.js"
4
-
5
- export default class VelociousDatabaseMigrateFromRequireContext {
6
- constructor(configuration) {
7
- this.configuration = configuration || Configuration.current()
8
- this.migrator = new Migrator({configuration: this.configuration})
9
- }
10
-
11
- async execute(requireContext) {
12
- await this.migrator.prepare()
13
-
14
- const files = requireContext.keys()
15
- .map((file) => {
16
- // "13,14" because somes "require-context"-npm-module deletes first character!?
17
- const match = file.match(/(\d{13,14})-(.+)\.js$/)
18
-
19
- if (!match) return null
20
-
21
- // Fix require-context-npm-module deletes first character
22
- let fileName = file
23
- let dateNumber = match[1]
24
-
25
- if (dateNumber.length == 13) {
26
- dateNumber = `2${dateNumber}`
27
- fileName = `2${fileName}`
28
- }
29
-
30
- // Parse regex
31
- const date = parseInt(dateNumber)
32
- const migrationName = match[2]
33
- const migrationClassName = inflection.camelize(migrationName.replaceAll("-", "_"))
34
-
35
- return {
36
- file: fileName,
37
- date,
38
- migrationClassName
39
- }
40
- })
41
- .filter((migration) => Boolean(migration))
42
- .sort((migration1, migration2) => migration1.date - migration2.date)
43
-
44
- for (const migration of files) {
45
- if (!this.migrator.hasRunMigrationVersion(migration.date)) {
46
- await this.runMigrationFile(migration, requireContext)
47
- }
48
- }
49
- }
50
-
51
- async runMigrationFile(migration, requireContext) {
52
- if (!this.configuration) throw new Error("No configuration set")
53
- if (!this.configuration.isDatabasePoolInitialized()) await this.configuration.initializeDatabasePool()
54
-
55
- await this.configuration.getDatabasePool().withConnection(async (db) => {
56
- const MigrationClass = requireContext(migration.file).default
57
- const migrationInstance = new MigrationClass({
58
- configuration: this.configuration
59
- })
60
-
61
- if (migrationInstance.change) {
62
- await migrationInstance.change()
63
- } else if (migrationInstance.up) {
64
- await migrationInstance.up()
65
- } else {
66
- throw new Error(`'change' or 'up' didn't exist on migration: ${migration.file}`)
67
- }
68
-
69
- await db.insert({tableName: "schema_migrations", data: {version: migration.date}})
70
- })
71
- }
72
- }
package/src/spec/index.js DELETED
@@ -1,5 +0,0 @@
1
- export default class VelocuiousSpec {
2
- static describe(description) {
3
- throw new Error("stub", description)
4
- }
5
- }