velocious 1.0.4 → 1.0.6

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 (79) hide show
  1. package/index.mjs +1 -19
  2. package/package.json +6 -3
  3. package/spec/cli/commands/db/create-spec.mjs +1 -1
  4. package/spec/cli/commands/db/migrate-spec.mjs +2 -0
  5. package/spec/database/connection/drivers/mysql/query-parser-spec.mjs +6 -5
  6. package/spec/database/drivers/mysql/connection-spec.mjs +2 -3
  7. package/spec/database/record/create-spec.mjs +9 -0
  8. package/spec/database/record/find-spec.mjs +0 -1
  9. package/spec/database/record/query-spec.mjs +37 -0
  10. package/spec/dummy/index.mjs +14 -2
  11. package/spec/dummy/src/config/configuration.example.mjs +16 -1
  12. package/spec/dummy/src/config/configuration.peakflow.mjs +16 -1
  13. package/spec/dummy/src/database/migrations/20230728075328-create-projects.mjs +0 -1
  14. package/spec/dummy/src/database/migrations/20230728075329-create-tasks.mjs +0 -1
  15. package/spec/dummy/src/database/migrations/20250605133926-create-project-translations.mjs +16 -0
  16. package/spec/dummy/src/models/project.mjs +9 -0
  17. package/spec/dummy/src/models/task.mjs +6 -2
  18. package/src/big-brother.mjs +37 -0
  19. package/src/cli/commands/db/create.mjs +8 -6
  20. package/src/cli/commands/db/migrate.mjs +1 -2
  21. package/src/cli/commands/generate/migration.mjs +1 -1
  22. package/src/cli/commands/generate/model.mjs +1 -1
  23. package/src/configuration.mjs +39 -3
  24. package/src/database/drivers/base.mjs +60 -0
  25. package/src/database/drivers/mysql/column.mjs +8 -0
  26. package/src/database/drivers/mysql/index.mjs +34 -2
  27. package/src/database/drivers/mysql/options.mjs +1 -0
  28. package/src/database/drivers/mysql/query-parser.mjs +2 -23
  29. package/src/database/drivers/mysql/table.mjs +25 -0
  30. package/src/database/drivers/sqlite/base.mjs +108 -0
  31. package/src/database/drivers/sqlite/column.mjs +10 -0
  32. package/src/database/drivers/sqlite/index.native.mjs +22 -63
  33. package/src/database/drivers/sqlite/index.web.mjs +28 -37
  34. package/src/database/drivers/sqlite/options.mjs +2 -1
  35. package/src/database/drivers/sqlite/query-parser.mjs +2 -23
  36. package/src/database/drivers/sqlite/query.native.mjs +16 -1
  37. package/src/database/drivers/sqlite/query.web.mjs +27 -2
  38. package/src/database/drivers/sqlite/sql/create-index.mjs +4 -0
  39. package/src/database/drivers/sqlite/table.mjs +24 -0
  40. package/src/database/initializer-from-require-context.mjs +21 -0
  41. package/src/database/migrate-from-require-context.mjs +11 -2
  42. package/src/database/migration/index.mjs +34 -2
  43. package/src/database/migrator.mjs +75 -0
  44. package/src/database/pool/async-tracked-multi-connection.mjs +1 -1
  45. package/src/database/pool/base.mjs +19 -1
  46. package/src/database/pool/single-multi-use.mjs +1 -1
  47. package/src/database/query/base.mjs +2 -1
  48. package/src/database/query/create-index-base.mjs +50 -0
  49. package/src/database/query/create-table-base.mjs +40 -17
  50. package/src/database/query/index.mjs +83 -21
  51. package/src/database/query/insert-base.mjs +4 -0
  52. package/src/database/query/preloader/belongs-to.mjs +52 -0
  53. package/src/database/query/preloader/has-many.mjs +55 -0
  54. package/src/database/query/preloader.mjs +41 -0
  55. package/src/database/query/where-base.mjs +9 -0
  56. package/src/database/query/where-hash.mjs +35 -0
  57. package/src/database/query/where-plain.mjs +13 -0
  58. package/src/database/query-parser/base-query-parser.mjs +33 -0
  59. package/src/database/query-parser/group-parser.mjs +40 -0
  60. package/src/database/query-parser/joins-parser.mjs +48 -7
  61. package/src/database/query-parser/limit-parser.mjs +40 -0
  62. package/src/database/query-parser/options.mjs +4 -3
  63. package/src/database/query-parser/order-parser.mjs +39 -0
  64. package/src/database/query-parser/select-parser.mjs +5 -1
  65. package/src/database/query-parser/where-parser.mjs +39 -0
  66. package/src/database/record/index.mjs +464 -29
  67. package/src/database/record/instance-relationships/base.mjs +28 -0
  68. package/src/database/record/instance-relationships/belongs-to.mjs +20 -0
  69. package/src/database/record/instance-relationships/has-many.mjs +47 -0
  70. package/src/database/record/relationships/base.mjs +32 -0
  71. package/src/database/record/relationships/belongs-to.mjs +12 -0
  72. package/src/database/record/relationships/has-many.mjs +12 -0
  73. package/src/database/table-data/index.mjs +15 -25
  74. package/src/http-server/worker-handler/worker-thread.mjs +7 -4
  75. package/src/templates/generate-model.mjs +3 -1
  76. package/src/utils/rest-args-error.mjs +9 -0
  77. package/src/database/drivers/index.mjs +0 -5
  78. package/src/database/index.mjs +0 -15
  79. package/src/database/migrator/index.mjs +0 -15
