@axiosleo/orm-mysql 0.3.1 → 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/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.1",
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,51 +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._buildJoins(options.joins);
55
- sql += this._buildContidion(options.conditions);
56
- if (options.groupField.length) {
57
- sql += ` GROUP BY ${options.groupField.map(f => this._buildFieldKey(f)).join(',')}`;
58
- sql += this._buildHaving(options.having);
59
- } 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) {
60
73
  throw new Error('having is not allowed without "GROUP BY"');
61
74
  }
75
+ emit(tmp, this._buildGroupField(options.groupField));
76
+ emit(tmp, this._buildHaving(options.having));
77
+ sql = tmp.join(' ');
62
78
  break;
63
79
  }
64
80
  default:
@@ -68,16 +84,31 @@ class Builder {
68
84
  this.sql = sql;
69
85
  }
70
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
+
71
94
  _buildHaving(having) {
72
- if (!having.length) {
95
+ if (!having || !having.length) {
73
96
  return '';
74
97
  }
75
- return this._buildContidion(having, ' HAVING ');
98
+ return this._buildContidion(having, 'HAVING ');
76
99
  }
77
100
 
78
101
  _buildJoins(joins = []) {
79
102
  return joins.map((j) => {
80
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
+ }
81
112
  if (alias) {
82
113
  table = `\`${table}\` AS \`${alias}\``;
83
114
  } else {
@@ -86,13 +117,13 @@ class Builder {
86
117
  let sql = '';
87
118
  switch (join_type) {
88
119
  case 'left':
89
- sql = ' LEFT JOIN ';
120
+ sql = 'LEFT JOIN ';
90
121
  break;
91
122
  case 'right':
92
- sql = ' RIGHT JOIN ';
123
+ sql = 'RIGHT JOIN ';
93
124
  break;
94
125
  default:
95
- sql = ' INNER JOIN ';
126
+ sql = 'INNER JOIN ';
96
127
  break;
97
128
  }
98
129
  sql += `${table} ON ${this._buildFieldWithTableName(self_column)} = ${this._buildFieldWithTableName(foreign_column)}`;
@@ -101,14 +132,17 @@ class Builder {
101
132
  }
102
133
 
103
134
  _buildOrders(orders = []) {
104
- const sql = ' ORDER BY ' + orders.map((o) => {
135
+ if (!orders || !orders.length) {
136
+ return '';
137
+ }
138
+ const sql = 'ORDER BY ' + orders.map((o) => {
105
139
  return `${this._buildFieldKey(o.sortField)} ${o.sortOrder}`;
106
140
  }).join(',');
107
141
  return sql;
108
142
  }
109
143
 
110
144
  _buildTables(tables) {
111
- if (!tables.length) {
145
+ if (!tables || !tables.length) {
112
146
  throw new Error('At least one table is required');
113
147
  }
114
148
  return tables.map((t) => {
@@ -156,10 +190,10 @@ class Builder {
156
190
  }
157
191
 
158
192
  _buildContidion(conditions, prefix) {
159
- if (!conditions.length) {
193
+ if (!conditions || !conditions.length) {
160
194
  return '';
161
195
  }
162
- let sql = typeof prefix === 'undefined' ? ' WHERE ' : prefix;
196
+ let sql = typeof prefix === 'undefined' ? 'WHERE ' : prefix;
163
197
  if (conditions.length) {
164
198
  sql += `${conditions.map((c) => {
165
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 });