@superhero/db 1.0.0 → 1.0.1-rc.0

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.
@@ -38,13 +38,10 @@ class AdapterMySql
38
38
  return new Promise(resolve)
39
39
  }
40
40
 
41
- async createTransaction()
41
+ async createTransaction(connection = await this.getConnection())
42
42
  {
43
- const connection = await this.getConnection()
44
43
  await connection.query('START TRANSACTION')
45
- const transaction = new AdapterMySqlTransaction(connection)
46
-
47
- return transaction
44
+ return new AdapterMySqlTransaction(connection)
48
45
  }
49
46
 
50
47
  close()
@@ -12,14 +12,7 @@ class AdapterMySqlTransaction
12
12
  throw new NestedTransactionsNotAllowed('Nested transactions are not allowed')
13
13
  }
14
14
 
15
- query(...args)
16
- {
17
- return this._query(...args)
18
- }
19
-
20
- // We are forced to have a different query function to be able to use it from inside
21
- // the class without the Proxy making interferences
22
- async _query(query, ...ctx)
15
+ query(query, ...ctx)
23
16
  {
24
17
  return new Promise((accept, reject) =>
25
18
  this.connection.query(query, ...ctx, (error, response) =>
@@ -30,13 +23,13 @@ class AdapterMySqlTransaction
30
23
 
31
24
  async commit()
32
25
  {
33
- await this._query('COMMIT')
26
+ await this.query('COMMIT')
34
27
  this.connection.release()
35
28
  }
36
29
 
37
30
  async rollback()
38
31
  {
39
- await this._query('ROLLBACK')
32
+ await this.query('ROLLBACK')
40
33
  this.connection.release()
41
34
  }
42
35
  }
@@ -38,13 +38,51 @@ class AdapterMySql2
38
38
  return new Promise(resolve)
39
39
  }
40
40
 
41
- async createTransaction()
41
+ async lock(
42
+ reference,
43
+ operation,
44
+ timeout = 10,
45
+ connection = await this.getConnection())
42
46
  {
43
- const connection = await this.getConnection()
44
- await connection.query('START TRANSACTION')
45
- const transaction = new AdapterMySql2Transaction(connection)
47
+ try
48
+ {
49
+ const
50
+ result = await connection.query('SELECT GET_LOCK(?, ?) as status', [reference, timeout]),
51
+ status = result[0].status
52
+
53
+ if(1 === status)
54
+ {
55
+ await operation(connection)
56
+ }
57
+ else if(0 === status)
58
+ {
59
+ const error = new Error(`advisory lock '${reference}' is busy by a different process`)
60
+ error.code = 'DB_LOCK_TIMEOUT'
61
+ error.reference = reference
62
+ error.timeout = timeout
63
+ throw error
64
+ }
65
+ else
66
+ {
67
+ const error = new Error(`something is wrong with the query for the advisory lock '${reference}'`)
68
+ error.code = 'E_EVENTFLOW_DB_ADVISORY_LOCK_NULL_RESULT'
69
+ error.reference = reference
70
+ error.timeout = timeout
71
+ throw error
72
+ }
73
+ }
74
+ finally
75
+ {
76
+ await connection.query('DO RELEASE_LOCK(?)', [reference])
77
+ .then(() => connection.release())
78
+ .catch(() => connection.end())
79
+ }
80
+ }
46
81
 
47
- return transaction
82
+ async createTransaction(connection = await this.getConnection())
83
+ {
84
+ await connection.query('START TRANSACTION')
85
+ return new AdapterMySql2Transaction(connection)
48
86
  }
49
87
 
50
88
  close()
@@ -12,14 +12,7 @@ class AdapterMySql2Transaction
12
12
  throw new NestedTransactionsNotAllowed('Nested transactions are not allowed')
13
13
  }
14
14
 
