@solashi/go-mysql-binlog-node 0.0.1
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 +66 -0
- package/dist/binding.d.ts +46 -0
- package/dist/binding.js +152 -0
- package/package.json +41 -0
- package/prebuilds/darwin-arm64 +0 -0
- package/prebuilds/darwin-x64 +0 -0
- package/prebuilds/linux-arm64 +0 -0
- package/prebuilds/linux-x64 +0 -0
- package/prebuilds/win32-arm64.exe +0 -0
- package/prebuilds/win32-x64.exe +0 -0
- package/scripts/postinstall.js +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @solashi/go-mysql-binlog-node
|
|
2
|
+
|
|
3
|
+
A library wrapping the [go-mysql](https://github.com/go-mysql-org/go-mysql) package, providing a MySQL client connector and binlog parsing implementation.
|
|
4
|
+
|
|
5
|
+
> This project is forked from [go-mysql-js](https://github.com/go-mysql-org/go-mysql-js)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i --save @solashi/go-mysql-binlog-node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
* Enable MySQL binlog in `my.cnf`, restart MySQL server after making the changes.
|
|
14
|
+
> binlog checksum is enabled by default. @solashi/go-mysql-binlog-node can work with it, but it doesn't really verify it.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
# Must be unique integer from 1-2^32
|
|
18
|
+
server-id = 1
|
|
19
|
+
# Row format required
|
|
20
|
+
binlog_format = row
|
|
21
|
+
# Directory must exist. This path works for Linux. Other OS may require
|
|
22
|
+
# different path.
|
|
23
|
+
log_bin = /var/log/mysql/mysql-bin.log
|
|
24
|
+
|
|
25
|
+
binlog_do_db = employees # Optional, limit which databases to log
|
|
26
|
+
expire_logs_days = 10 # Optional, purge old logs
|
|
27
|
+
max_binlog_size = 100M # Optional, limit log size
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
* Create an account with replication privileges, e.g. given privileges to account `root` (or any account that you use to read binary logs)
|
|
31
|
+
|
|
32
|
+
```sql
|
|
33
|
+
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SELECT ON *.* TO 'root'@'localhost'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import MysqlBinlog from '@solashi/go-mysql-binlog-node';
|
|
40
|
+
|
|
41
|
+
async function main() {
|
|
42
|
+
let syncer = await MysqlBinlog.create({
|
|
43
|
+
hostname: "localhost",
|
|
44
|
+
port: 3306,
|
|
45
|
+
username: "root",
|
|
46
|
+
password: "mypassword",
|
|
47
|
+
tableRegexes: ['Users'],
|
|
48
|
+
});
|
|
49
|
+
syncer.on('event', (event) => {
|
|
50
|
+
console.log('got row event', event);
|
|
51
|
+
});
|
|
52
|
+
syncer.on('error', (err) => {
|
|
53
|
+
console.error('got error', err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
process.on('SIGINT', function() {
|
|
57
|
+
console.log("Caught interrupt signal");
|
|
58
|
+
syncer.close();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main().catch(err => {
|
|
63
|
+
console.error(err);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
});
|
|
66
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface BinlogPosition {
|
|
3
|
+
name: string;
|
|
4
|
+
position: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Config {
|
|
7
|
+
hostname: string;
|
|
8
|
+
port: number;
|
|
9
|
+
username: string;
|
|
10
|
+
password: string;
|
|
11
|
+
tableRegexes: string[];
|
|
12
|
+
binlogPosition?: BinlogPosition;
|
|
13
|
+
}
|
|
14
|
+
export interface BinlogEvent {
|
|
15
|
+
binlogPosition: {
|
|
16
|
+
name: string;
|
|
17
|
+
position: number;
|
|
18
|
+
};
|
|
19
|
+
table: {
|
|
20
|
+
schema: string;
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
insert?: Record<string, any>[];
|
|
24
|
+
update?: {
|
|
25
|
+
old: Record<string, any>;
|
|
26
|
+
new: Record<string, any>;
|
|
27
|
+
}[];
|
|
28
|
+
delete?: Record<string, any>[];
|
|
29
|
+
}
|
|
30
|
+
export declare class ErrorWithBinLogPosition extends Error {
|
|
31
|
+
readonly binlogPosition: BinlogPosition;
|
|
32
|
+
constructor(message: string, binlogPosition: BinlogPosition);
|
|
33
|
+
}
|
|
34
|
+
declare class MysqlBinlog extends EventEmitter {
|
|
35
|
+
private _process;
|
|
36
|
+
private _readline;
|
|
37
|
+
private constructor();
|
|
38
|
+
private send;
|
|
39
|
+
static create(config: Config): Promise<MysqlBinlog>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
on(name: 'event', listener: (event: BinlogEvent) => void): this;
|
|
42
|
+
on(name: 'error', listener: (err: Error) => void): this;
|
|
43
|
+
on(name: 'close', listener: () => void): this;
|
|
44
|
+
on(name: 'beforeClose', listener: () => void): this;
|
|
45
|
+
}
|
|
46
|
+
export default MysqlBinlog;
|
package/dist/binding.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ErrorWithBinLogPosition = void 0;
|
|
13
|
+
const events_1 = require("events");
|
|
14
|
+
const debug_1 = require("debug");
|
|
15
|
+
const child_process_1 = require("child_process");
|
|
16
|
+
const readline = require("readline");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const os = require("os");
|
|
20
|
+
const debugChannel = (0, debug_1.debug)('mysql_binlog');
|
|
21
|
+
class ErrorWithBinLogPosition extends Error {
|
|
22
|
+
constructor(message, binlogPosition) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = this.constructor.name;
|
|
25
|
+
this.binlogPosition = binlogPosition;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ErrorWithBinLogPosition = ErrorWithBinLogPosition;
|
|
29
|
+
function _discoverGoBinary() {
|
|
30
|
+
let suffix = '';
|
|
31
|
+
if (os.platform() === 'win32') {
|
|
32
|
+
suffix = '.exe';
|
|
33
|
+
}
|
|
34
|
+
const filename = path.join(__dirname, '..', 'prebuilds', `${os.platform()}-${os.arch()}${suffix}`);
|
|
35
|
+
if (!fs.existsSync(filename)) {
|
|
36
|
+
throw new Error('Could not find pre-compiled Go binary. Either your platform is unsupported or you need to compile the binaries');
|
|
37
|
+
}
|
|
38
|
+
return filename;
|
|
39
|
+
}
|
|
40
|
+
class MysqlBinlog extends events_1.EventEmitter {
|
|
41
|
+
constructor(config, process) {
|
|
42
|
+
super();
|
|
43
|
+
this._process = process;
|
|
44
|
+
this._readline = readline.createInterface({
|
|
45
|
+
input: this._process.stdout,
|
|
46
|
+
crlfDelay: Infinity,
|
|
47
|
+
});
|
|
48
|
+
this._readline.on('line', (line) => {
|
|
49
|
+
let msg;
|
|
50
|
+
try {
|
|
51
|
+
msg = JSON.parse(line);
|
|
52
|
+
if (typeof msg !== 'object') {
|
|
53
|
+
throw new Error();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
debugChannel('received unexpected message on stdout: %s', line);
|
|
58
|
+
this.emit('error', new Error('received unexpected message'));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
switch (msg.type) {
|
|
62
|
+
case 'connect_ok':
|
|
63
|
+
this.emit('_connect_ok');
|
|
64
|
+
break;
|
|
65
|
+
case 'connect_error':
|
|
66
|
+
this.emit('_connect_err', new Error(msg.error));
|
|
67
|
+
break;
|
|
68
|
+
case 'binlog_change':
|
|
69
|
+
this.emit('event', msg.event);
|
|
70
|
+
break;
|
|
71
|
+
case 'log':
|
|
72
|
+
debugChannel(msg.message.trimEnd());
|
|
73
|
+
break;
|
|
74
|
+
case 'error':
|
|
75
|
+
console.log(msg);
|
|
76
|
+
if (msg.binlogPosition) {
|
|
77
|
+
this.emit('error', new ErrorWithBinLogPosition(msg.error, msg.binlogPosition));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.emit('error', new Error(msg.error));
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
debugChannel('received unexpected message on stdout: %o', msg);
|
|
85
|
+
this.emit('error', new Error('received unexpected message'));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this._process.stderr.on('data', (chunk) => {
|
|
90
|
+
debugChannel('received unexpected data on stderr: %s', chunk);
|
|
91
|
+
this.emit('error', new Error('received unexpected data on stderr'));
|
|
92
|
+
});
|
|
93
|
+
this._process.on('close', () => {
|
|
94
|
+
this.emit('close');
|
|
95
|
+
});
|
|
96
|
+
this._process.on('error', (err) => {
|
|
97
|
+
debugChannel('received unexpected error: %s', err);
|
|
98
|
+
this.emit('error', err);
|
|
99
|
+
});
|
|
100
|
+
this.send({
|
|
101
|
+
type: 'connect',
|
|
102
|
+
config,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
send(message) {
|
|
106
|
+
this._process.stdin.write(JSON.stringify(message) + '\n');
|
|
107
|
+
}
|
|
108
|
+
static create(config) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const process = (0, child_process_1.spawn)(_discoverGoBinary(), {
|
|
111
|
+
stdio: 'pipe',
|
|
112
|
+
});
|
|
113
|
+
process.on('error', (err) => {
|
|
114
|
+
reject(err);
|
|
115
|
+
});
|
|
116
|
+
process.once('spawn', () => {
|
|
117
|
+
process.removeAllListeners('error');
|
|
118
|
+
const obj = new MysqlBinlog(config, process);
|
|
119
|
+
obj.once('_connect_ok', () => {
|
|
120
|
+
resolve(obj);
|
|
121
|
+
});
|
|
122
|
+
obj.once('_connect_err', (err) => {
|
|
123
|
+
obj.close();
|
|
124
|
+
reject(err);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
close() {
|
|
130
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
131
|
+
if (this._process.exitCode !== null) {
|
|
132
|
+
// do not try to kill the process multiple times
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.emit('beforeClose');
|
|
136
|
+
this._readline.close();
|
|
137
|
+
this._process.kill();
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
this._process.once('error', (err) => {
|
|
140
|
+
reject(err);
|
|
141
|
+
});
|
|
142
|
+
this._process.once('close', () => {
|
|
143
|
+
resolve(undefined);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
on(eventName, listener) {
|
|
149
|
+
return super.on(eventName, listener);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.default = MysqlBinlog;
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solashi/go-mysql-binlog-node",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A Node.js MySQL binlog listener based on go-mysql",
|
|
5
|
+
"main": "dist/binding.js",
|
|
6
|
+
"types": "dist/binding.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node ./test/test_binding.js",
|
|
9
|
+
"build": "npm run build:golang && npm run build:js",
|
|
10
|
+
"build:js": "tsc",
|
|
11
|
+
"build:golang": "node scripts/build_golang.js",
|
|
12
|
+
"postinstall": "node scripts/postinstall.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mysql",
|
|
16
|
+
"binlog"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"debug": "^4.4.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/debug": "^4.1.12",
|
|
23
|
+
"@types/node": "^22.13.4",
|
|
24
|
+
"typescript": "^5.7.3"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist/*",
|
|
28
|
+
"prebuilds/*",
|
|
29
|
+
"scripts/postinstall.js"
|
|
30
|
+
],
|
|
31
|
+
"author": "Pham Van Cong",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/congpv93/mysql-binlog-node"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/congpv93/mysql-binlog-node"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/congpv93/mysql-binlog-node"
|
|
41
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
function nativeGoBinaryName() {
|
|
6
|
+
let suffix = '';
|
|
7
|
+
if (os.platform() === 'win32') {
|
|
8
|
+
suffix = '.exe';
|
|
9
|
+
}
|
|
10
|
+
return `${os.platform()}-${os.arch()}${suffix}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PREBUILDS_PATH = path.join(__dirname, '..', 'prebuilds');
|
|
14
|
+
const nativeBinaryFilename = nativeGoBinaryName();
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(PREBUILDS_PATH)) {
|
|
17
|
+
console.error('WARNING: directory containing precompiled Go binaries does not exist. Please run "npm run build" first.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let nativeBinaryFound = false;
|
|
22
|
+
for (const entry of fs.readdirSync(PREBUILDS_PATH, { withFileTypes: true })) {
|
|
23
|
+
if (!entry.isFile()) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (entry.name !== nativeBinaryFilename) {
|
|
28
|
+
fs.unlinkSync(path.join(PREBUILDS_PATH, entry.name));
|
|
29
|
+
} else {
|
|
30
|
+
// ensure the binary is executable
|
|
31
|
+
fs.chmodSync(path.join(PREBUILDS_PATH, entry.name), 0o755);
|
|
32
|
+
nativeBinaryFound = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!nativeBinaryFound) {
|
|
37
|
+
console.error(`WARNING: could not find the precompiled Go binary for your platform (${nativeBinaryFilename})!`);
|
|
38
|
+
}
|