@hackthedev/dsync-sql 1.0.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.
Files changed (4) hide show
  1. package/README.md +82 -0
  2. package/bun.lock +72 -0
  3. package/index.mjs +192 -0
  4. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # dSyncSql
2
+
3
+ dSyncSql is supposed to be a helper for handling MySQL / MariaDB connections and executing queries. In addition it will also allow you to automatically create a database's structure like tables, column and such and will automatically create missing columns and tables based off a json Object.
4
+
5
+ > [!IMPORTANT]
6
+ >
7
+ > This readme is still work in progress and examples and all will come once done.
8
+
9
+ ------
10
+
11
+ ## Connection setup
12
+
13
+ The library was made with the intent to always create a connection pool.
14
+
15
+ ```js
16
+ import dSyncSql from "@hackthedev/dsync-sql"
17
+
18
+ export let db = new dSyncSql({
19
+ host: "127.0.0.1",
20
+ user: "username",
21
+ password: "some_password",
22
+ database: "database_name",
23
+ waitForConnections: true, // optional
24
+ connectionLimit: 10, // optional
25
+ queueLimit: 0, // optional
26
+ });
27
+ ```
28
+
29
+ ------
30
+
31
+ ## Defining a database
32
+
33
+ The following is optional but dSyncSql was designed to automatically create the database structure to make deployment and updates smooth and automatic.
34
+
35
+ ```js
36
+ const tables = [
37
+ {
38
+ name: "network_servers",
39
+ columns: [
40
+ {name: "id", type: "int(11) NOT NULL"},
41
+ {name: "address", type: "varchar(255) NOT NULL"},
42
+ {name: "status", type: "varchar(255) NOT NULL"},
43
+ {name: "data", type: "longtext"},
44
+ {name: "last_sync", type: "datetime NULL"},
45
+ ],
46
+ keys: [
47
+ {name: "PRIMARY KEY", type: "(id)"},
48
+ {name: "UNIQUE KEY", type: "address (address)"},
49
+ ],
50
+ autoIncrement: "id int(11) NOT NULL AUTO_INCREMENT",
51
+ }
52
+ ]
53
+ ```
54
+
55
+ You could then loop through the tables object and create the tables and columns automatically using `checkAndCreateTable`.
56
+
57
+ ```js
58
+ for (const table of tables) {
59
+ await db.checkAndCreateTable(table);
60
+ }
61
+ ```
62
+
63
+ ------
64
+
65
+ ## Running Queries
66
+
67
+ You can also run manual queries to your heart's desire using `queryDatabase`. The function also handles `ER_LOCK_DEADLOCK` errors and will retry to execute a statement 3 times on default
68
+
69
+ ```js
70
+ await db.queryDatabase(
71
+ "SELECT * FROM network_servers WHERE stats = ?",
72
+ ["verified"]
73
+ );
74
+
75
+ // with custom retry counter
76
+ await db.queryDatabase(
77
+ "SELECT * FROM network_servers WHERE stats = ?",
78
+ ["verified"],
79
+ 10
80
+ );
81
+ ```
82
+
package/bun.lock ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 0,
4
+ "workspaces": {
5
+ "": {
6
+ "dependencies": {
7
+ "@hackthedev/terminal-logger": "^1.0.0",
8
+ "mysql2": "^3.16.0",
9
+ "mysql2-promise": "^0.1.4",
10
+ },
11
+ },
12
+ },
13
+ "packages": {
14
+ "@hackthedev/terminal-logger": ["@hackthedev/terminal-logger@1.0.0", "", {}, "sha512-n2SiSpJXCCbFJwgyzR94aDkgelTsK0CG7Ruh6W0n05D5oAm8wZz8F1+0SCJISf2L0/lhTD39zeNxGRVYse8EKA=="],
15
+
16
+ "ansicolors": ["ansicolors@0.2.1", "", {}, "sha512-tOIuy1/SK/dr94ZA0ckDohKXNeBNqZ4us6PjMVLs5h1w2GBB6uPtOknp2+VF4F/zcy9LI70W+Z+pE2Soajky1w=="],
17
+
18
+ "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
19
+
20
+ "bn.js": ["bn.js@2.0.0", "", {}, "sha512-NmOLApC80+n+P28y06yHgwGlOCkq/X4jRh5s590959FZXSrM+I/61h0xxuIaYsg0mD44mEAZYG/rnclWuRoz+A=="],
21
+
22
+ "cardinal": ["cardinal@0.4.4", "", { "dependencies": { "ansicolors": "~0.2.1", "redeyed": "~0.4.0" }, "bin": { "cdl": "bin/cdl.js" } }, "sha512-3MxV0o9wOpQcobrcSrRpaSxlYkohCcZu0ytOjJUww/Yo/223q4Ecloo7odT+M0SI5kPgb1JhvSaF4EEuVXOLAQ=="],
23
+
24
+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
25
+
26
+ "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
27
+
28
+ "double-ended-queue": ["double-ended-queue@2.0.0-0", "", {}, "sha512-t5ouWOpItmHrm0J0+bX/cFrIjBFWnJkk5LbIJq6bbU/M4aLX2c3LrM4QYsBptwvlPe3WzdpQefQ0v1pe/A5wjg=="],
29
+
30
+ "esprima": ["esprima@1.0.4", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA=="],
31
+
32
+ "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="],
33
+
34
+ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
35
+
36
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
37
+
38
+ "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="],
39
+
40
+ "isarray": ["isarray@0.0.1", "", {}, "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="],
41
+
42
+ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
43
+
44
+ "lru-cache": ["lru-cache@2.5.0", "", {}, "sha512-dVmQmXPBlTgFw77hm60ud//l2bCuDKkqC2on1EBoM7s9Urm9IQDrnujwZ93NFnAq0dVZ0HBXTS7PwEG+YE7+EQ=="],
45
+
46
+ "lru.min": ["lru.min@1.1.3", "", {}, "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q=="],
47
+
48
+ "mysql2": ["mysql2@3.16.0", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-AEGW7QLLSuSnjCS4pk3EIqOmogegmze9h8EyrndavUQnIUcfkVal/sK7QznE+a3bc6rzPbAiui9Jcb+96tPwYA=="],
49
+
50
+ "mysql2-promise": ["mysql2-promise@0.1.4", "", { "dependencies": { "mysql2": "^0.15.7", "q": "^1.3.0" } }, "sha512-/h8ubU/36aIPpbfB6CENw9ZdbzIhZMZOIbstJUHVKp4J9JBRSLScrYImVx+3yZilgag732UhpQMMK5+ktdhLCw=="],
51
+
52
+ "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="],
53
+
54
+ "q": ["q@1.5.1", "", {}, "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw=="],
55
+
56
+ "readable-stream": ["readable-stream@1.0.33", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha512-72KxhcKi8bAvHP/cyyWSP+ODS5ef0DIRs0OzrhGXw31q41f19aoELCbvd42FjhpyEDxQMRiiC5rq9rfE5PzTqg=="],
57
+
58
+ "redeyed": ["redeyed@0.4.4", "", { "dependencies": { "esprima": "~1.0.4" } }, "sha512-pnk1vsaNLu1UAAClKsImKz9HjBvg9i8cbRqTRzJbiCjGF0fZSMqpdcA5W3juO3c4etFvTrabECkq9wjC45ZyxA=="],
59
+
60
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
61
+
62
+ "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="],
63
+
64
+ "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
65
+
66
+ "string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="],
67
+
68
+ "mysql2-promise/mysql2": ["mysql2@0.15.8", "", { "dependencies": { "bn.js": "2.0.0", "cardinal": "0.4.4", "double-ended-queue": "2.0.0-0", "named-placeholders": "0.1.3", "readable-stream": "1.0.33" } }, "sha512-3x5o6C20bfwJYPSoT74MOoad7/chJoq4qXHDL5VAuRBBrIyErovLoj04Dz/5EY9X2kTxWSGNiTegtxpROTd2YQ=="],
69
+
70
+ "mysql2-promise/mysql2/named-placeholders": ["named-placeholders@0.1.3", "", { "dependencies": { "lru-cache": "2.5.0" } }, "sha512-Mt79RtxZ6MYTIEemPGv/YDKpbuavcAyGHb0r37xB2mnE5jej3uBzc4+nzOeoZ4nZiii1M32URKt9IjkSTZAmTA=="],
71
+ }
72
+ }
package/index.mjs ADDED
@@ -0,0 +1,192 @@
1
+ import mysql from "mysql2/promise";
2
+ import Logger from "@hackthedev/terminal-logger"
3
+
4
+ export default class dSyncSql {
5
+ constructor({
6
+ host,
7
+ user,
8
+ password,
9
+ database,
10
+ waitForConnections = true,
11
+ connectionLimit = 10,
12
+ queueLimit = 0,
13
+ }) {
14
+
15
+ if(!host) throw new Error('host address is required');
16
+ if(!user) throw new Error('username is required');
17
+ if(!password) throw new Error('password is required');
18
+ if(!database) throw new Error('database is required');
19
+
20
+ this.connection_info = {host, user, password, database};
21
+
22
+ this.pool = mysql.createPool({
23
+ host,
24
+ user,
25
+ password,
26
+ database,
27
+ waitForConnections,
28
+ connectionLimit,
29
+ queueLimit: 0,
30
+ typeCast: function (field, next) {
31
+ if (field.type === "TINY" && field.length === 1) {
32
+ return field.string() === "1";
33
+ }
34
+ return next();
35
+ },
36
+ });
37
+ }
38
+
39
+ async waitForConnection() {
40
+ while (true) {
41
+ try {
42
+ let conn = await this.pool.getConnection();
43
+ await conn.ping();
44
+ conn.release();
45
+ return;
46
+ } catch {
47
+ await new Promise((r) => setTimeout(r, 1000));
48
+ }
49
+ }
50
+ }
51
+
52
+ async queryDatabase(query, params, retryCount = 3) {
53
+ let connection;
54
+
55
+ try {
56
+ connection = await this.pool.getConnection();
57
+ const [results,] = await connection.execute(query, params);
58
+ return results;
59
+ } catch (err) {
60
+ if (err.code === 'ER_LOCK_DEADLOCK' && retryCount > 0) {
61
+ Logger.warn('Deadlock detected, retrying transaction...', retryCount);
62
+ // wait for a short period before retrying
63
+ await new Promise(resolve => setTimeout(resolve, 100));
64
+ return this.queryDatabase(query, params, retryCount - 1);
65
+ } else {
66
+ Logger.error('SQL Error executing query:');
67
+ Logger.error(err);
68
+ throw err;
69
+ }
70
+ } finally {
71
+ if (connection) connection.release();
72
+ }
73
+ }
74
+
75
+ async checkAndCreateTable(table) {
76
+ const query = `
77
+ SELECT COUNT(*)
78
+ FROM information_schema.tables
79
+ WHERE table_schema = ?
80
+ AND table_name = ?
81
+ `;
82
+
83
+ try {
84
+ const results = await this.queryDatabase(query, [this.connection_info.database, table.name]);
85
+ const tableExists = results[0]['COUNT(*)'] > 0;
86
+
87
+ if (tableExists) {
88
+ await this.checkAndCreateColumns(table);
89
+ } else {
90
+ await this.createTable(table);
91
+ }
92
+ } catch (err) {
93
+ Logger.error('Error in checkAndCreateTable:', err);
94
+ }
95
+ }
96
+
97
+ async checkAndCreateColumns(table) {
98
+ const query = `
99
+ SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_TYPE, COLUMN_DEFAULT
100
+ FROM information_schema.columns
101
+ WHERE table_schema = ?
102
+ AND table_name = ?
103
+ `;
104
+
105
+ try {
106
+ const results = await this.queryDatabase(query, [this.connection_info.database, table.name]);
107
+ const existingColumns = results.map(row => row.COLUMN_NAME);
108
+ const missingColumns = table.columns.filter(col => !existingColumns.includes(col.name));
109
+
110
+ if (missingColumns.length > 0) {
111
+ console.log(`Adding missing columns to table "${table.name}":`, missingColumns);
112
+ await this.addMissingColumns(table.name, missingColumns);
113
+ } else {
114
+ //console.log(`All columns in table "${table.name}" are up to date.`);
115
+ }
116
+ } catch (err) {
117
+ Logger.error('Error in checkAndCreateColumns:', err);
118
+ }
119
+ }
120
+
121
+ async createTable(table) {
122
+ const columnsDefinition = table.columns.map(col => `${col.name} ${col.type}`).join(', ');
123
+ const createTableQuery = mysql.format(
124
+ `
125
+ CREATE TABLE ??
126
+ (
127
+ ${columnsDefinition}
128
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE =utf8mb4_general_ci
129
+ `,
130
+ [table.name]
131
+ );
132
+
133
+ try {
134
+ console.log('Executing CREATE TABLE query:', createTableQuery);
135
+ await this.queryDatabase(createTableQuery);
136
+
137
+ console.log(`Table "${table.name}" created successfully.`);
138
+ if (table.keys) {
139
+ await this.addKeys(table);
140
+ }
141
+ if (table.autoIncrement) {
142
+ await this.addAutoIncrement(table);
143
+ }
144
+ } catch (err) {
145
+ Logger.error('Error in createTable:', err);
146
+ }
147
+ }
148
+
149
+ async addMissingColumns(tableName, columns) {
150
+ const alter = columns
151
+ .map(col => `ADD COLUMN ${col.name} ${col.type}`)
152
+ .join(", ");
153
+
154
+ const query = mysql.format(
155
+ `ALTER TABLE ?? ${alter}`,
156
+ [tableName]
157
+ );
158
+
159
+ await this.queryDatabase(query);
160
+ }
161
+
162
+
163
+ async addKeys(table) {
164
+ const keysQueries = table.keys.map(key => `ADD ${key.name} ${key.type}`).join(', ');
165
+ const keysQuery = mysql.format(
166
+ `ALTER TABLE ?? ${keysQueries}`,
167
+ [table.name]
168
+ );
169
+
170
+ try {
171
+ console.log('Executing ADD KEYS query:', keysQuery);
172
+ await this.queryDatabase(keysQuery);
173
+ } catch (err) {
174
+ Logger.error('Error in addKeys:', err);
175
+ }
176
+ }
177
+
178
+
179
+ async addAutoIncrement(table) {
180
+ const autoIncrementQuery = mysql.format(
181
+ `ALTER TABLE ?? MODIFY ${table.autoIncrement}`,
182
+ [table.name]
183
+ );
184
+
185
+ try {
186
+ console.log('Executing AUTO_INCREMENT query:', autoIncrementQuery);
187
+ await this.queryDatabase(autoIncrementQuery);
188
+ } catch (err) {
189
+ Logger.error('Error in addAutoIncrement:', err);
190
+ }
191
+ }
192
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@hackthedev/dsync-sql",
3
+ "version": "1.0.0",
4
+ "description": "dSyncSql is supposed to be a helper for handling MySQL / MariaDB connections and executing queries. In addition it will also allow you to automatically create a database's structure like tables, column and such and will automatically create missing columns and tables based off a json Object.",
5
+ "homepage": "https://github.com/NETWORK-Z-Dev/dSyncSql#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/NETWORK-Z-Dev/dSyncSql/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/NETWORK-Z-Dev/dSyncSql.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "",
15
+ "main": "index.mjs",
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ },
19
+ "dependencies": {
20
+ "@hackthedev/terminal-logger": "^1.0.0",
21
+ "mysql2": "^3.16.0",
22
+ "mysql2-promise": "^0.1.4"
23
+ },
24
+ "devDependencies": {}
25
+ }