@@ -3,6 +3,7 @@ import QueryParserOptions from "../../query-parser/options.mjs"
3
3
  export default class VelociousDatabaseDriversMysqlOptions extends QueryParserOptions {
4
4
  constructor(options) {
5
5
  options.columnQuote = "`"
6
+ options.indexQuote = "`"
6
7
  options.stringQuote = "'"
7
8
  options.tableQuote = "`"
8
9
 
@@ -1,25 +1,4 @@
1
- import {digs} from "diggerize"
2
- import FromParser from "../../query-parser/from-parser.mjs"
3
- import JoinsParser from "../../query-parser/joins-parser.mjs"
4
- import SelectParser from "../../query-parser/select-parser.mjs"
1
+ import BaseQueryParser from "../../query-parser/base-query-parser.mjs"
5
2
 
6
- export default class VelociousDatabaseConnectionDriversMysqlQueryParser {
7
- constructor({pretty, query}) {
8
- if (!query) throw new Error("No query given")
9
-
10
- this.pretty = pretty
11
- this.query = query
12
- }
13
-
14
- toSql() {
15
- const {pretty, query} = digs(this, "pretty", "query")
16
-
17
- let sql = ""
18
-
19
- sql += new SelectParser({pretty, query}).toSql()
20
- sql += new FromParser({pretty, query}).toSql()
21
- sql += new JoinsParser({pretty, query}).toSql()
22
-
23
- return sql
24
- }
3
+ export default class VelociousDatabaseConnectionDriversMysqlQueryParser extends BaseQueryParser {
25
4
  }
@@ -0,0 +1,25 @@
1
+ import Column from "./column.mjs"
2
+
3
+ export default class VelociousDatabaseDriversMysqlTable {
4
+ constructor(driver, data) {
5
+ this.data = data
6
+ this.driver = driver
7
+ }
8
+
9
+ async getColumns() {
10
+ const result = await this.driver.query(`SHOW FULL COLUMNS FROM \`${this.getName()}\``)
11
+ const columns = []
12
+
13
+ for (const data of result) {
14
+ const column = new Column(this, data)
15
+
16
+ columns.push(column)
17
+ }
18
+
19
+ return columns
20
+ }
21
+
22
+ getName() {
23
+ return Object.values(this.data)[0]
24
+ }
25
+ }
@@ -0,0 +1,108 @@
1
+ import {digg} from "diggerize"
2
+
3
+ import Base from "../base.mjs"
4
+ import CreateIndex from "../sqlite/sql/create-index.mjs"
5
+ import CreateTable from "../sqlite/sql/create-table.mjs"
6
+ import Delete from "../sqlite/sql/delete.mjs"
7
+ import escapeString from "sql-string-escape"
8
+ import Insert from "../sqlite/sql/insert.mjs"
9
+ import Options from "../sqlite/options.mjs"
10
+ import QueryParser from "../sqlite/query-parser.mjs"
11
+ import Table from "./table"
12
+ import Update from "../sqlite/sql/update.mjs"
13
+
14
+ export default class VelociousDatabaseDriversSqliteBase extends Base {
15
+ createIndexSql(indexData) {
16
+ const createArgs = Object.assign({driver: this}, indexData)
17
+ const createIndex = new CreateIndex(createArgs)
18
+
19
+ return createIndex.toSql()
20
+ }
21
+
22
+ createTableSql(tableData) {
23
+ const createArgs = Object.assign({tableData, driver: this, indexInCreateTable: false})
24
+ const createTable = new CreateTable(createArgs)
25
+
26
+ return createTable.toSql()
27
+ }
28
+
29
+ deleteSql = ({tableName, conditions}) => new Delete({conditions, driver: this, tableName}).toSql()
30
+ insertSql = ({tableName, data}) => new Insert({driver: this, tableName, data}).toSql()
31
+
32
+ async getTableByName(tableName) {
33
+ const result = await this.query(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${this.quote(tableName)} LIMIT 1`)
34
+ const row = result[0]
35
+
36
+ if (!row) {
37
+ const tables = await this.getTables()
38
+ const tableNames = tables.map((table) => table.getName())
39
+
40
+ throw new Error(`No table by that name: ${tableName} in ${tableNames.join(", ")}`)
41
+ }
42
+
43
+ return new Table({driver: this, row})
44
+ }
45
+
46
+ async getTables() {
47
+ const result = await this.query("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
48
+ const tables = []
49
+
50
+ for (const row of result) {
51
+ const table = new Table({driver: this, row})
52
+
53
+ tables.push(table)
54
+ }
55
+
56
+ return tables
57
+ }
58
+
59
+ async lastInsertID() {
60
+ const result = await this.query("SELECT LAST_INSERT_ROWID() AS last_insert_id")
61
+
62
+ return digg(result, 0, "last_insert_id")
63
+ }
64
+
65
+ options() {
66
+ if (!this._options) {
67
+ this._options = new Options({driver: this})
68
+ }
69
+
70
+ return this._options
71
+ }
72
+
73
+ queryToSql = (query) => new QueryParser({query}).toSql()
74
+
75
+ escape(value) {
76
+ const type = typeof value
77
+
78
+ if (type != "string") value = `${value}`
79
+
80
+ const resultWithQuotes = escapeString(value)
81
+ const result = resultWithQuotes.substring(1, resultWithQuotes.length - 1)
82
+
83
+ return result
84
+ }
85
+
86
+ quote(value) {
87
+ const type = typeof value
88
+
89
+ if (type == "number") return value
90
+ if (type != "string") value = `${value}`
91
+
92
+ return escapeString(value)
93
+ }
94
+
95
+ quoteColumn = (string) => {
96
+ if (string.includes("`")) throw new Error(`Possible SQL injection in column name: ${string}`)
97
+
98
+ return `\`${string}\``
99
+ }
100
+
101
+ quoteTable = (string) => {
102
+ if (string.includes("`")) throw new Error(`Possible SQL injection in table name: ${string}`)
103
+
104
+ return `\`${string}\``
105
+ }
106
+
107
+ updateSql = ({conditions, data, tableName}) => new Update({conditions, data, driver: this, tableName}).toSql()
108
+ }
@@ -0,0 +1,10 @@
1
+ import {digg} from "diggerize"
2
+
3
+ export default class VelociousDatabaseDriversSqliteColumn {
4
+ constructor({column, driver}) {
5
+ this.column = column
6
+ this.driver = driver
7
+ }
8
+
9
+ getName = () => digg(this, "column", "name")
10
+ }
@@ -1,23 +1,31 @@
1
- import Base from "../base.mjs"
2
- import CreateTable from "../sqlite/sql/create-table.mjs"
3
- import Delete from "../sqlite/sql/delete.mjs"
4
1
  import {digg} from "diggerize"
5
- import Insert from "../sqlite/sql/insert.mjs"
6
- import Options from "../sqlite/options.mjs"
7
- import query from "./query.mjs"
8
- import QueryParser from "../sqlite/query-parser.mjs"
2
+ import query from "./query"
9
3
  import * as SQLite from "expo-sqlite"
10
- import Update from "../sqlite/sql/update.mjs"
11
4
 
12
- export default class VelociousDatabaseDriversSqliteNative extends Base{
5
+ import Base from "./base"
6
+
7
+ export default class VelociousDatabaseDriversSqliteNative extends Base {
13
8
  async connect() {
14
- const connection = await SQLite.openDatabaseAsync(digg(this.connectArgs(), "name"))
9
+ const args = this.getArgs()
10
+ const databaseName = digg(args, "name")
11
+
12
+ if (args.reset) {
13
+ try {
14
+ await SQLite.deleteDatabaseAsync(databaseName)
15
+ } catch (error) {
16
+ if (error.message.match(/Database '(.+)' not found/)) {
17
+ // Ignore not found
18
+ } else {
19
+ throw error
20
+ }
21
+ }
22
+ }
15
23
 
16
- this.connection = connection
24
+ this.connection = await SQLite.openDatabaseAsync(databaseName)
17
25
  }
18
26
 
19
- disconnect() {
20
- this.connection.end()
27
+ async disconnect() {
28
+ await this.connection.closeAsync()
21
29
  }
22
30
 
23
31
  connectArgs() {
@@ -39,54 +47,5 @@ export default class VelociousDatabaseDriversSqliteNative extends Base{
39
47
  this.connection = undefined
40
48
  }
41
49
 
42
- createTableSql(tableData) {
43
- const createArgs = Object.assign({tableData, driver: this})
44
- const createTable = new CreateTable(createArgs)
45
-
46
- return createTable.toSql()
47
- }
48
-
49
- async query(sql) {
50
- return await query(this.connection, sql)
51
- }
52
-
53
- queryToSql(query) {
54
- return new QueryParser({query}).toSql()
55
- }
56
-
57
- quote(string) {
58
- if (!this.connection) throw new Error("Can't escape before connected")
59
-
60
- return this.connection.escape(string)
61
- }
62
-
63
- quoteColumn(string) {
64
- return `\`${string}\``
65
- }
66
-
67
- deleteSql({tableName, conditions}) {
68
- const deleteInstruction = new Delete({conditions, driver: this, tableName})
69
-
70
- return deleteInstruction.toSql()
71
- }
72
-
73
- insertSql({tableName, data}) {
74
- const insert = new Insert({driver: this, tableName, data})
75
-
76
- return insert.toSql()
77
- }
78
-
79
- options() {
80
- if (!this._options) {
81
- this._options = new Options({driver: this})
82
- }
83
-
84
- return this._options
85
- }
86
-
87
- updateSql({conditions, data, tableName}) {
88
- const update = new Update({conditions, data, driver: this, tableName})
89
-
90
- return update.toSql()
91
- }
50
+ query = async (sql) => await query(this.connection, sql)
92
51
  }
@@ -1,64 +1,55 @@
1
- import Base from "../base.mjs"
2
- import CreateTable from "../sqlite/sql/create-table.mjs"
3
- import Delete from "../sqlite/sql/delete.mjs"
1
+ import BetterLocalStorage from "better-localstorage"
2
+ import debounce from "debounce"
4
3
  import {digg} from "diggerize"
5
- import Insert from "../sqlite/sql/insert.mjs"
6
- import Options from "../sqlite/options.mjs"
4
+ import initSqlJs from "sql.js"
7
5
  import query from "./query"
8
- import QueryParser from "../sqlite/query-parser.mjs"
9
- import Update from "../sqlite/sql/update.mjs"
10
6
 
11
- import initSqlJs from "sql.js"
7
+ import Base from "./base.mjs"
12
8
 
13
- export default class VelociousDatabaseDriversSqliteWeb extends Base{
9
+ export default class VelociousDatabaseDriversSqliteWeb extends Base {
14
10
  async connect() {
11
+ this.betterLocaleStorage ||= new BetterLocalStorage()
12
+
13
+ const args = this.getArgs()
14
+
15
+ if (args.reset) {
16
+ await this.betterLocaleStorage.delete(this.localStorageName())
17
+ }
18
+
15
19
  const SQL = await initSqlJs({
16
20
  // Required to load the wasm binary asynchronously. Of course, you can host it wherever you want you can omit locateFile completely when running in Node.
17
21
  locateFile: (file) => `https://sql.js.org/dist/${file}`
18
22
  })
19
23
 
20
- const databaseContent = localStorage.getItem(this.localStorageName())
24
+ const databaseContent = await this.betterLocaleStorage.get(this.localStorageName())
21
25
 
22
26
  this.connection = new SQL.Database(databaseContent)
23
27
  }
24
28
 
25
29
  localStorageName = () => `VelociousDatabaseDriversSqliteWeb---${digg(this.getArgs(), "name")}`
26
30
  disconnect = () => this.saveDatabase()
27
- saveDatabase = () => localStorage.setItem(this.localStorageName(), this.connection.export())
31
+ saveDatabase = async () => {
32
+ const localStorageContent = this.connection.export()
33
+ await this.betterLocaleStorage.set(this.localStorageName(), localStorageContent)
34
+ }
35
+
36
+ saveDatabaseDebounce = debounce(this.saveDatabase, 500)
28
37
 
29
38
  async close() {
30
- this.saveDatabase()
39
+ await this.saveDatabase()
31
40
  await this.connection.end()
32
41
  this.connection = undefined
33
42
  }
34
43
 
35
- createTableSql(tableData) {
36
- const createArgs = Object.assign({tableData, driver: this})
37
- const createTable = new CreateTable(createArgs)
44
+ query = async (sql) => {
45
+ const result = await query(this.connection, sql)
46
+ const downcasedSQL = sql.toLowerCase().trim()
38
47
 
39
- return createTable.toSql()
40
- }
41
-
42
- query = async (sql) => await query(this.connection, sql)
43
- queryToSql = (query) => new QueryParser({query}).toSql()
44
-
45
- quote(string) {
46
- if (!this.connection) throw new Error("Can't escape before connected")
47
-
48
- return this.connection.escape(string)
49
- }
50
-
51
- quoteColumn = (string) => `\`${string}\``
52
- deleteSql = ({tableName, conditions}) => new Delete({conditions, driver: this, tableName}).toSql()
53
- insertSql = ({tableName, data}) => new Insert({driver: this, tableName, data}).toSql()
54
-
55
- options() {
56
- if (!this._options) {
57
- this._options = new Options({driver: this})
48
+ // Auto-save database in local storage in case we can find manipulating instructions in the SQL
49
+ if (downcasedSQL.startsWith("delete ") || downcasedSQL.startsWith("insert into ") || downcasedSQL.startsWith("update ")) {
50
+ this.saveDatabaseDebounce()
58
51
  }
59
52
 
60
- return this._options
53
+ return result
61
54
  }
62
-
63
- updateSql = ({conditions, data, tableName}) => new Update({conditions, data, driver: this, tableName}).toSql()
64
55
  }
@@ -1,8 +1,9 @@
1
1
  import QueryParserOptions from "../../query-parser/options.mjs"
2
2
 
3
- export default class VelociousDatabaseDriversMysqlOptions extends QueryParserOptions {
3
+ export default class VelociousDatabaseDriversSqliteOptions extends QueryParserOptions {
4
4
  constructor(options) {
5
5
  options.columnQuote = "`"
6
+ options.indexQuote = "`"
6
7
  options.stringQuote = "'"
7
8
  options.tableQuote = "`"
8
9
 
@@ -1,25 +1,4 @@
1
- import {digs} from "diggerize"
2
- import FromParser from "../../query-parser/from-parser.mjs"
3
- import JoinsParser from "../../query-parser/joins-parser.mjs"
4
- import SelectParser from "../../query-parser/select-parser.mjs"
1
+ import BaseQueryParser from "../../query-parser/base-query-parser.mjs"
5
2
 
6
- export default class VelociousDatabaseConnectionDriversMysqlQueryParser {
7
- constructor({pretty, query}) {
8
- if (!query) throw new Error("No query given")
9
-
10
- this.pretty = pretty
11
- this.query = query
12
- }
13
-
14
- toSql() {
15
- const {pretty, query} = digs(this, "pretty", "query")
16
-
17
- let sql = ""
18
-
19
- sql += new SelectParser({pretty, query}).toSql()
20
- sql += new FromParser({pretty, query}).toSql()
21
- sql += new JoinsParser({pretty, query}).toSql()
22
-
23
- return sql
24
- }
3
+ export default class VelociousDatabaseConnectionDriversSqliteQueryParser extends BaseQueryParser {
25
4
  }
@@ -1,7 +1,22 @@
1
1
  export default async function query(connection, sql) {
2
2
  const rows = []
3
+ let result
3
4
 
4
- for await (const entry of connection.getEachAsync(sql)) {
5
+ try {
6
+ result = await connection.getAllAsync(sql)
7
+ } catch (error) {
8
+ let sqlInErrorMessage = `${sql}`
9
+
10
+ if (sqlInErrorMessage.length >= 4096) {
11
+ sqlInErrorMessage = `${sqlInErrorMessage.substring(0, 4096)}...`
12
+ }
13
+
14
+ error.message += `\n\n${sqlInErrorMessage}`
15
+
16
+ throw error
17
+ }
18
+
19
+ for await (const entry of result) {
5
20
  rows.push(entry)
6
21
  }
7
22
 
@@ -1,8 +1,33 @@
1
1
  export default async function query(connection, sql) {
2
2
  const rows = []
3
+ let result
3
4
 
4
- for await (const entry of connection.exec(sql)) {
5
- rows.push(entry)
5
+ try {
6
+ result = connection.exec(sql)
7
+ } catch (error) {
8
+ let sqlInErrorMessage = `${sql}`
9
+
10
+ if (sqlInErrorMessage.length >= 4096) {
11
+ sqlInErrorMessage = `${sqlInErrorMessage.substring(0, 4096)}...`
12
+ }
13
+
14
+ error.message += `\n\n${sqlInErrorMessage}`
15
+
16
+ throw error
17
+ }
18
+
19
+ if (result[0]) {
20
+ const columns = result[0].columns
21
+
22
+ for (const rowValues of result[0].values) {
23
+ const row = {}
24
+
25
+ for (const columnIndex in columns) {
26
+ row[columns[columnIndex]] = rowValues[columnIndex]
27
+ }
28
+
29
+ rows.push(row)
30
+ }
6
31
  }
7
32
 
8
33
  return rows
@@ -0,0 +1,4 @@
1
+ import CreateIndexBase from "../../../query/create-index-base.mjs"
2
+
3
+ export default class VelociousDatabaseConnectionDriversMysqlSqlCreateIndex extends CreateIndexBase {
4
+ }
@@ -0,0 +1,24 @@
1
+ import Column from "./column.mjs"
2
+ import {digg} from "diggerize"
3
+
4
+ export default class VelociousDatabaseDriversSqliteTable {
5
+ constructor({driver, row}) {
6
+ this.driver = driver
7
+ this.row = row
8
+ }
9
+
10
+ getColumns = async () => {
11
+ const result = await this.driver.query(`PRAGMA table_info('${this.getName()}')`)
12
+ const columns = []
13
+
14
+ for (const columnData of result) {
15
+ const column = new Column({column: columnData, driver: this.driver, table: this})
16
+
17
+ columns.push(column)
18
+ }
19
+
20
+ return columns
21
+ }
22
+
23
+ getName = () => digg(this, "row", "name")
24
+ }
@@ -0,0 +1,21 @@
1
+ export default class VelociousDatabaseInitializerFromRequireContext {
2
+ constructor({requireContext, ...restProps}) {
3
+ const restPropsKeys = Object.keys(restProps)
4
+
5
+ if (restPropsKeys.length > 0) throw new Error(`Unknown arguments: ${restPropsKeys.join(", ")}`)
6
+
7
+ this.requireContext = requireContext
8
+ }
9
+
10
+ async initialize({configuration}) {
11
+ for (const fileName of this.requireContext.keys()) {
12
+ const modelClass = this.requireContext(fileName).default
13
+
14
+ await modelClass.initializeRecord({configuration})
15
+
16
+ if (await modelClass.hasTranslationsTable()) {
17
+ await modelClass.getTranslationClass().initializeRecord({configuration})
18
+ }
19
+ }
20
+ }
21
+ }
@@ -1,5 +1,6 @@
1
1
  import Configuration from "../configuration.mjs"
2
2
  import * as inflection from "inflection"
3
+ import Migrator from "./migrator"
3
4
 
4
5
  export default class VelociousDatabaseMigrateFromRequireContext {
5
6
  constructor(configuration) {
@@ -7,6 +8,10 @@ export default class VelociousDatabaseMigrateFromRequireContext {
7
8
  }
8
9
 
9
10
  async execute(requireContext) {
11
+ const migrator = new Migrator({configuration: this.configuration})
12
+
13
+ await migrator.prepare()
14
+
10
15
  const files = requireContext.keys()
11
16
  .map((file) => {
12
17
  const match = file.match(/^\.\/(\d{14})-(.+)\.mjs$/)
@@ -27,7 +32,9 @@ export default class VelociousDatabaseMigrateFromRequireContext {
27
32
  .sort((migration1, migration2) => migration1.date - migration2.date)
28
33
 
29
34
  for (const migration of files) {
30
- await this.runMigrationFile(migration, requireContext)
35
+ if (!migrator.hasRunMigrationVersion(migration.date)) {
36
+ await this.runMigrationFile(migration, requireContext)
37
+ }
31
38
  }
32
39
  }
33
40
 
@@ -35,7 +42,7 @@ export default class VelociousDatabaseMigrateFromRequireContext {
35
42
  if (!this.configuration) throw new Error("No configuration set")
36
43
  if (!this.configuration.isDatabasePoolInitialized()) await this.configuration.initializeDatabasePool()
37
44
 
38
- await this.configuration.getDatabasePool().withConnection(async () => {
45
+ await this.configuration.getDatabasePool().withConnection(async (db) => {
39
46
  const MigrationClass = requireContext(migration.file).default
40
47
  const migrationInstance = new MigrationClass({
41
48
  configuration: this.configuration
@@ -48,6 +55,8 @@ export default class VelociousDatabaseMigrateFromRequireContext {
48
55
  } else {
49
56
  throw new Error(`'change' or 'up' didn't exist on migration: ${migration.file}`)
50
57
  }
58
+
59
+ await db.insert({tableName: "schema_migrations", data: {version: migration.date}})
51
60
  })
52
61
  }
53
62
  }
@@ -5,14 +5,46 @@ export default class VelociousDatabaseMigration {
5
5
  this.configuration = configuration
6
6
  }
7
7
 
8
+ async addIndex(tableName, columns, args) {
9
+ const databasePool = this.configuration.getDatabasePool()
10
+ const createIndexArgs = Object.assign(
11
+ {
12
+ columns,
13
+ tableName
14
+ },
15
+ args
16
+ )
17
+ const sql = databasePool.createIndexSql(createIndexArgs)
18
+
19
+ await databasePool.query(sql)
20
+ }
21
+
8
22
  async createTable(tableName, callback) {
9
23
  const tableData = new TableData(tableName)
10
24
 
25
+ tableData.integer("id", {null: false, primaryKey: true})
26
+
11
27
  callback(tableData)
12
28
 
13
29
  const databasePool = this.configuration.getDatabasePool()
14
- const sql = databasePool.createTableSql(tableData)
30
+ const sqls = databasePool.createTableSql(tableData)
15
31
 
16
- await databasePool.query(sql)
32
+ for (const sql of sqls) {
33
+ await databasePool.query(sql)
34
+ }
35
+ }
36
+
37
+ getConnection() {
38
+ const connection = this.configuration.getDatabasePool().getCurrentConnection()
39
+
40
+ if (!connection) throw new Error("Couldn't get current connection")
41
+
42
+ return connection
43
+ }
44
+
45
+ async tableExists(tableName) {
46
+ const exists = await this.getConnection().tableExists(tableName)
47
+
48
+ return exists
17
49
  }
18
50
  }