memx 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/LICENSE.md +211 -0
- package/NOTICE.md +13 -0
- package/README.md +1 -0
- package/dist/index.js +1147 -0
- package/dist/index.js.map +6 -0
- package/dist/index.mjs +1124 -0
- package/dist/index.mjs.map +6 -0
- package/index.d.ts +623 -0
- package/package.json +48 -0
- package/src/buffers.ts +27 -0
- package/src/client.ts +228 -0
- package/src/cluster.ts +139 -0
- package/src/connection.ts +191 -0
- package/src/constants.ts +103 -0
- package/src/decode.ts +95 -0
- package/src/encode.ts +81 -0
- package/src/index.ts +16 -0
- package/src/internals.ts +41 -0
- package/src/server.ts +489 -0
- package/src/types.ts +263 -0
package/src/constants.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export const EMPTY_BUFFER = Buffer.alloc(0)
|
|
2
|
+
|
|
3
|
+
export enum FLAGS {
|
|
4
|
+
// by default everything is a "buffer"
|
|
5
|
+
BUFFER = 0x00000000,
|
|
6
|
+
|
|
7
|
+
// from "typeof" we get bigint, boolean, number and string
|
|
8
|
+
BIGINT = 0xCACA0000,
|
|
9
|
+
BOOLEAN = 0xCACA0001,
|
|
10
|
+
NUMBER = 0xCACA0002,
|
|
11
|
+
STRING = 0xCACA0003,
|
|
12
|
+
|
|
13
|
+
// typeof null === object
|
|
14
|
+
NULL = 0xCACA000E,
|
|
15
|
+
|
|
16
|
+
// objects become JSON
|
|
17
|
+
JSON = 0xCACA000F,
|
|
18
|
+
|
|
19
|
+
// typed arrays
|
|
20
|
+
UINT8ARRAY = 0xCACA0010,
|
|
21
|
+
UINT8CLAMPEDARRAY = 0xCACA0011,
|
|
22
|
+
UINT16ARRAY = 0xCACA0012,
|
|
23
|
+
UINT32ARRAY = 0xCACA0013,
|
|
24
|
+
INT8ARRAY = 0xCACA0014,
|
|
25
|
+
INT16ARRAY = 0xCACA0015,
|
|
26
|
+
INT32ARRAY = 0xCACA0016,
|
|
27
|
+
BIGUINT64ARRAY = 0xCACA0017,
|
|
28
|
+
BIGINT64ARRAY = 0xCACA0018,
|
|
29
|
+
FLOAT32ARRAY = 0xCACA0019,
|
|
30
|
+
FLOAT64ARRAY = 0xCACA001A,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export enum BUFFERS {
|
|
34
|
+
POOL_SIZE = 64,
|
|
35
|
+
BUFFER_SIZE = 8192,
|
|
36
|
+
HEADER_SIZE = 24,
|
|
37
|
+
KEY_SIZE = 250,
|
|
38
|
+
KEY_TOO_BIG = 251,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export enum OFFSETS {
|
|
42
|
+
MAGIC_$8 = 0,
|
|
43
|
+
OPCODE_$8 = 1,
|
|
44
|
+
KEY_LENGTH_$16 = 2,
|
|
45
|
+
EXTRAS_LENGTH_$8 = 4,
|
|
46
|
+
DATA_TYPE_$8 = 5,
|
|
47
|
+
STATUS_$16 = 6,
|
|
48
|
+
BODY_LENGTH_$32 = 8,
|
|
49
|
+
SEQUENCE_$32 = 12,
|
|
50
|
+
CAS_$64 = 16,
|
|
51
|
+
BODY = 24,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export enum MAGIC {
|
|
55
|
+
REQUEST = 0x80, // Request packet for this protocol version
|
|
56
|
+
RESPONSE = 0x81, // Response packet for this protocol version
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export enum STATUS {
|
|
60
|
+
OK = 0x0000, // No error
|
|
61
|
+
KEY_NOT_FOUND = 0x0001, // Key not found
|
|
62
|
+
KEY_EXISTS = 0x0002, // Key exists
|
|
63
|
+
TOO_LARGE = 0x0003, // Value too large
|
|
64
|
+
INVALID_ARGS = 0x0004, // Invalid arguments
|
|
65
|
+
ITEM_NOT_STORED = 0x0005, // Item not stored
|
|
66
|
+
NON_NUMERIC_VALUE = 0x0006, // Incr/Decr on non-numeric value
|
|
67
|
+
WRONG_VBUCKET = 0x0007, // The vbucket belongs to another server
|
|
68
|
+
AUTH_ERROR = 0x0008, // Authentication error
|
|
69
|
+
AUTH_CONTINUE = 0x0009, // Authentication continue
|
|
70
|
+
UNKNOWN_COMMAND = 0x0081, // Unknown command
|
|
71
|
+
OUT_OF_MEMORY = 0x0082, // Out of memory
|
|
72
|
+
NOT_SUPPORTED = 0x0083, // Not supported
|
|
73
|
+
INTERNAL_ERROR = 0x0084, // Internal error
|
|
74
|
+
BUSY = 0x0085, // Busy
|
|
75
|
+
TEMPORARY_FAILURE = 0x0086, // Temporary failure
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export enum OPCODE {
|
|
79
|
+
GET = 0x00,
|
|
80
|
+
SET = 0x01,
|
|
81
|
+
ADD = 0x02,
|
|
82
|
+
REPLACE = 0x03,
|
|
83
|
+
DELETE = 0x04,
|
|
84
|
+
INCREMENT = 0x05,
|
|
85
|
+
DECREMENT = 0x06,
|
|
86
|
+
QUIT = 0x07,
|
|
87
|
+
FLUSH = 0x08,
|
|
88
|
+
NOOP = 0x0a,
|
|
89
|
+
VERSION = 0x0b,
|
|
90
|
+
APPEND = 0x0e,
|
|
91
|
+
PREPEND = 0x0f,
|
|
92
|
+
STAT = 0x10,
|
|
93
|
+
TOUCH = 0x1c,
|
|
94
|
+
GAT = 0x1d,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export enum DATA_TYPE {
|
|
98
|
+
RAW = 0x00,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export enum VBUCKET {
|
|
102
|
+
NIL = 0x00,
|
|
103
|
+
}
|
package/src/decode.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { DATA_TYPE, EMPTY_BUFFER, MAGIC, OFFSETS } from './constants'
|
|
2
|
+
import assert from 'assert'
|
|
3
|
+
import { allocateBuffer, RecyclableBuffer } from './buffers'
|
|
4
|
+
|
|
5
|
+
// Reading the full header would be like
|
|
6
|
+
// const magic = header.readUInt8 (0)
|
|
7
|
+
// const opcode = header.readUInt8 (1)
|
|
8
|
+
// const key_length = header.readUInt16BE (2)
|
|
9
|
+
// const extras_length = header.readUInt8 (4)
|
|
10
|
+
// const data_type = header.readUInt8 (5)
|
|
11
|
+
// const status = header.readUInt16BE (6)
|
|
12
|
+
// const body_length = header.readUInt32BE (8)
|
|
13
|
+
// const sequence = header.readUInt32BE (12)
|
|
14
|
+
// const cas = header.readBigUInt64BE (16)
|
|
15
|
+
|
|
16
|
+
export interface RawIncomingPacket {
|
|
17
|
+
readonly opcode: number
|
|
18
|
+
readonly status: number
|
|
19
|
+
readonly sequence: number
|
|
20
|
+
readonly cas: bigint
|
|
21
|
+
readonly extras: Buffer
|
|
22
|
+
readonly key: Buffer
|
|
23
|
+
readonly value: Buffer
|
|
24
|
+
recycle: () => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Decoder {
|
|
28
|
+
#consumer: (packet: RawIncomingPacket) => void
|
|
29
|
+
#header = Buffer.allocUnsafeSlow(24)
|
|
30
|
+
#body?: RecyclableBuffer
|
|
31
|
+
#pos = 0
|
|
32
|
+
|
|
33
|
+
constructor(consumer: (packet: RawIncomingPacket) => void) {
|
|
34
|
+
this.#consumer = consumer
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
append(buffer: Buffer, start: number, end: number): void {
|
|
38
|
+
const header = this.#header
|
|
39
|
+
|
|
40
|
+
while (start < end) {
|
|
41
|
+
if (this.#pos < 24) {
|
|
42
|
+
const copied = buffer.copy(header, this.#pos, start, end)
|
|
43
|
+
this.#pos += copied
|
|
44
|
+
start += copied
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (this.#pos < 24) return
|
|
48
|
+
|
|
49
|
+
let extras = EMPTY_BUFFER
|
|
50
|
+
let key = EMPTY_BUFFER
|
|
51
|
+
let value = EMPTY_BUFFER
|
|
52
|
+
let recycle = () => void 0
|
|
53
|
+
|
|
54
|
+
const bodyLength = header.readUInt32BE(OFFSETS.BODY_LENGTH_$32)
|
|
55
|
+
if (bodyLength) {
|
|
56
|
+
const body = this.#body || (this.#body = allocateBuffer(bodyLength))
|
|
57
|
+
|
|
58
|
+
const copied = buffer.copy(body, this.#pos - 24, start, end)
|
|
59
|
+
this.#pos += copied
|
|
60
|
+
start += copied
|
|
61
|
+
|
|
62
|
+
if (this.#pos - 24 < body.length) return
|
|
63
|
+
|
|
64
|
+
const keyLength = header.readUInt16BE(OFFSETS.KEY_LENGTH_$16)
|
|
65
|
+
const extrasLength = header.readUInt8(OFFSETS.EXTRAS_LENGTH_$8)
|
|
66
|
+
const valueLength = bodyLength - keyLength - extrasLength
|
|
67
|
+
|
|
68
|
+
key = keyLength ? body.subarray(extrasLength, extrasLength + keyLength) : EMPTY_BUFFER
|
|
69
|
+
value = valueLength ? body.subarray(extrasLength + keyLength) : EMPTY_BUFFER
|
|
70
|
+
extras = extrasLength ? body.subarray(0, extrasLength) : EMPTY_BUFFER
|
|
71
|
+
recycle = () => void body.recycle()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const packet: RawIncomingPacket = {
|
|
75
|
+
opcode: header.readUInt8(OFFSETS.OPCODE_$8),
|
|
76
|
+
status: header.readUInt16BE(OFFSETS.STATUS_$16),
|
|
77
|
+
sequence: header.readUInt32BE(OFFSETS.SEQUENCE_$32),
|
|
78
|
+
cas: header.readBigUInt64BE(OFFSETS.CAS_$64),
|
|
79
|
+
extras,
|
|
80
|
+
key,
|
|
81
|
+
value,
|
|
82
|
+
recycle,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const magic = header.readUInt8(OFFSETS.MAGIC_$8)
|
|
86
|
+
const dataType = header.readUInt8(OFFSETS.DATA_TYPE_$8)
|
|
87
|
+
assert.equal(magic, MAGIC.RESPONSE, 'Invalid magic in header')
|
|
88
|
+
assert.equal(dataType, DATA_TYPE.RAW, 'Invalid data type in header')
|
|
89
|
+
|
|
90
|
+
this.#pos = 0
|
|
91
|
+
this.#body = undefined
|
|
92
|
+
this.#consumer(packet)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/encode.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { allocateBuffer, RecyclableBuffer } from './buffers'
|
|
2
|
+
import { DATA_TYPE, EMPTY_BUFFER, MAGIC, VBUCKET, OPCODE, OFFSETS } from './constants'
|
|
3
|
+
|
|
4
|
+
// Writing the full header would be like
|
|
5
|
+
// header.writeUInt8 (magic, 0)
|
|
6
|
+
// header.writeUInt8 (opcode, 1)
|
|
7
|
+
// header.writeUInt16BE (key_length, 2)
|
|
8
|
+
// header.writeUInt8 (extras_length, 4)
|
|
9
|
+
// header.writeUInt8 (data_type, 5)
|
|
10
|
+
// header.writeUInt16BE (status, 6)
|
|
11
|
+
// header.writeUInt32BE (body_length, 8)
|
|
12
|
+
// header.writeUInt32BE (sequence, 12)
|
|
13
|
+
// header.writeBigUInt64BE (cas, 16)
|
|
14
|
+
|
|
15
|
+
// keep as an "enum", the compiler will replace with values
|
|
16
|
+
enum CONSTANTS {
|
|
17
|
+
HEADER_SIZE = 24, // header size
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RawOutgoingPacket {
|
|
21
|
+
readonly opcode: OPCODE
|
|
22
|
+
readonly sequence?: number
|
|
23
|
+
readonly cas?: bigint
|
|
24
|
+
|
|
25
|
+
readonly extras?: Buffer
|
|
26
|
+
readonly extrasOffset?: number
|
|
27
|
+
readonly extrasLength?: number
|
|
28
|
+
|
|
29
|
+
readonly key?: Buffer
|
|
30
|
+
readonly keyOffset?: number
|
|
31
|
+
readonly keyLength?: number
|
|
32
|
+
|
|
33
|
+
readonly value?: Buffer
|
|
34
|
+
readonly valueOffset?: number
|
|
35
|
+
readonly valueLength?: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class Encoder {
|
|
39
|
+
encode(packet: RawOutgoingPacket, seq: number = 0): RecyclableBuffer {
|
|
40
|
+
const {
|
|
41
|
+
opcode,
|
|
42
|
+
sequence = seq,
|
|
43
|
+
cas = 0n,
|
|
44
|
+
|
|
45
|
+
extras = EMPTY_BUFFER,
|
|
46
|
+
extrasOffset = 0,
|
|
47
|
+
extrasLength = extras.length,
|
|
48
|
+
|
|
49
|
+
key = EMPTY_BUFFER,
|
|
50
|
+
keyOffset = 0,
|
|
51
|
+
keyLength = key.length,
|
|
52
|
+
|
|
53
|
+
value = EMPTY_BUFFER,
|
|
54
|
+
valueOffset = 0,
|
|
55
|
+
valueLength = value.length,
|
|
56
|
+
} = packet
|
|
57
|
+
|
|
58
|
+
const bodyLength = extrasLength + keyLength + valueLength
|
|
59
|
+
const length = bodyLength + CONSTANTS.HEADER_SIZE
|
|
60
|
+
|
|
61
|
+
const buffer = allocateBuffer(length) // /* length <= buffer.length ? buffer : */ Buffer.allocUnsafe(length)
|
|
62
|
+
|
|
63
|
+
buffer.writeUInt8(MAGIC.REQUEST, OFFSETS.MAGIC_$8)
|
|
64
|
+
buffer.writeUInt8(opcode, OFFSETS.OPCODE_$8)
|
|
65
|
+
buffer.writeUInt16BE(keyLength, OFFSETS.KEY_LENGTH_$16)
|
|
66
|
+
buffer.writeUInt8(extrasLength, OFFSETS.EXTRAS_LENGTH_$8)
|
|
67
|
+
buffer.writeUInt8(DATA_TYPE.RAW, OFFSETS.DATA_TYPE_$8)
|
|
68
|
+
buffer.writeUInt16BE(VBUCKET.NIL, OFFSETS.STATUS_$16)
|
|
69
|
+
buffer.writeUInt32BE(bodyLength, OFFSETS.BODY_LENGTH_$32)
|
|
70
|
+
buffer.writeUInt32BE(sequence, OFFSETS.SEQUENCE_$32)
|
|
71
|
+
buffer.writeBigUInt64BE(cas, OFFSETS.CAS_$64)
|
|
72
|
+
|
|
73
|
+
let pos = 24
|
|
74
|
+
if (extrasLength) pos += extras.copy(buffer, pos, extrasOffset, extrasOffset + extrasLength)
|
|
75
|
+
if (keyLength) pos += key.copy(buffer, pos, keyOffset, keyOffset + keyLength)
|
|
76
|
+
if (valueLength) pos += value.copy(buffer, pos, valueOffset, valueOffset + valueLength)
|
|
77
|
+
void pos
|
|
78
|
+
|
|
79
|
+
return buffer // .length === length ? buffer : buffer.subarray(0, length)
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as decode from './decode'
|
|
2
|
+
import * as encode from './encode'
|
|
3
|
+
import * as constants from './constants'
|
|
4
|
+
import * as connection from './connection'
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
decode,
|
|
8
|
+
encode,
|
|
9
|
+
constants,
|
|
10
|
+
connection,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export * from './types'
|
|
14
|
+
export * from './cluster'
|
|
15
|
+
export * from './server'
|
|
16
|
+
export * from './client'
|
package/src/internals.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Keep these here: they must be ignored by Istanbul, but ESBUILD swallows
|
|
2
|
+
// commments (for a reason: https://github.com/evanw/esbuild/issues/578)
|
|
3
|
+
|
|
4
|
+
import assert from 'assert'
|
|
5
|
+
import type { Socket } from 'net'
|
|
6
|
+
import {
|
|
7
|
+
isUint8Array,
|
|
8
|
+
isUint8ClampedArray,
|
|
9
|
+
isUint16Array,
|
|
10
|
+
isUint32Array,
|
|
11
|
+
isInt8Array,
|
|
12
|
+
isInt16Array,
|
|
13
|
+
isInt32Array,
|
|
14
|
+
isBigUint64Array,
|
|
15
|
+
isBigInt64Array,
|
|
16
|
+
isFloat32Array,
|
|
17
|
+
isFloat64Array,
|
|
18
|
+
} from 'util/types'
|
|
19
|
+
|
|
20
|
+
import { FLAGS } from './constants'
|
|
21
|
+
|
|
22
|
+
export const socketFinalizationRegistry = new FinalizationRegistry((socket: Socket): void => {
|
|
23
|
+
if (! socket.destroyed) socket.destroy()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export function typedArrayFlags(value: NodeJS.TypedArray): FLAGS {
|
|
27
|
+
const flags =
|
|
28
|
+
isUint8Array(value) ? FLAGS.UINT8ARRAY :
|
|
29
|
+
isUint8ClampedArray(value) ? FLAGS.UINT8CLAMPEDARRAY :
|
|
30
|
+
isUint16Array(value) ? FLAGS.UINT16ARRAY :
|
|
31
|
+
isUint32Array(value) ? FLAGS.UINT32ARRAY :
|
|
32
|
+
isInt8Array(value) ? FLAGS.INT8ARRAY :
|
|
33
|
+
isInt16Array(value) ? FLAGS.INT16ARRAY :
|
|
34
|
+
isInt32Array(value) ? FLAGS.INT32ARRAY :
|
|
35
|
+
isBigUint64Array(value) ? FLAGS.BIGUINT64ARRAY :
|
|
36
|
+
isBigInt64Array(value) ? FLAGS.BIGINT64ARRAY :
|
|
37
|
+
isFloat32Array(value) ? FLAGS.FLOAT32ARRAY :
|
|
38
|
+
isFloat64Array(value) ? FLAGS.FLOAT64ARRAY :
|
|
39
|
+
assert.fail('Unsupported kind of TypedArray')
|
|
40
|
+
return flags
|
|
41
|
+
}
|