@superhero/db 0.5.2 → 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.
@@ -20,6 +20,11 @@ class AdapterMySql
20
20
  return new Promise(resolve)
21
21
  }
22
22
 
23
+ getFormattedQuery(query, ...ctx)
24
+ {
25
+ return this.pool.format(query, ...ctx)
26
+ }
27
+
23
28
  getConnection()
24
29
  {
25
30
  const resolve = (accept, reject, i = 0) =>
@@ -33,13 +38,10 @@ class AdapterMySql
33
38
  return new Promise(resolve)
34
39
  }
35
40
 
36
- async createTransaction()
41
+ async createTransaction(connection = await this.getConnection())
37
42
  {
38
- const connection = await this.getConnection()
39
43
  await connection.query('START TRANSACTION')
40
- const transaction = new AdapterMySqlTransaction(connection)
41
-
42
- return transaction
44
+ return new AdapterMySqlTransaction(connection)
43
45
  }
44
46
 
45
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
  }
@@ -20,6 +20,11 @@ class AdapterMySql2
20
20
  return new Promise(resolve)
21
21
  }
22
22
 
23
+ getFormattedQuery(query, ...ctx)
24
+ {
25
+ return this.pool.format(query, ...ctx)
26
+ }
27
+
23
28
  getConnection()
24
29
  {
25
30
  const resolve = (accept, reject, i = 0) =>
@@ -33,13 +38,51 @@ class AdapterMySql2
33
38
  return new Promise(resolve)
34
39
  }
35
40
 
36
- async createTransaction()
41
+ async lock(
42
+ reference,
43
+ operation,
44
+ timeout = 10,
45
+ connection = await this.getConnection())
37
46
  {
38
- const connection = await this.getConnection()
39
- await connection.query('START TRANSACTION')
40
- 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
41
52
 
42
- return transaction
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
+ }
81
+
82
+ async createTransaction(connection = await this.getConnection())
83
+ {
84
+ await connection.query('START TRANSACTION')
85
+ return new AdapterMySql2Transaction(connection)
43
86
  }
44
87
 
45
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,56 +22,81 @@ 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'))
25
+ let query = await this.getQuery(file, ctx)
26
+
27
+ try
29
28
  {
30
- query = query.replace('?%s', ctx.shift())
29
+ return await this.adaptor.query(query, ...ctx)
30
+ }
31
+ catch(reason)
32
+ {
33
+ this._throwOnQueryError(reason, file, query, ctx)
31
34
  }
32
-
33
- return await this.adaptor.query(query, ...ctx)
34
35
  }
35
36
 
36
- async formatQuery(file, formatCtx, sqlCtx)
37
+ async lock(...args)
37
38
  {
38
- const
39
- query = await this.getQuery(file),
40
- formattedQuery = util.format(query, ...formatCtx),
41
- response = await this.adaptor.query(formattedQuery, ...sqlCtx)
42
-
43
- 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
+ }
44
47
  }
45
48
 
46
- async createTransaction()
49
+ async createTransaction(connection)
47
50
  {
48
- const transaction = await this.adaptor.createTransaction()
51
+ const transaction = await this.adaptor.createTransaction(connection)
49
52
 
50
53
  return new Proxy(transaction,
51
54
  {
52
- get: (target, property) =>
55
+ get: (target, member) =>
53
56
  {
54
- return property !== 'query'
55
- ? target[property]
57
+ return 'query' !== member
58
+ ? Reflect.get(target, member, target)
56
59
  : async (file, ...ctx) =>
57
60
  {
58
- const
59
- query = await this.getQuery(file),
60
- response = await transaction.query(query, ...ctx)
61
+ const query = await this.getQuery(file, ctx)
61
62
 
62
- 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
+ }
63
72
  }
64
73
  }
65
74
  })
66
75
  }
67
76
 
68
- async getQuery(file)
77
+ async getQuery(file, ctx)
69
78
  {
70
- const query = file in this.queries
79
+ const buffer = file in this.queries
71
80
  ? this.queries[file]
72
81
  : this.queries[file] = await readFile(this.queryPath + file + this.fileSuffix)
73
- 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
74
100
  }
75
101
  }
76
102
 
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@superhero/db",
3
- "version": "0.5.2",
3
+ "version": "1.0.1-rc.0",
4
4
  "description": "Db interaction, designed to separate queries from the source code",
5
- "repository": "git@github.com:superhero/js.db.git",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+ssh://git@github.com/superhero/js.db.git"
8
+ },
6
9
  "main": "index.js",
7
10
  "license": "MIT",
8
11
  "author": {