@axiosleo/orm-mysql 0.4.0 → 0.5.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.
package/.env CHANGED
@@ -3,4 +3,4 @@ MYSQL_HOST = "rm-bp1a906h4019pldm6io.mysql.rds.aliyuncs.com"
3
3
  MYSQL_USER = "kms"
4
4
  MYSQL_PASS = "bmPWuq_vJgupdL9"
5
5
  MYSQL_PORT = 3306
6
- MYSQL_DB = "business_5b5ecd9941d6cc1bbc065fc6"
6
+ MYSQL_DB = "kms-test"
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # @axiosleo/orm-mysql
2
2
 
3
+
3
4
  [![NPM version](https://img.shields.io/npm/v/@axiosleo/orm-mysql.svg?style=flat-square)](https://npmjs.org/package/@axiosleo/orm-mysql)
4
5
  [![npm download](https://img.shields.io/npm/dm/@axiosleo/orm-mysql.svg?style=flat-square)](https://npmjs.org/package/@axiosleo/orm-mysql)
5
6
  [![License](https://img.shields.io/github/license/AxiosLeo/node-orm-mysql?color=%234bc524)](LICENSE)
6
- [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql/refs/branch/master)
7
+ [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql?ref=badge_shield)
7
8
 
8
9
  ## Installation
9
10
 
@@ -150,6 +151,43 @@ Hook.post(async (options, result) => {
150
151
  }, { table: 'table_name', opt: 'insert' });
151
152
  ```
152
153
 
154
+ ### Transaction
155
+
156
+ ```javascript
157
+ const { TransactionHandler, createPromiseClient } = require("@axiosleo/orm-mysql");
158
+
159
+ const conn = createPromiseClient({
160
+ host: process.env.MYSQL_HOST,
161
+ port: process.env.MYSQL_PORT,
162
+ user: process.env.MYSQL_USER,
163
+ password: process.env.MYSQL_PASS,
164
+ database: process.env.MYSQL_DB,
165
+ });
166
+
167
+ const transaction = new TransactionHandler(connection);
168
+
169
+ try {
170
+ // insert user info
171
+ let row = await transaction.table("users").insert({
172
+ name: "Joe",
173
+ age: 18,
174
+ });
175
+ const lastInsertId = row[0].insertId;
176
+
177
+ // insert student info
178
+ await transaction.table("students").insert({
179
+ user_id: lastInsertId,
180
+ });
181
+ await transaction.commit();
182
+ } catch (e) {
183
+ await transaction.rollback();
184
+ throw e;
185
+ }
186
+ ```
187
+
153
188
  ## License
154
189
 
155
190
  This project is open-sourced software licensed under the [MIT](LICENSE).
191
+
192
+
193
+ [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FAxiosLeo%2Fnode-orm-mysql?ref=badge_large)
package/index.d.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import {
2
+ Pool,
2
3
  OkPacket,
3
4
  Connection,
5
+ PoolOptions,
4
6
  QueryOptions,
5
7
  RowDataPacket,
6
8
  ConnectionOptions
7
9
  } from 'mysql2';
8
10
 
11
+ import {
12
+ Connection as PromiseConnection,
13
+ } from 'mysql2/promise';
14
+
9
15
  export type Clients = {
10
16
  [key: string]: Connection
11
17
  }
@@ -54,6 +60,8 @@ export interface QueryOperatorOptions {
54
60
  groupField: string[];
55
61
  joins: JoinOption[];
56
62
  having: WhereOptions[];
63
+ suffix?: string | null;
64
+ transaction: boolean;
57
65
  }
58
66
 
59
67
  export declare class Query {
@@ -112,7 +120,12 @@ export declare class QueryOperator extends Query {
112
120
 
113
121
  count(): Promise<number>;
114
122
 
115
- delete(id?: number): Promise<OkPacket>;
123
+ /**
124
+ * delete data
125
+ * @param id
126
+ * @param index_field_name default is 'id'
127
+ */
128
+ delete(id?: number, index_field_name?: string): Promise<OkPacket>;
116
129
  }
117
130
 
118
131
  export declare class QueryHandler {
@@ -127,10 +140,43 @@ export declare class QueryHandler {
127
140
  upsert(tableName: string, data: any, condition: Record<string, ConditionValueType>): Promise<OkPacket>;
128
141
  }
129
142
 
143
+ export declare class TransactionOperator extends QueryOperator {
144
+ append(suffix: string): this;
145
+ }
146
+
147
+ export declare class TransactionHandler {
148
+ constructor(conn: PromiseConnection, options?: {
149
+ level: 'READ UNCOMMITTED' | 'RU'
150
+ | 'READ COMMITTED' | 'RC'
151
+ | 'REPEATABLE READ' | 'RR'
152
+ | 'SERIALIZABLE' | 'S'
153
+ });
154
+
155
+ query(options: QueryOptions): Promise<any>;
156
+
157
+ execute(sql: string, values: any[]): Promise<any>;
158
+
159
+ lastInsertId(alias?: string): Promise<number>;
160
+
161
+ table(table: string, alias?: string | null): TransactionOperator;
162
+
163
+ begin(): Promise<void>;
164
+
165
+ commit(): Promise<void>;
166
+
167
+ rollback(): Promise<void>;
168
+
169
+ upsert(tableName: string, data: any, condition: Record<string, ConditionValueType>): Promise<OkPacket>;
170
+ }
171
+
130
172
  export function createClient(options: ConnectionOptions, name?: string | null | undefined): Connection;
131
173
 
132
174
  export function getClient(name: string): Connection;
133
175
 
176
+ export function createPool(options: PoolOptions, name?: string | null | undefined): Pool;
177
+
178
+ export function createPromiseClient(options: ConnectionOptions, name?: string | null | undefined): PromiseConnection;
179
+
134
180
  export declare class Hook {
135
181
  static pre: (
136
182
  callback: (options: QueryOperatorOptions) => void,
@@ -141,4 +187,4 @@ export declare class Hook {
141
187
  callback: (options: QueryOperatorOptions, result: QueryResult | Error) => void,
142
188
  option: { table?: string, opt?: OperatorType }
143
189
  ) => string;
144
- }
190
+ }
package/index.js CHANGED
@@ -6,7 +6,17 @@ const {
6
6
  Query
7
7
  } = require('./src/operator');
8
8
 
9
- const { createClient, getClient } = require('./src/client');
9
+ const {
10
+ TransactionOperator,
11
+ TransactionHandler
12
+ } = require('./src/transaction');
13
+
14
+ const {
15
+ getClient,
16
+ createPool,
17
+ createClient,
18
+ createPromiseClient
19
+ } = require('./src/client');
10
20
 
11
21
  const { Hook } = require('./src/hook');
12
22
 
@@ -17,6 +27,11 @@ module.exports = {
17
27
  QueryHandler,
18
28
  QueryOperator,
19
29
 
30
+ TransactionOperator,
31
+ TransactionHandler,
32
+
20
33
  getClient,
21
- createClient
34
+ createPool,
35
+ createClient,
36
+ createPromiseClient
22
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiosleo/orm-mysql",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "MySQL ORM tool",
5
5
  "keywords": [
6
6
  "mysql",
package/runtimes/test.js CHANGED
@@ -1,49 +1,73 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  /* eslint-disable no-console */
2
3
  'use strict';
3
4
 
5
+ const path = require('path');
4
6
  const dotenv = require('dotenv');
5
- dotenv.config();
7
+ dotenv.config({
8
+ path: path.join(__dirname, '../.env')
9
+ });
6
10
  const { debug } = require('@axiosleo/cli-tool');
7
11
 
8
- const mysql = require('mysql2');
9
- const {
10
- QueryHandler,
11
- // Query
12
- } = require('../src/operator');
13
- const { Hook } = require('../index');
14
-
15
- const conn = mysql.createConnection({
16
- host: process.env.MYSQL_HOST,
17
- port: process.env.MYSQL_PORT,
18
- user: process.env.MYSQL_USER,
19
- password: process.env.MYSQL_PASS,
20
- database: process.env.MYSQL_DB,
21
- });
22
- const hanlder = new QueryHandler(conn);
12
+ const mysql = require('mysql2/promise');
23
13
 
24
- Hook.pre(async (options) => {
25
- debug.log('options', options);
26
- }, {
27
- table: 'test',
28
- opt: 'insert'
29
- });
30
- Hook.postInsert(async (options, result) => {
31
- throw new Error('some error');
32
- }, { table: 'test', opt: 'insert' });
14
+ const { TransactionHandler } = require('../src/transaction');
33
15
 
34
- const test = async () => {
35
- console.time('query');
36
- const query = hanlder.table('test');
37
- const result = await query.insert({
38
- name: 'test',
39
- item_code: '178eb48aa207a59ef8e3cb6944b5ed35'
40
- });
41
- await hanlder.table('test')
42
- .where('name', 'test')
43
- .where('item_code', '178eb48aa207a59ef8e3cb6944b5ed35')
44
- .delete();
45
- console.timeEnd('query');
46
- debug.halt(result);
47
- };
16
+ async function main() {
17
+ const items = ['RI0002', 'CB0004'];
18
+ const config = {
19
+ user: process.env.MYSQL_USER,
20
+ password: process.env.MYSQL_PASS,
21
+ host: process.env.MYSQL_HOST,
22
+ port: process.env.MYSQL_PORT,
23
+ database: process.env.MYSQL_DB,
24
+ };
25
+ const connection = await mysql.createConnection(config);
26
+ const transaction = new TransactionHandler(connection);
27
+ await transaction.begin();
28
+ console.log('Finished setting the isolation level to read committed');
29
+ try {
30
+ await transaction.table('product').attr('id', 'name').where('sku', items, 'IN').append('FOR UPDATE').select();
31
+ console.log(`Locked rows for skus ${items.join()}`);
32
+ const [itemsToOrder] = await transaction.table('product').attr('name', 'quantity', 'price').where('sku', items, 'IN').orderBy('id').select();
33
+ console.log('Selected quantities for items');
34
+ let orderTotal = 0;
35
+ let orderItems = [];
36
+ for (let itemToOrder of itemsToOrder) {
37
+ if (itemToOrder.quantity < 1) {
38
+ throw new Error(`One of the items is out of stock ${itemToOrder.name}`);
39
+ }
40
+ console.log(`Quantity for ${itemToOrder.name} is ${itemToOrder.quantity}`);
41
+ orderTotal += itemToOrder.price;
42
+ orderItems.push(itemToOrder.name);
43
+ }
44
+ const res = await transaction.table('sales_order').insert({
45
+ items: orderItems.join(),
46
+ total: orderTotal,
47
+ });
48
+ // const lastInsertId = res[0].insertId;
49
+ debug.log('result', res);
50
+ await debug.pause('pause', {
51
+ items: orderItems.join(),
52
+ total: orderTotal,
53
+ });
54
+ console.log('Order created');
55
+ await transaction.execute(
56
+ 'UPDATE product SET quantity=quantity - 1 WHERE sku IN (?, ?)',
57
+ items
58
+ );
59
+ console.log(`Deducted quantities by 1 for ${items.join()}`);
60
+ await transaction.commit();
61
+ const lastInsertId = await transaction.lastInsertId('order_id');
62
+ debug.log(`order created with id ${lastInsertId}`);
63
+ return `order created with id ${lastInsertId}`;
64
+ } catch (err) {
65
+ console.error(`Error occurred while creating order: ${err.message}`, err);
66
+ transaction.rollback();
67
+ console.info('Rollback successful');
68
+ debug.log('error creating order');
69
+ return 'error creating order';
70
+ }
71
+ }
48
72
 
49
- test();
73
+ main();
package/src/builder.js CHANGED
@@ -36,6 +36,9 @@ class Builder {
36
36
  emit(tmp, this._buildGroupField(options.groupField));
37
37
  emit(tmp, this._buildHaving(options.having));
38
38
  sql = tmp.join(' ');
39
+ if (options.suffix) {
40
+ sql += ' ' + options.suffix;
41
+ }
39
42
  break;
40
43
  }
41
44
  case 'insert': {
package/src/client.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const mysql = require('mysql2');
4
+ const mysqlPromise = require('mysql2/promise');
4
5
  const { validate } = require('./utils');
5
6
 
6
7
  const clients = {};
@@ -28,6 +29,51 @@ const createClient = (options, name = null) => {
28
29
  return clients[key];
29
30
  };
30
31
 
32
+ /**
33
+ * @param {mysql.ConnectionOptions} options
34
+ * @param {string|null} name
35
+ * @returns {mysqlPromise.Connection}
36
+ */
37
+ const createPromiseClient = async (options, name = null) => {
38
+ validate(options, {
39
+ host: 'required|string',
40
+ user: 'required|string',
41
+ password: 'required|string',
42
+ port: 'required|integer',
43
+ database: 'required|string',
44
+ });
45
+ const key = name ? name :
46
+ `${options.host}:${options.port}:${options.user}:${options.password}:${options.database}`;
47
+ if (clients[key]) {
48
+ return clients[key];
49
+ }
50
+ clients[key] = await mysqlPromise.createConnection(options);
51
+ return clients[key];
52
+ };
53
+
54
+ /**
55
+ * create pool
56
+ * @param {mysql.PoolOptions} options
57
+ * @returns {mysql.Pool}
58
+ */
59
+ const createPool = (options, name = null) => {
60
+ validate(options, {
61
+ host: 'required|string',
62
+ user: 'required|string',
63
+ password: 'required|string',
64
+ port: 'required|integer',
65
+ database: 'required|string',
66
+ });
67
+ const key = name ? name :
68
+ `${options.host}:${options.port}:${options.user}:${options.password}:${options.database}`;
69
+ if (clients[key]) {
70
+ return clients[key];
71
+ }
72
+ const pool = mysql.createPool(options);
73
+ clients[key] = pool;
74
+ return pool;
75
+ };
76
+
31
77
  /**
32
78
  * get client
33
79
  * @param {*} name
@@ -45,5 +91,7 @@ const getClient = (name) => {
45
91
 
46
92
  module.exports = {
47
93
  getClient,
48
- createClient
94
+ createPool,
95
+ createClient,
96
+ createPromiseClient
49
97
  };
package/src/operator.js CHANGED
@@ -4,15 +4,21 @@ const { Builder } = require('./builder');
4
4
  const Query = require('./query');
5
5
  const { handleEvent } = require('./hook');
6
6
 
7
- const query = async (conn, options) => {
7
+ const query = async (conn, options, transaction) => {
8
8
  return new Promise((resolve, reject) => {
9
- conn.query(options, (err, result) => {
10
- if (err) {
11
- reject(err);
12
- } else {
13
- resolve(result);
14
- }
15
- });
9
+ if (transaction) {
10
+ conn.execute(options)
11
+ .then((res) => resolve(res))
12
+ .catch((err) => reject(err));
13
+ } else {
14
+ conn.query(options, (err, result) => {
15
+ if (err) {
16
+ reject(err);
17
+ } else {
18
+ resolve(result);
19
+ }
20
+ });
21
+ }
16
22
  });
17
23
  };
18
24
 
@@ -45,17 +51,17 @@ class QueryOperator extends Query {
45
51
  try {
46
52
  switch (this.options.operator) {
47
53
  case 'find': {
48
- const tmp = await query(this.conn, options);
54
+ const tmp = await query(this.conn, options, this.options.transaction);
49
55
  res = tmp[0];
50
56
  break;
51
57
  }
52
58
  case 'count': {
53
- const [tmp] = await query(this.conn, options);
59
+ const [tmp] = await query(this.conn, options, this.options.transaction);
54
60
  res = tmp.count;
55
61
  break;
56
62
  }
57
63
  default:
58
- res = await query(this.conn, options);
64
+ res = await query(this.conn, options, this.options.transaction);
59
65
  }
60
66
  handleEvent('post', from, this.options.operator, this.options, res);
61
67
  } catch (err) {
@@ -96,9 +102,9 @@ class QueryOperator extends Query {
96
102
  return await this.exec();
97
103
  }
98
104
 
99
- async delete(id) {
105
+ async delete(id, index_field_name = 'id') {
100
106
  if (id) {
101
- this.where('id', id);
107
+ this.where(index_field_name, id);
102
108
  }
103
109
  this.options.operator = 'delete';
104
110
  return await this.exec();
package/src/query.js CHANGED
@@ -10,7 +10,9 @@ class Query {
10
10
  data: null,
11
11
  groupField: [],
12
12
  having: [],
13
- joins: []
13
+ joins: [],
14
+ suffix: null,
15
+ transaction: false
14
16
  };
15
17
  }
16
18
 
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ // eslint-disable-next-line no-unused-vars
4
+ const mysql = require('mysql2/promise');
5
+ const { QueryOperator } = require('./operator');
6
+
7
+ const levels = {
8
+ RU: 'READ UNCOMMITTED',
9
+ RC: 'READ COMMITTED',
10
+ RR: 'REPEATABLE READ',
11
+ S: 'SERIALIZABLE'
12
+ };
13
+
14
+ class TransactionOperator extends QueryOperator {
15
+ /**
16
+ * @param {*} conn
17
+ */
18
+ constructor(conn) {
19
+ super(conn);
20
+ this.options.transaction = true;
21
+ }
22
+
23
+ /**
24
+ * @example LOCK IN SHARE MODE
25
+ * @example FOR UPDATE
26
+ */
27
+ append(suffix) {
28
+ this.options.suffix = suffix || null;
29
+ return this;
30
+ }
31
+ }
32
+
33
+ class TransactionHandler {
34
+ /**
35
+ * @param {mysql.Connection} conn
36
+ * @param {mysql.ConnectionOptions} options
37
+ */
38
+ constructor(conn, options = {}) {
39
+ this.isbegin = false;
40
+ this.conn = conn;
41
+ this.level = options.level || 'SERIALIZABLE';
42
+ if (levels[this.level]) {
43
+ this.level = levels[this.level];
44
+ }
45
+ if (!Object.values(levels).includes(this.level)) {
46
+ throw new Error('Invalid transaction level: ' + this.level);
47
+ }
48
+ }
49
+
50
+ async query(options) {
51
+ return new Promise((resolve, reject) => {
52
+ this.conn.query(options, (err, result) => {
53
+ if (err) {
54
+ reject(err);
55
+ } else {
56
+ resolve(result);
57
+ }
58
+ });
59
+ });
60
+ }
61
+
62
+ async execute(sql, values = []) {
63
+ return this.conn.execute(sql, values);
64
+ }
65
+
66
+ async lastInsertId(alias = 'insert_id') {
67
+ let sql = `SELECT LAST_INSERT_ID() as ${alias}`;
68
+ const [row] = await this.execute(sql);
69
+ return row && row[0] ? row[0][alias] : 0;
70
+ }
71
+
72
+ async begin() {
73
+ this.isbegin = true;
74
+ await this.execute(`SET TRANSACTION ISOLATION LEVEL ${this.level}`);
75
+ await this.conn.beginTransaction();
76
+ }
77
+
78
+ table(table, alias = null) {
79
+ if (!this.isbegin) {
80
+ throw new Error('Transaction is not begin');
81
+ }
82
+ return (new TransactionOperator(this.conn)).table(table, alias);
83
+ }
84
+
85
+ async upsert(tableName, data, condition = {}) {
86
+ const count = await this.table(tableName).whereObject(condition).count();
87
+ if (count) {
88
+ return await this.table(tableName).whereObject(condition).update(data);
89
+ }
90
+ return await this.table(tableName).insert(data);
91
+ }
92
+
93
+ async commit() {
94
+ if (!this.isbegin) {
95
+ throw new Error('Transaction is not begin');
96
+ }
97
+ await this.conn.commit();
98
+ }
99
+
100
+ async rollback() {
101
+ if (!this.isbegin) {
102
+ throw new Error('Transaction is not begin');
103
+ }
104
+ await this.conn.rollback();
105
+ }
106
+ }
107
+
108
+ module.exports = {
109
+ TransactionOperator,
110
+ TransactionHandler
111
+ };