15
- query(...args)
16
- {
17
- return this._query(...args)
18
- }
19
-
20
- // We are forced to have a different query function to be able to use it from inside
21
- // the class without the Proxy making interferences
22
- async _query(query, ...ctx)
15
+ query(query, ...ctx)
23
16
  {
24
17
  return new Promise((accept, reject) =>
25
18
  this.connection.query(query, ...ctx, (error, response) =>
@@ -30,13 +23,13 @@ class AdapterMySql2Transaction
30
23
 
31
24
  async commit()
32
25
  {
33
- await this._query('COMMIT')
26
+ await this.query('COMMIT')
34
27
  this.connection.release()
35
28
  }
36
29
 
37
30
  async rollback()
38
31
  {
39
- await this._query('ROLLBACK')
32
+ await this.query('ROLLBACK')
40
33
  this.connection.release()
41
34
  }
42
35
  }
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const
2
2
  util = require('util'),
3
3
  fs = require('fs'),
4
+ os = require('os'),
4
5
  promisify = util.promisify,
5
6
  readFile = promisify(fs.readFile)
6
7
 
@@ -9,7 +10,7 @@ class Db
9
10
  constructor(adaptor, queryPath, fileSuffix = '')
10
11
  {
11
12
  this.adaptor = adaptor
12
- this.queryPath = require('path').normalize(queryPath) + '/'
13
+ this.queryPath = require('path').normalize(queryPath) + os.EOL
13
14
  this.fileSuffix = fileSuffix
14
15
  this.queries = {}
15
16
  }
@@ -21,14 +22,7 @@ class Db
21
22
 
22
23
  async query(file, ...ctx)
23
24
  {
24
- let query = await this.getQuery(file)
25
-
26
- for(let noEscapeReplace = query.indexOf('?%s');
27
- noEscapeReplace > -1;
28
- noEscapeReplace = query.indexOf('?%s'))
29
- {
30
- query = query.replace('?%s', ctx.shift())
31
- }
25
+ let query = await this.getQuery(file, ctx)
32
26
 
33
27
  try
34
28
  {
@@ -36,52 +30,73 @@ class Db
36
30
  }
37
31
  catch(reason)
38
32
  {
39
- const error = new Error(`DB query '${file}' failed`)
40
- error.code = 'DB_QUERY_ERROR'
41
- error.query = this.adaptor.getFormattedQuery?.(query, ...ctx) ?? query
42
- error.cause = reason
43
- throw error
33
+ this._throwOnQueryError(reason, file, query, ctx)
44
34
  }
45
35
  }
46
36
 
47
- async formatQuery(file, formatCtx, sqlCtx)
37
+ async lock(...args)
48
38
  {
49
- const
50
- query = await this.getQuery(file),
51
- formattedQuery = util.format(query, ...formatCtx),
52
- response = await this.adaptor.query(formattedQuery, ...sqlCtx)
53
-
54
- return response
39
+ if('lock' in this.adaptor)
40
+ {
41
+ await this.adaptor.lock(...args)
42
+ }
43
+ else
44
+ {
45
+ throw new Error('The database adapter provided does not support locking')
46
+ }
55
47
  }
56
48
 
57
- async createTransaction()
49
+ async createTransaction(connection)
58
50
  {
59
- const transaction = await this.adaptor.createTransaction()
51
+ const transaction = await this.adaptor.createTransaction(connection)
60
52
 
61
53
  return new Proxy(transaction,
62
54
  {
63
- get: (target, property) =>
55
+ get: (target, member) =>
64
56
  {
65
- return property !== 'query'
66
- ? target[property]
57
+ return 'query' !== member
58
+ ? Reflect.get(target, member, target)
67
59
  : async (file, ...ctx) =>
68
60
  {
69
- const
70
- query = await this.getQuery(file),
71
- response = await transaction.query(query, ...ctx)
61
+ const query = await this.getQuery(file, ctx)
72
62
 
73
- return response
63
+ try
64
+ {
65
+ const method = Reflect.get(target, member, target)
66
+ return await Reflect.apply(method, target, [query, ...ctx])
67
+ }
68
+ catch(reason)
69
+ {
70
+ this._throwOnQueryError(reason, file, query, ctx)
71
+ }
74
72
  }
75
73
  }
76
74
  })
77
75
  }
78
76
 
79
- async getQuery(file)
77
+ async getQuery(file, ctx)
80
78
  {
81
- const query = file in this.queries
79
+ const buffer = file in this.queries
82
80
  ? this.queries[file]
83
81
  : this.queries[file] = await readFile(this.queryPath + file + this.fileSuffix)
84
- return query.toString()
82
+
83
+ let query = buffer.toString()
84
+
85
+ while(ctx.length && query.indexOf('?%s') > -1)
86
+ {
87
+ query = query.replace('?%s', ctx.shift())
88
+ }
89
+
90
+ return query
91
+ }
92
+
93
+ _throwOnQueryError(reason, file, query, ctx)
94
+ {
95
+ const error = new Error(`DB query '${file}' failed`)
96
+ error.code = 'DB_QUERY_ERROR'
97
+ error.query = this.adaptor.getFormattedQuery?.(query, ...ctx) ?? query
98
+ error.cause = reason
99
+ throw error
85
100
  }
86
101
  }
87
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhero/db",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-rc.0",
4
4
  "description": "Db interaction, designed to separate queries from the source code",
5
5
  "repository": {
6
6
  "type": "git",