@vlasky/zongji 0.5.9 → 0.6.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.
@@ -1,56 +1,159 @@
1
- const Util = require('util');
2
- const { EofPacket, ErrorPacket, ComBinlog, initBinlogPacketClass } = require('../packet');
3
- const Sequence = require('@vlasky/mysql/lib/protocol/sequences').Sequence;
1
+ import { EventEmitter } from 'events';
2
+ import { initBinlogPacketClass } from '../packet/index.js';
3
+ import { Parser } from '../reader.js';
4
4
 
5
- module.exports = function(zongji) {
6
- const BinlogPacket = initBinlogPacketClass(zongji);
5
+ const BINLOG_DUMP_COMMAND = 0x12;
6
+
7
+ class Command extends EventEmitter {
8
+ constructor() {
9
+ super();
10
+ this.next = null;
11
+ }
12
+
13
+ execute(packet, connection) {
14
+ if (!this.next) {
15
+ // @ts-ignore - start is defined in subclass
16
+ this.next = this.start;
17
+ connection._resetSequenceId();
18
+ }
19
+ if (packet && packet.isError && packet.isError()) {
20
+ const err = packet.asError(connection.clientEncoding);
21
+ // @ts-ignore - onResult is an optional callback property
22
+ if (this.onResult) {
23
+ // @ts-ignore
24
+ this.onResult(err);
25
+ this.emit('end');
26
+ } else {
27
+ this.emit('error', err);
28
+ this.emit('end');
29
+ }
30
+ return true;
31
+ }
32
+ this.next = this.next(packet, connection);
33
+ if (this.next) {
34
+ return false;
35
+ }
36
+ this.emit('end');
37
+ return true;
38
+ }
39
+ }
40
+
41
+ class SimplePacket {
42
+ constructor(length) {
43
+ this.buffer = Buffer.allocUnsafe(length);
44
+ this.offset = 4;
45
+ }
46
+
47
+ length() {
48
+ return this.buffer.length;
49
+ }
50
+
51
+ writeInt8(value) {
52
+ this.buffer.writeUInt8(value, this.offset);
53
+ this.offset += 1;
54
+ }
7
55
 
8
- function Binlog(callback) {
9
- Sequence.call(this, callback);
56
+ writeInt16(value) {
57
+ this.buffer.writeUInt16LE(value, this.offset);
58
+ this.offset += 2;
10
59
  }
11
60
 
12
- Util.inherits(Binlog, Sequence);
61
+ writeInt24(value) {
62
+ this.buffer.writeUIntLE(value, this.offset, 3);
63
+ this.offset += 3;
64
+ }
65
+
66
+ writeInt32(value) {
67
+ this.buffer.writeUInt32LE(value, this.offset);
68
+ this.offset += 4;
69
+ }
70
+
71
+ writeNullTerminatedString(value, encoding) {
72
+ const buf = Buffer.from(value, encoding);
73
+ buf.copy(this.buffer, this.offset);
74
+ this.offset += buf.length;
75
+ this.writeInt8(0);
76
+ }
77
+
78
+ writeHeader(sequenceId) {
79
+ const offset = this.offset;
80
+ this.offset = 0;
81
+ this.writeInt24(this.buffer.length - 4);
82
+ this.writeInt8(sequenceId);
83
+ this.offset = offset;
84
+ }
85
+ }
13
86
 
14
- Binlog.prototype.start = function() {
15
- // options include: position / nonBlock / serverId / filename
16
- let options = zongji.get([
17
- 'serverId', 'position', 'filename', 'nonBlock',
18
- ]);
19
- this.emit('packet', new ComBinlog(options));
20
- };
87
+ export default function initBinlogClass(zongji) {
88
+ const BinlogPacket = initBinlogPacketClass(zongji);
21
89
 
22
- Binlog.prototype.determinePacket = function(firstByte) {
23
- switch (firstByte) {
24
- case 0xfe:
25
- return EofPacket;
26
- case 0xff:
27
- return ErrorPacket;
28
- default:
29
- return BinlogPacket;
90
+ class Binlog extends Command {
91
+ constructor(callback) {
92
+ super();
93
+ this._callback = callback;
30
94
  }
31
- };
32
95
 
33
- Binlog.prototype['OkPacket'] = function() {
34
- console.log('Received one OkPacket ...');
35
- };
96
+ start(packet, connection) {
97
+ const options = zongji.get([
98
+ 'serverId', 'position', 'filename', 'nonBlock',
99
+ ]);
100
+ const binlogPos = options.position || 4;
101
+ const serverId = options.serverId || 1;
102
+ const flags = options.nonBlock ? 1 : 0;
103
+ const filename = options.filename || '';
36
104
 
37
- Binlog.prototype['BinlogPacket'] = function(packet) {
38
- if (this._callback) {
105
+ const length = 16 + Buffer.byteLength(filename, 'utf8');
106
+ const outPacket = new SimplePacket(length);
107
+ outPacket.writeInt8(BINLOG_DUMP_COMMAND);
108
+ outPacket.writeInt32(binlogPos);
109
+ outPacket.writeInt16(flags);
110
+ outPacket.writeInt32(serverId);
111
+ outPacket.writeNullTerminatedString(filename, 'utf8');
112
+ connection.writePacket(outPacket);
113
+ return Binlog.prototype.binlogData;
114
+ }
39
115
 
40
- // Check event filtering
41
- if (zongji._skipEvent(packet.eventName.toLowerCase())) {
42
- return this._callback.call(this);
116
+ binlogData(packet, connection) {
117
+ if (packet.isEOF()) {
118
+ this.emit('eof');
119
+ return null;
43
120
  }
44
121
 
45
- let event, error;
46
- try {
47
- event = packet.getEvent();
48
- } catch (err) {
49
- error = err;
122
+ if (packet.isError()) {
123
+ const err = packet.asError(connection.clientEncoding);
124
+ if (this._callback) {
125
+ this._callback.call(this, err);
126
+ } else {
127
+ this.emit('error', err);
128
+ }
129
+ return null;
50
130
  }
51
- this._callback.call(this, error, event);
131
+
132
+ const parser = new Parser(packet);
133
+ const binlogPacket = new BinlogPacket();
134
+ binlogPacket.parse(parser);
135
+
136
+ if (this._callback) {
137
+ const eventName = binlogPacket.eventName
138
+ ? binlogPacket.eventName.toLowerCase()
139
+ : 'unknown';
140
+ if (zongji._skipEvent(eventName)) {
141
+ return Binlog.prototype.binlogData;
142
+ }
143
+
144
+ let event;
145
+ let error;
146
+ try {
147
+ event = binlogPacket.getEvent();
148
+ } catch (err) {
149
+ error = err;
150
+ }
151
+ this._callback.call(this, error, event);
152
+ }
153
+
154
+ return Binlog.prototype.binlogData;
52
155
  }
53
- };
156
+ }
54
157
 
55
158
  return Binlog;
56
159
  };
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "@vlasky/zongji",
3
- "version": "0.5.9",
4
- "description": "A MySQL 8.0-compatible fork of ZongJi - a MySQL binlog listener for Node.js.",
3
+ "version": "0.6.1",
4
+ "description": "MySQL binlog-based change data capture (CDC) for Node.js",
5
+ "type": "module",
5
6
  "main": "index.js",
7
+ "types": "index.d.ts",
6
8
  "directories": {
7
9
  "test": "test"
8
10
  },
9
11
  "scripts": {
10
- "test": "tap --bail --no-coverage --jobs=1 test/*.js",
11
- "lint": "eslint ."
12
+ "pretest": "bash scripts/start-mysql.sh",
13
+ "test": "tap --bail --disable-coverage --jobs=1 test/*.js",
14
+ "lint": "eslint .",
15
+ "checkjs": "tsc --project jsconfig.json"
12
16
  },
13
17
  "repository": {
14
18
  "type": "git",
15
- "url": "https://github.com/vlasky/zongji"
19
+ "url": "git+https://github.com/vlasky/zongji.git"
16
20
  },
17
21
  "keywords": [
18
22
  "mysql",
@@ -30,15 +34,16 @@
30
34
  ],
31
35
  "license": "MIT",
32
36
  "engines": {
33
- "node": ">= 8.0"
37
+ "node": ">= 18.0"
34
38
  },
35
39
  "devDependencies": {
36
- "eslint": "6.8.0",
37
- "tap": "14.10.7"
40
+ "@eslint/js": "^9.39.2",
41
+ "eslint": "^9.39.2",
42
+ "tap": "^21.5.0"
38
43
  },
39
44
  "dependencies": {
40
- "big-integer": "1.6.51",
41
- "iconv-lite": "0.6.3",
42
- "@vlasky/mysql": "^2.18.6"
45
+ "big-integer": "1.6.52",
46
+ "iconv-lite": "0.7.2",
47
+ "mysql2": "^3.17.1"
43
48
  }
44
49
  }
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # Start MySQL Docker containers for testing
3
+ # Skips MySQL 5.7 on ARM64 (Apple Silicon) as no image is available
4
+
5
+ if [ "$(uname -m)" = "arm64" ]; then
6
+ SERVICES="mysql80 mysql84"
7
+ else
8
+ SERVICES="mysql57 mysql80 mysql84"
9
+ fi
10
+
11
+ docker-compose up -d $SERVICES
12
+
13
+ # Wait for MySQL to be ready
14
+ for service in $SERVICES; do
15
+ container="zongji-${service}-1"
16
+ echo -n "Waiting for ${service}..."
17
+ for i in $(seq 1 30); do
18
+ if docker exec "$container" mysqladmin ping -u root -psecret --silent 2>/dev/null; then
19
+ echo " ready"
20
+ break
21
+ fi
22
+ if [ "$i" -eq 30 ]; then
23
+ echo " timed out"
24
+ exit 1
25
+ fi
26
+ sleep 2
27
+ done
28
+ done
package/.eslintrc DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "env": {
3
- "node": true,
4
- "es2020": true
5
- },
6
- "extends": "eslint:recommended",
7
- "rules": {
8
- "comma-dangle": ["warn", "only-multiline"],
9
- "eol-last": ["error"],
10
- "keyword-spacing": ["error", { "before": true } ],
11
- "no-console": "off",
12
- "no-trailing-spaces": ["error", { "skipBlankLines": true }],
13
- "no-unused-vars": "warn",
14
- "no-var": "warn",
15
- "quotes": ["warn", "single", "avoid-escape"],
16
- "semi": ["error", "always"],
17
- "space-before-blocks": "error"
18
- }
19
- }