@axiosleo/orm-mysql 0.3.0 → 0.4.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/.eslintrc CHANGED
@@ -12,7 +12,7 @@
12
12
  "single"
13
13
  ],
14
14
  "linebreak-style": [
15
- 2,
15
+ 0,
16
16
  "unix"
17
17
  ],
18
18
  "semi": [
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @axiosleo/orm-mysql
2
2
 
3
+ [![NPM version](https://img.shields.io/npm/v/@axiosleo/orm-mysql.svg?style=flat-square)](https://npmjs.org/package/@axiosleo/orm-mysql)
4
+ [![npm download](https://img.shields.io/npm/dm/@axiosleo/orm-mysql.svg?style=flat-square)](https://npmjs.org/package/@axiosleo/orm-mysql)
5
+ [![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
+
3
8
  ## Installation
4
9
 
5
10
  ```bash
@@ -46,7 +51,7 @@ query.offset(0); // set offset
46
51
  let rows = await query.select(); // select
47
52
  ```
48
53
 
49
- ### Some Examples
54
+ ### Some Query Examples
50
55
 
51
56
  ```javascript
52
57
  const { createClient, QueryHandler, Query } = require("@axiosleo/orm-mysql");
@@ -129,6 +134,22 @@ async function subqueryExample() {
129
134
  }
130
135
  ```
131
136
 
137
+ ### Hook
138
+
139
+ ```javascript
140
+ const { Hook } = require("@axiosleo/orm-mysql");
141
+
142
+ // opt: 'select' | 'find' | 'insert' | 'update' | 'delete' | 'count'
143
+
144
+ Hook.pre(async (options) => {
145
+ debug.log('options', options);
146
+ }, { table: 'table_name', opt: 'insert'});
147
+
148
+ Hook.post(async (options, result) => {
149
+ throw new Error('some error');
150
+ }, { table: 'table_name', opt: 'insert' });
151
+ ```
152
+
132
153
  ## License
133
154
 
134
155
  This project is open-sourced software licensed under the [MIT](LICENSE).
package/index.d.ts CHANGED
@@ -30,7 +30,7 @@ export interface OrderByOptions {
30
30
  export type OperatorType = 'select' | 'find' | 'insert' | 'update' | 'delete' | 'count';
31
31
 
32
32
  export interface JoinOption {
33
- table: string;
33
+ table: string | Query;
34
34
  table_alias?: string;
35
35
  self_column: string;
36
36
  foreign_column: string;
@@ -90,6 +90,8 @@ export declare class Query {
90
90
  join(opt: JoinOption): this;
91
91
  }
92
92
 
93
+ export type QueryResult = any | undefined | RowDataPacket[] | RowDataPacket | OkPacket;
94
+
93
95
  export declare class QueryOperator extends Query {
94
96
  conn: Connection;
95
97
  options: QueryOperatorOptions
@@ -98,7 +100,7 @@ export declare class QueryOperator extends Query {
98
100
 
99
101
  buildSql(operator: OperatorType): { sql: string, values: any[] };
100
102
 
101
- exec(): Promise<any | undefined | RowDataPacket[] | RowDataPacket | OkPacket>;
103
+ exec(): Promise<QueryResult>;
102
104
 
103
105
  select<T>(): Promise<T[]>;
104
106
 
@@ -128,3 +130,15 @@ export declare class QueryHandler {
128
130
  export function createClient(options: ConnectionOptions, name?: string | null | undefined): Connection;
129
131
 
130
132
  export function getClient(name: string): Connection;
133
+
134
+ export declare class Hook {
135
+ static pre: (
136
+ callback: (options: QueryOperatorOptions) => void,
137
+ option: { table?: string, opt?: OperatorType }
138
+ ) => string;
139
+
140
+ static post: (
141
+ callback: (options: QueryOperatorOptions, result: QueryResult | Error) => void,
142
+ option: { table?: string, opt?: OperatorType }
143
+ ) => string;
144
+ }
package/index.js CHANGED
@@ -8,7 +8,11 @@ const {
8
8
 
9
9
  const { createClient, getClient } = require('./src/client');
10
10
 
11
+ const { Hook } = require('./src/hook');
12
+
11
13
  module.exports = {
14
+ Hook,
15
+
12
16
  Query,
13
17
  QueryHandler,
14
18
  QueryOperator,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiosleo/orm-mysql",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "MySQL ORM tool",
5
5
  "keywords": [
6
6
  "mysql",
@@ -0,0 +1,104 @@
1
+ /* eslint-disable no-console */
2
+ 'use strict';
3
+
4
+ const EventEmitter = require('events');
5
+ const { debug } = require('@axiosleo/cli-tool');
6
+ const events = {}; // event tree
7
+ const hook = new EventEmitter();
8
+
9
+ const pushEvent = ({ label, table, opt, callback }) => {
10
+ label = label || '*';
11
+ if (!events[label]) {
12
+ events[label] = {};
13
+ }
14
+ table = table || '*';
15
+ if (!events[label][table]) {
16
+ events[label][table] = {};
17
+ }
18
+ opt = opt || '*';
19
+ if (!events[label][table][opt]) {
20
+ events[label][table][opt] = 0;
21
+ }
22
+ events[label][table][opt]++;
23
+ hook.on(`${label}::${table}::${opt}`, callback);
24
+ return { label, table, opt, callback };
25
+ };
26
+
27
+ const eventRecur = (curr, trace, step, paths, args) => {
28
+ if (step === trace.length) {
29
+ hook.emit(paths.join('::'), ...args);
30
+ return;
31
+ }
32
+ const t = trace[step];
33
+ if (curr['*']) {
34
+ paths[step] = '*';
35
+ eventRecur(curr[t], trace, step + 1, paths, args);
36
+ }
37
+ if (curr[t]) {
38
+ paths[step] = t;
39
+ eventRecur(curr[t], trace, step + 1, paths, args);
40
+ }
41
+ return;
42
+ };
43
+
44
+ const handleEvent = (label, table, opt, ...args) => {
45
+ let curr = events;
46
+ let step = 0;
47
+ let trace = [label, table, opt];
48
+ eventRecur(curr, trace, step, [], args);
49
+ };
50
+
51
+ pushEvent({
52
+ label: 'before',
53
+ table: 'table1',
54
+ opt: 'insert',
55
+ callback: (...args) => {
56
+ debug.log(args);
57
+ }
58
+ });
59
+ pushEvent({
60
+ table: 'table1',
61
+ opt: 'insert',
62
+ callback: (...args) => {
63
+ debug.log(args);
64
+ }
65
+ });
66
+ pushEvent({
67
+ label: 'before',
68
+ table: 'table1',
69
+ callback: (...args) => {
70
+ debug.log(args);
71
+ }
72
+ });
73
+ pushEvent({
74
+ label: 'before',
75
+ opt: 'insert',
76
+ callback: (...args) => {
77
+ debug.log(args);
78
+ }
79
+ });
80
+ pushEvent({
81
+ label: 'before',
82
+ callback: (...args) => {
83
+ debug.log(args);
84
+ }
85
+ });
86
+ pushEvent({
87
+ table: 'table1',
88
+ callback: (...args) => {
89
+ debug.log(args);
90
+ }
91
+ });
92
+ pushEvent({
93
+ opt: 'insert',
94
+ callback: (...args) => {
95
+ debug.log(args);
96
+ }
97
+ });
98
+ pushEvent({
99
+ callback: (...args) => {
100
+ debug.log(args);
101
+ }
102
+ });
103
+ debug.log(JSON.stringify(events, null, 2));
104
+ handleEvent('before', 'table1', 'insert', 1, 2, 3);
package/runtimes/test.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-console */
1
2
  'use strict';
2
3
 
3
4
  const dotenv = require('dotenv');
@@ -9,6 +10,7 @@ const {
9
10
  QueryHandler,
10
11
  // Query
11
12
  } = require('../src/operator');
13
+ const { Hook } = require('../index');
12
14
 
13
15
  const conn = mysql.createConnection({
14
16
  host: process.env.MYSQL_HOST,
@@ -19,27 +21,29 @@ const conn = mysql.createConnection({
19
21
  });
20
22
  const hanlder = new QueryHandler(conn);
21
23
 
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' });
33
+
22
34
  const test = async () => {
23
- const query = hanlder.table('meta_items_relationship', 'mir')
24
- .attr('mi_p.meta as p_meta', 'mi_c.meta as c_meta', 'mir.item_parent as item_parent_id', 'mir.item_child as item_child_id');
25
- const res = query
26
- .where('mi_p.id', 601)
27
- .join({
28
- table: 'meta_items',
29
- table_alias: 'mi_p',
30
- self_column: 'mir.item_parent',
31
- foreign_column: 'mi_p.id',
32
- // join_type: 'left'
33
- })
34
- .join({
35
- table: 'meta_items',
36
- table_alias: 'mi_c',
37
- self_column: 'mir.item_child',
38
- foreign_column: 'mi_c.id',
39
- }).buildSql('select');
40
- const result = await query.select();
41
- debug.halt(res, result);
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);
42
47
  };
43
48
 
44
49
  test();
45
-
package/src/builder.js CHANGED
@@ -3,10 +3,21 @@
3
3
  const Query = require('./query');
4
4
  const is = require('@axiosleo/cli-tool/src/helper/is');
5
5
 
6
+ /**
7
+ * @param {array} arr
8
+ * @param {string} res
9
+ */
10
+ const emit = (arr, res) => {
11
+ if (res) {
12
+ arr.push(res);
13
+ }
14
+ };
15
+
6
16
  class Builder {
7
17
  constructor(options) {
8
18
  let sql = '';
9
19
  this.values = [];
20
+ let tmp = [];
10
21
  switch (options.operator) {
11
22
  case 'find': {
12
23
  options.pageLimit = 1;
@@ -14,50 +25,56 @@ class Builder {
14
25
  }
15
26
  // eslint-disable-next-line no-fallthrough
16
27
  case 'select': {
17
- sql = `SELECT ${options.attrs ? options.attrs.map((a) => this._buildFieldKey(a)).join(',') : '*'} FROM ${this._buildTables(options.tables)}`;
18
- sql += this._buildJoins(options.joins);
19
- sql += this._buildContidion(options.conditions);
20
- sql += options.orders.length > 0 ? this._buildOrders(options.orders) : '';
21
- sql += this._buldPagenation(options.pageLimit, options.pageOffset);
22
- if (options.groupField.length) {
23
- sql += ` GROUP BY ${options.groupField.map(f => this._buildFieldKey(f)).join(',')}`;
24
- sql += this._buildHaving(options.having);
25
- } else if (options.having && options.having.length) {
28
+ emit(tmp, `SELECT ${options.attrs ? options.attrs.map((a) => this._buildFieldKey(a)).join(',') : '*'} FROM ${this._buildTables(options.tables)}`);
29
+ emit(tmp, this._buildJoins(options.joins));
30
+ emit(tmp, this._buildContidion(options.conditions));
31
+ emit(tmp, this._buildOrders(options.orders));
32
+ emit(tmp, this._buldPagenation(options.pageLimit, options.pageOffset));
33
+ if (options.having && options.having.length && !options.groupField.length) {
26
34
  throw new Error('having is not allowed without "GROUP BY"');
27
35
  }
36
+ emit(tmp, this._buildGroupField(options.groupField));
37
+ emit(tmp, this._buildHaving(options.having));
38
+ sql = tmp.join(' ');
28
39
  break;
29
40
  }
30
41
  case 'insert': {
31
42
  const fields = this._buildValues(options.data);
32
- sql = `INSERT INTO ${this._buildTables(options.tables)}(${fields.map((f) => this._buildFieldKey(f))}) VALUES (${fields.map(() => '?').join(',')})`;
43
+ emit(tmp, `INSERT INTO ${this._buildTables(options.tables)}(${fields.map((f) => `\`${f}\``).join(',')})`);
44
+ emit(tmp, `VALUES (${fields.map((f) => '?').join(',')})`);
45
+ sql = tmp.join(' ');
33
46
  break;
34
47
  }
35
48
  case 'update': {
36
49
  const fields = this._buildValues(options.data);
37
- sql = `UPDATE ${this._buildTables(options.tables)} SET ${fields.map(f => `${this._buildFieldKey(f)} = ?`).join(',')}`;
50
+ emit(tmp, `UPDATE ${this._buildTables(options.tables)}`);
51
+ emit(tmp, `SET ${fields.map((f) => `\`${f}\` = ?`).join(',')}`);
38
52
  if (!options.conditions.length) {
39
53
  throw new Error('At least one condition is required for update operation');
40
54
  }
41
- sql += this._buildContidion(options.conditions);
55
+ emit(tmp, this._buildContidion(options.conditions));
56
+ sql = tmp.join(' ');
42
57
  break;
43
58
  }
44
59
  case 'delete': {
45
- sql = `DELETE FROM ${this._buildTables(options.tables)}`;
60
+ emit(tmp, `DELETE FROM ${this._buildTables(options.tables)}`);
46
61
  if (!options.conditions.length) {
47
62
  throw new Error('At least one where condition is required for delete operation');
48
63
  }
49
- sql += this._buildContidion(options.conditions);
64
+ emit(tmp, this._buildContidion(options.conditions));
65
+ sql = tmp.join(' ');
50
66
  break;
51
67
  }
52
68
  case 'count': {
53
- sql = `SELECT COUNT(*) AS count FROM ${this._buildTables(options.tables)}`;
54
- sql += this._buildContidion(options.conditions);
55
- if (options.groupField.length) {
56
- sql += ` GROUP BY ${options.groupField.map(f => this._buildFieldKey(f)).join(',')}`;
57
- sql += this._buildHaving(options.having);
58
- } else if (options.having && options.having.length) {
69
+ emit(tmp, `SELECT COUNT(*) AS count FROM ${this._buildTables(options.tables)}`);
70
+ emit(tmp, this._buildJoins(options.joins));
71
+ emit(tmp, this._buildContidion(options.conditions));
72
+ if (options.having && options.having.length && !options.groupField.length) {
59
73
  throw new Error('having is not allowed without "GROUP BY"');
60
74
  }
75
+ emit(tmp, this._buildGroupField(options.groupField));
76
+ emit(tmp, this._buildHaving(options.having));
77
+ sql = tmp.join(' ');
61
78
  break;
62
79
  }
63
80
  default:
@@ -67,16 +84,31 @@ class Builder {
67
84
  this.sql = sql;
68
85
  }
69
86
 
87
+ _buildGroupField(groupFields = []) {
88
+ if (!groupFields || !groupFields.length) {
89
+ return '';
90
+ }
91
+ return `GROUP BY ${groupFields.map(f => this._buildFieldKey(f)).join(',')}`;
92
+ }
93
+
70
94
  _buildHaving(having) {
71
- if (!having.length) {
95
+ if (!having || !having.length) {
72
96
  return '';
73
97
  }
74
- return this._buildContidion(having, ' HAVING ');
98
+ return this._buildContidion(having, 'HAVING ');
75
99
  }
76
100
 
77
101
  _buildJoins(joins = []) {
78
102
  return joins.map((j) => {
79
103
  let { table, alias, self_column, foreign_column, join_type } = j;
104
+ if (table instanceof Query) {
105
+ if (!alias) {
106
+ throw new Error('Alias is required for subquery');
107
+ }
108
+ const builder = new Builder(table.options);
109
+ this.values = this.values.concat(builder.values);
110
+ table = `(${builder.sql})`;
111
+ }
80
112
  if (alias) {
81
113
  table = `\`${table}\` AS \`${alias}\``;
82
114
  } else {
@@ -85,13 +117,13 @@ class Builder {
85
117
  let sql = '';
86
118
  switch (join_type) {
87
119
  case 'left':
88
- sql = ' LEFT JOIN ';
120
+ sql = 'LEFT JOIN ';
89
121
  break;
90
122
  case 'right':
91
- sql = ' RIGHT JOIN ';
123
+ sql = 'RIGHT JOIN ';
92
124
  break;
93
125
  default:
94
- sql = ' INNER JOIN ';
126
+ sql = 'INNER JOIN ';
95
127
  break;
96
128
  }
97
129
  sql += `${table} ON ${this._buildFieldWithTableName(self_column)} = ${this._buildFieldWithTableName(foreign_column)}`;
@@ -100,14 +132,17 @@ class Builder {
100
132
  }
101
133
 
102
134
  _buildOrders(orders = []) {
103
- const sql = ' ORDER BY ' + orders.map((o) => {
104
- return `\`${o.sortField}\` ${o.sortOrder}`;
135
+ if (!orders || !orders.length) {
136
+ return '';
137
+ }
138
+ const sql = 'ORDER BY ' + orders.map((o) => {
139
+ return `${this._buildFieldKey(o.sortField)} ${o.sortOrder}`;
105
140
  }).join(',');
106
141
  return sql;
107
142
  }
108
143
 
109
144
  _buildTables(tables) {
110
- if (!tables.length) {
145
+ if (!tables || !tables.length) {
111
146
  throw new Error('At least one table is required');
112
147
  }
113
148
  return tables.map((t) => {
@@ -155,10 +190,10 @@ class Builder {
155
190
  }
156
191
 
157
192
  _buildContidion(conditions, prefix) {
158
- if (!conditions.length) {
193
+ if (!conditions || !conditions.length) {
159
194
  return '';
160
195
  }
161
- let sql = typeof prefix === 'undefined' ? ' WHERE ' : prefix;
196
+ let sql = typeof prefix === 'undefined' ? 'WHERE ' : prefix;
162
197
  if (conditions.length) {
163
198
  sql += `${conditions.map((c) => {
164
199
  if (c.key === null && c.value === null) {
package/src/hook.js ADDED
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('events');
4
+
5
+ const events = {}; // event tree
6
+ const hook = new EventEmitter();
7
+
8
+ const pushEvent = ({ label, table, opt, callback }) => {
9
+ label = label || '*';
10
+ if (!events[label]) {
11
+ events[label] = {};
12
+ }
13
+ table = table || '*';
14
+ if (!events[label][table]) {
15
+ events[label][table] = {};
16
+ }
17
+ opt = opt || '*';
18
+ if (!events[label][table][opt]) {
19
+ events[label][table][opt] = 0;
20
+ }
21
+ events[label][table][opt]++;
22
+ hook.on(`${label}::${table}::${opt}`, callback);
23
+ return { label, table, opt, callback };
24
+ };
25
+
26
+ const eventRecur = (curr, trace, step, paths, args) => {
27
+ if (step === trace.length) {
28
+ hook.emit(paths.join('::'), ...args);
29
+ return;
30
+ }
31
+ const t = trace[step];
32
+ if (curr['*']) {
33
+ paths[step] = '*';
34
+ eventRecur(curr[t], trace, step + 1, paths, args);
35
+ }
36
+ if (curr[t]) {
37
+ paths[step] = t;
38
+ eventRecur(curr[t], trace, step + 1, paths, args);
39
+ }
40
+ return;
41
+ };
42
+
43
+ const handleEvent = (label, table, opt, ...args) => {
44
+ let curr = events;
45
+ let step = 0;
46
+ let trace = [label, table, opt];
47
+ eventRecur(curr, trace, step, [], args);
48
+ };
49
+
50
+ class Hook {
51
+ static pre(callback, { table, opt }) {
52
+ return pushEvent({
53
+ label: 'pre', table, opt, callback
54
+ });
55
+ }
56
+
57
+ static post(callback, { table, opt }) {
58
+ return pushEvent({
59
+ label: 'post', table, opt, callback
60
+ });
61
+ }
62
+ }
63
+
64
+ module.exports = {
65
+ Hook,
66
+ handleEvent
67
+ };
package/src/operator.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { Builder } = require('./builder');
4
4
  const Query = require('./query');
5
+ const { handleEvent } = require('./hook');
5
6
 
6
7
  const query = async (conn, options) => {
7
8
  return new Promise((resolve, reject) => {
@@ -38,18 +39,30 @@ class QueryOperator extends Query {
38
39
  const options = {
39
40
  sql, values
40
41
  };
41
- switch (this.options.operator) {
42
- case 'find': {
43
- const res = await query(this.conn, options);
44
- return res[0];
45
- }
46
- case 'count': {
47
- const [res] = await query(this.conn, options);
48
- return res.count;
42
+ const from = this.options.tables.map(t => t.tableName).join(',');
43
+ handleEvent('pre', from, this.options.operator, this.options);
44
+ let res;
45
+ try {
46
+ switch (this.options.operator) {
47
+ case 'find': {
48
+ const tmp = await query(this.conn, options);
49
+ res = tmp[0];
50
+ break;
51
+ }
52
+ case 'count': {
53
+ const [tmp] = await query(this.conn, options);
54
+ res = tmp.count;
55
+ break;
56
+ }
57
+ default:
58
+ res = await query(this.conn, options);
49
59
  }
50
- default:
51
- return query(this.conn, options);
60
+ handleEvent('post', from, this.options.operator, this.options, res);
61
+ } catch (err) {
62
+ handleEvent('post', from, this.options.operator, this.options, err);
63
+ throw err;
52
64
  }
65
+ return res;
53
66
  }
54
67
 
55
68
  async select() {
package/src/query.js CHANGED
@@ -119,6 +119,7 @@ class Query {
119
119
  let lastOpt = this.options.having[this.options.having.length - 1].opt.toUpperCase();
120
120
  if (lastOpt !== 'AND' && lastOpt !== 'OR') {
121
121
  this.options.having.push({ key: null, opt: 'AND', value: null });
122
+ return this;
122
123
  }
123
124
  }
124
125
  this.options.having.push({ key, opt, value });