jsql-neo 5.2.0 → 5.2.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.
@@ -0,0 +1,141 @@
1
+ /*
2
+ * Multi-protocol multiplexing server for JSQL-NEO.
3
+ *
4
+ * Listens on ONE port and sniffs the first bytes of each connection to route
5
+ * to the correct wire-protocol handler:
6
+ *
7
+ * - PostgreSQL : first byte 0x00 (big-endian Int32 length, length < 2^24)
8
+ * - MySQL : first byte 0x0a/0x0d (protocol handshake version / cap flag)
9
+ * - Redis : anything else (ASCII command or RESP prefix)
10
+ *
11
+ * This lets every npm database client (mysql2, pg, ioredis, sequelize, knex,
12
+ * typeorm, redis-cli, psql, phpMyAdmin ...) connect to the same endpoint.
13
+ */
14
+ const net = require('net');
15
+ const path = require('path');
16
+ const Database = require('./database');
17
+ const { MysqlServer, MysqlConnection } = require('./mysql_server');
18
+ const { PgServer, PgConnection } = require('./pg_server');
19
+ const { RedisServer } = require('./redis_server');
20
+ const { MongoServer } = require('./mongo_server');
21
+
22
+ function sniffProtocol(buf) {
23
+ if (buf.length < 1) return null;
24
+ const b = buf[0];
25
+ if (b === 0x00) return 'pg';
26
+ if (b === 0x0a || b === 0x0d) return 'mysql';
27
+ // MongoDB: header 前 4 字节 = int32LE 长度,第 13 字节 = opCode(OP_QUERY 2004 / OP_COMPRESSED 2012 / OP_MSG 2013)
28
+ if (buf.length >= 16) {
29
+ const opc = buf.readInt32LE(12);
30
+ if (opc === 2012 || opc === 2013 || opc === 2004) {
31
+ const len = buf.readInt32LE(0);
32
+ if (len >= 16 && len <= 48 * 1024 * 1024) return 'mongo';
33
+ }
34
+ }
35
+ return 'redis';
36
+ }
37
+
38
+ class MultiServer {
39
+ constructor(options = {}) {
40
+ this.options = options;
41
+ this.port = options.port || 5432;
42
+ this.host = options.host || '127.0.0.1';
43
+ this.dataDir = options.dataDir || null;
44
+ // 共享 SQL 引擎:三种 SQL 协议读写同一份数据
45
+ this._engines = new Map();
46
+ const getEngine = async (dbName) => {
47
+ const key = dbName || 'default';
48
+ if (this._engines.has(key)) return this._engines.get(key);
49
+ let engine;
50
+ if (this.dataDir && this.dataDir !== ':memory:') {
51
+ engine = new Database(path.join(this.dataDir, key), { autoSave: true });
52
+ } else {
53
+ engine = new Database(':memory:', { autoSave: false });
54
+ }
55
+ this._engines.set(key, engine);
56
+ return engine;
57
+ };
58
+ // 三个底层服务器共享同一个数据目录(不各自 listen)
59
+ const baseOpts = { ...options, port: 0, host: this.host };
60
+ this.mysql = new MysqlServer(baseOpts);
61
+ this.mysql._getEngine = getEngine;
62
+ this.pg = new PgServer(baseOpts);
63
+ this.pg._getEngine = getEngine;
64
+ this.redis = new RedisServer({ ...baseOpts });
65
+ this.mongo = new MongoServer(baseOpts);
66
+ this.mongo._getEngine = getEngine;
67
+ this._server = null;
68
+ this._sockets = new Set();
69
+ }
70
+
71
+ listen(cb) {
72
+ this._server = net.createServer((socket) => {
73
+ this._sockets.add(socket);
74
+ socket.on('close', () => this._sockets.delete(socket));
75
+ let first = Buffer.alloc(0);
76
+ let routed = false;
77
+ // MySQL 客户端会先等服务器握手包,不主动发字节;超时后按 MySQL 处理
78
+ const t = setTimeout(() => {
79
+ if (!routed) { routed = true; socket.removeListener('data', sniff); this._route(socket, first); }
80
+ }, 200);
81
+ t.unref();
82
+ const sniff = (chunk) => {
83
+ first = Buffer.concat([first, chunk]);
84
+ if (routed) return;
85
+ if (first.length < 4) return; // 等至少 4 字节再判断
86
+ routed = true;
87
+ clearTimeout(t);
88
+ socket.removeListener('data', sniff);
89
+ try {
90
+ this._route(socket, first);
91
+ } catch (e) {
92
+ socket.destroy();
93
+ }
94
+ };
95
+ socket.on('data', sniff);
96
+ });
97
+ this._server.listen(this.port, this.host, cb || (() => {}));
98
+ this._server.on('error', (err) => {
99
+ if (this.options.onError) this.options.onError(err);
100
+ else throw err;
101
+ });
102
+ return this;
103
+ }
104
+
105
+ _route(socket, first) {
106
+ const proto = sniffProtocol(first) || 'mysql'; // 无字节(MySQL 客户端等握手)→ 按 MySQL 处理
107
+ if (proto === 'mysql') {
108
+ const conn = new MysqlConnection(socket, this.mysql);
109
+ conn._onData(first);
110
+ } else if (proto === 'pg') {
111
+ const conn = new PgConnection(socket, this.pg);
112
+ conn._onData(first);
113
+ } else if (proto === 'mongo') {
114
+ this.mongo._handleSocket(socket, first);
115
+ } else {
116
+ this.redis._handleSocket(socket, first.toString('utf8'));
117
+ }
118
+ }
119
+
120
+ get address() {
121
+ return this._server ? this._server.address() : null;
122
+ }
123
+
124
+ close(cb) {
125
+ for (const s of this._sockets) s.destroy();
126
+ const stops = [];
127
+ for (const engine of this._engines.values()) {
128
+ if (engine && typeof engine.stop === 'function') stops.push(engine.stop());
129
+ }
130
+ const done = () => { if (this._server) { this._server.close(cb || (() => {})); this._server = null; } else if (cb) cb(); };
131
+ if (stops.length > 0) Promise.allSettled(stops).then(done);
132
+ else done();
133
+ return this;
134
+ }
135
+ }
136
+
137
+ function createMultiServer(options) {
138
+ return new MultiServer(options || {});
139
+ }
140
+
141
+ module.exports = { MultiServer, createMultiServer, sniffProtocol };
@@ -1222,4 +1222,4 @@ function createMysqlServer(options) {
1222
1222
  return new MysqlServer(options || {});
1223
1223
  }
1224
1224
 
1225
- module.exports = { createMysqlServer, MysqlServer };
1225
+ module.exports = { createMysqlServer, MysqlServer, MysqlConnection };