tmc.js 0.3.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/lib/message.js ADDED
@@ -0,0 +1,302 @@
1
+ /* eslint max-classes-per-file: ["error", 3] */
2
+ const { types } = require('util');
3
+ const ValueFormat = require('./value-format');
4
+ const MessageType = require('./message-type');
5
+ const MessageKind = require('./message-kind');
6
+ const {
7
+ EndOfHeaders, Custom, StatusCode, StatusPhrase, Flag, Token,
8
+ } = require('./header-type');
9
+ const {
10
+ TYPE, CODE, PHRASE, FLAG, TOKEN, CONTENT,
11
+ } = require('./message-fields');
12
+
13
+ class Message {
14
+ protocolVersion = 2;
15
+
16
+ [TYPE];
17
+
18
+ [CODE];
19
+
20
+ [PHRASE];
21
+
22
+ [FLAG];
23
+
24
+ [TOKEN];
25
+
26
+ [CONTENT] = {};
27
+
28
+ constructor(type, kind) {
29
+ /* eslint-disable-next-line no-unused-expressions */
30
+ MessageType.includes(type) && Reflect.set(this, TYPE, type);
31
+ /* eslint-disable-next-line no-unused-expressions */
32
+ MessageKind.includes(kind) && Reflect.set(this[CONTENT], MessageKind, kind);
33
+ }
34
+
35
+ with(key, value) {
36
+ Reflect.set(
37
+ [TYPE, CODE, PHRASE, FLAG, TOKEN].includes(key) ? this : this[CONTENT],
38
+ key,
39
+ value,
40
+ );
41
+
42
+ return this;
43
+ }
44
+
45
+ get buffer() {
46
+ /* eslint-disable-next-line no-use-before-define */
47
+ return new Encoder(this).buffer;
48
+ }
49
+
50
+ static from(buf) {
51
+ /* eslint-disable-next-line no-use-before-define */
52
+ return new Decoder(buf).data;
53
+ }
54
+ }
55
+
56
+ const utf8 = 'utf8';
57
+
58
+ const write = 'write';
59
+
60
+ const writeInt8 = 'writeInt8';
61
+
62
+ const writeInt16LE = 'writeInt16LE';
63
+
64
+ const writeInt32LE = 'writeInt32LE';
65
+
66
+ const writeBigInt64LE = 'writeBigInt64LE';
67
+
68
+ const {
69
+ Void, Byte, Int16, Int32, Int64, ByteArray, CountedString,
70
+ } = ValueFormat;
71
+
72
+ function pick(size, method, ...value) {
73
+ const buf = Buffer.alloc(size);
74
+ buf[method](...value);
75
+
76
+ return Buffer.from(buf.buffer);
77
+ }
78
+
79
+ class Encoder {
80
+ buffers = [];
81
+
82
+ data;
83
+
84
+ constructor(message) {
85
+ if (!(message instanceof Message)) {
86
+ throw new TypeError('Must be an instance Message here.');
87
+ }
88
+ this.data = message;
89
+ }
90
+
91
+ compress() {
92
+ this.put(this.data.protocolVersion).put(this.data[TYPE]);
93
+ /* eslint-disable-next-line no-unused-expressions */
94
+ this.data[CODE] && this.putShort(StatusCode).putShort(this.data[CODE]);
95
+ /* eslint-disable-next-line no-unused-expressions */
96
+ this.data[PHRASE] && this.putShort(StatusPhrase).writeCountedString(this.data[PHRASE]);
97
+ /* eslint-disable-next-line no-unused-expressions */
98
+ this.data[FLAG] && this.putShort(Flag).putInt(this.data[FLAG]);
99
+ /* eslint-disable-next-line no-unused-expressions */
100
+ this.data[TOKEN] && this.putShort(Token).writeCountedString(this.data[TOKEN]);
101
+ Object.entries(this.data[CONTENT] || {}).forEach(([k, v]) => this.putShort(Custom).writeCountedString(k).writeCustomValue(v));
102
+ this.putShort(EndOfHeaders);
103
+
104
+ return this;
105
+ }
106
+
107
+ put(value) {
108
+ this.buffers.push(pick(1, writeInt8, Number(value)));
109
+
110
+ return this;
111
+ }
112
+
113
+ putShort(value) {
114
+ this.buffers.push(pick(2, writeInt16LE, Number(value)));
115
+
116
+ return this;
117
+ }
118
+
119
+ putInt(value) {
120
+ this.buffers.push(pick(4, writeInt32LE, Number(value)));
121
+
122
+ return this;
123
+ }
124
+
125
+ putLong(value) {
126
+ this.buffers.push(pick(8, writeBigInt64LE, BigInt(value)));
127
+
128
+ return this;
129
+ }
130
+
131
+ writeCountedString(value) {
132
+ const len = value ? Buffer.byteLength(value) : 0;
133
+ this.putInt(len);
134
+ /* eslint-disable-next-line no-unused-expressions */
135
+ len && this.buffers.push(pick(len, write, value, utf8));
136
+
137
+ return this;
138
+ }
139
+
140
+ writeCustomValue(value) {
141
+ const type = typeof value;
142
+ if (value === null || ['undefined', 'symbol', 'boolean', 'function'].includes(type)) {
143
+ return this.put(Void);
144
+ }
145
+
146
+ if (type === 'number') {
147
+ switch (true) {
148
+ case value > -128 && value < 127:
149
+ return this.put(Byte).put(value);
150
+
151
+ case value > -32_768 && value < 32_767:
152
+ return this.put(Int16).putShort(value);
153
+
154
+ case value > -2_147_483_648 && value < 2_147_483_647:
155
+ return this.put(Int32).putInt(value);
156
+
157
+ default:
158
+ return this.put(Int64).putLong(value);
159
+ }
160
+ }
161
+
162
+ if (type === 'bigint') {
163
+ return this.put(Int64).putLong(value);
164
+ }
165
+
166
+ if (type === 'string') {
167
+ return this.put(CountedString).writeCountedString(value);
168
+ }
169
+
170
+ if (types.isDate(value)) {
171
+ return this.put(ValueFormat.Date).putLong(value);
172
+ }
173
+
174
+ if (Buffer.isBuffer(value)) {
175
+ this.put(ByteArray).putInt(value.length);
176
+ this.buffers.push(value);
177
+ }
178
+
179
+ return this;
180
+ }
181
+
182
+ get buffer() { return Buffer.concat(this.compress().buffers); }
183
+ }
184
+
185
+ const readInt8 = 'readInt8';
186
+
187
+ const readInt16LE = 'readInt16LE';
188
+
189
+ const readInt32LE = 'readInt32LE';
190
+
191
+ const readBigInt64LE = 'readBigInt64LE';
192
+
193
+ function read(buffer, method, offset, ...extra) {
194
+ return buffer[method](offset, ...extra);
195
+ }
196
+
197
+ class Decoder {
198
+ offset = 1;
199
+
200
+ buffer;
201
+
202
+ data;
203
+
204
+ constructor(buf) {
205
+ if (!Buffer.isBuffer(buf)) {
206
+ throw new TypeError('Must be an instance Buffer here.');
207
+ }
208
+ this.buffer = buf;
209
+ this.data = new Message();
210
+ this.extract();
211
+ }
212
+
213
+ extract() {
214
+ this.data.with(TYPE, this.reset().get());
215
+
216
+ let headerType;
217
+ do {
218
+ headerType = this.getShort();
219
+ this[`pick${headerType}`]();
220
+ } while (headerType !== EndOfHeaders);
221
+
222
+ this.reset();
223
+ }
224
+
225
+ [`pick${EndOfHeaders}`]() { return this.data; }
226
+
227
+ [`pick${Custom}`]() { return this.data.with(this.readCountedString(), this.readCustomValue()); }
228
+
229
+ [`pick${StatusCode}`]() { return this.data.with(CODE, this.getInt()); }
230
+
231
+ [`pick${StatusPhrase}`]() { return this.data.with(PHRASE, this.readCountedString()); }
232
+
233
+ [`pick${Flag}`]() { return this.data.with(FLAG, this.getInt()); }
234
+
235
+ [`pick${Token}`]() { return this.data.with(TOKEN, this.readCountedString()); }
236
+
237
+ reset() {
238
+ this.offset = 1;
239
+ return this;
240
+ }
241
+
242
+ get() {
243
+ const value = read(this.buffer, readInt8, this.offset);
244
+ this.offset += 1;
245
+ return Number(value);
246
+ }
247
+
248
+ getShort() {
249
+ const value = read(this.buffer, readInt16LE, this.offset);
250
+ this.offset += 2;
251
+ return Number(value);
252
+ }
253
+
254
+ getInt() {
255
+ const value = read(this.buffer, readInt32LE, this.offset);
256
+ this.offset += 4;
257
+ return Number(value);
258
+ }
259
+
260
+ getLong() {
261
+ const value = read(this.buffer, readBigInt64LE, this.offset);
262
+ this.offset += 8;
263
+ return BigInt(value);
264
+ }
265
+
266
+ readBuffer() {
267
+ const size = this.getInt();
268
+ if (size === 0) {
269
+ return null;
270
+ }
271
+ const buf = this.buffer.subarray(this.offset, this.offset + size);
272
+ this.offset += size;
273
+ return buf;
274
+ }
275
+
276
+ readCountedString() { return this.readBuffer()?.toString(utf8); }
277
+
278
+ readCustomValue() { return this[`parse${this.get()}`](); }
279
+
280
+ [`parse${Void}`]() { /* eslint-disable-line class-methods-use-this */ return null; }
281
+
282
+ [`parse${Byte}`]() { return this.get(); }
283
+
284
+ [`parse${Int16}`]() { return this.getShort(); }
285
+
286
+ [`parse${Int32}`]() { return this.getInt(); }
287
+
288
+ [`parse${Int64}`]() { return this.getLong(); }
289
+
290
+ [`parse${ValueFormat.Date}`]() { return new Date(Number(this.getLong())); }
291
+
292
+ [`parse${ByteArray}`]() { return this.readBuffer(); }
293
+
294
+ [`parse${CountedString}`]() { return this.readCountedString(); }
295
+
296
+ get message() { return this.data; }
297
+ }
298
+
299
+ module.exports = Message;
300
+ module.exports.default = Message;
301
+ module.exports.Encoder = Encoder;
302
+ module.exports.Decoder = Decoder;
@@ -0,0 +1,37 @@
1
+ const Void = 0;
2
+ const CountedString = 1;
3
+ const Byte = 2;
4
+ const Int16 = 3;
5
+ const Int32 = 4;
6
+ const Int64 = 5;
7
+ const FmtDate = 6;
8
+ const ByteArray = 7;
9
+
10
+ class ValueFormat {
11
+ static Void = Void;
12
+
13
+ static CountedString = CountedString;
14
+
15
+ static Byte = Byte;
16
+
17
+ static Int16 = Int16;
18
+
19
+ static Int32 = Int32;
20
+
21
+ static Int64 = Int64;
22
+
23
+ static Date = FmtDate;
24
+
25
+ static ByteArray = ByteArray;
26
+ }
27
+
28
+ module.exports = ValueFormat;
29
+ module.exports.default = ValueFormat;
30
+ module.exports.Void = Void;
31
+ module.exports.CountedString = CountedString;
32
+ module.exports.Byte = Byte;
33
+ module.exports.Int16 = Int16;
34
+ module.exports.Int32 = Int32;
35
+ module.exports.Int64 = Int64;
36
+ module.exports.Date = FmtDate;
37
+ module.exports.ByteArray = ByteArray;
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "tmc.js",
3
+ "version": "0.3.0",
4
+ "description": "Events driven and chained Taobao Message Channel(TMC) for NodeJS",
5
+ "author": "James ZHANG",
6
+ "license": "MIT",
7
+ "types": "types/index.d.ts",
8
+ "main": "index.js",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./index.mjs",
12
+ "require": "./index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/TheNorthMemory/tmc.js/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/TheNorthMemory/tmc.js.git"
22
+ },
23
+ "files": [
24
+ "lib/",
25
+ "types/",
26
+ "index.js",
27
+ "index.mjs",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "dependencies": {
32
+ "json-bigint": "^1.0.0",
33
+ "ws": "^8.11.0"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/eslint-parser": "^7.19.1",
37
+ "@types/node": "^18.8.3",
38
+ "eslint": "^8.25.0",
39
+ "eslint-config-airbnb-base": "^15.0.0"
40
+ },
41
+ "keywords": [
42
+ "tmc.js",
43
+ "taobao message channel"
44
+ ],
45
+ "engines": {
46
+ "node": ">=14.5"
47
+ }
48
+ }