ordered-binary 1.1.1 → 1.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.
package/README.md CHANGED
@@ -1,8 +1,34 @@
1
+ [![npm version](https://img.shields.io/npm/dw/ordered-binary)](https://www.npmjs.org/package/ordered-binary)
2
+ [![npm version](https://img.shields.io/npm/v/ordered-binary.svg?style=flat-square)](https://www.npmjs.org/package/ordered-binary)
3
+ [![license](https://img.shields.io/badge/license-MIT-brightgreen)](LICENSE)
1
4
  <a href="https://dev.doctorevidence.com/"><img src="./assets/powers-dre.png" width="203" /></a>
2
5
 
3
- The ordered-binary provides a representation of JavaScript primitives as NodeJS Buffers, such that there binary values are naturally ordered such that it matches primitive ordering. For example, since -2.0321 > -2.04, then `toBufferKey(-2.0321)` will be greater than `toBufferKey(-2.04)` as a binary representation, in left-to-right evaluation. This is particular useful for storing keys as binaries with something like LevelDB, to avoid any custom sorting.
6
+ The ordered-binary package provides a representation of JavaScript primitives, serialized into binary format (NodeJS Buffers or Uint8Arrays), such that the binary values are naturally ordered such that it matches the natural ordering or values. For example, since -2.0321 > -2.04, then `toBufferKey(-2.0321)` will be greater than `toBufferKey(-2.04)` as a binary representation, in left-to-right evaluation. This is particular useful for storing keys as binaries with something like LMDB or LevelDB, to avoid any custom sorting.
4
7
 
5
- The main module exports two functions:
8
+ The ordered-binary package supports strings, numbers, booleans, symbols, null, as well as an array of primitives. Here is an example of ordering of primitive values:
9
+ ```
10
+ Buffer.from([0]) // buffers are left unchanged, and this is the minimum value
11
+ Symbol.for('even symbols')
12
+ -10 // negative supported
13
+ -1.1 // decimals supported
14
+ 400
15
+ 3E10
16
+ 'Hello'
17
+ ['Hello', 'World']
18
+ 'World'
19
+ 'hello'
20
+ ['hello', 1, 'world']
21
+ ['hello', 'world']
22
+ Buffer.from([0xff])
23
+ ```
24
+
25
+
26
+ The main module exports these functions:
27
+
28
+ `writeKey(key: string | number | boolean | null | Array, target: Buffer, position: integer, inSequence?: boolean)` - Writes the provide key to the target buffer
29
+
30
+ `readKey(buffer, start, end, inSequence)` - Reads the key from the buffer, given the provided start and end, as a primitive value
6
31
 
7
32
  `toBufferKey(jsPrimitive)` - This accepts a string, number, or boolean as the argument, and returns a `Buffer`.
33
+
8
34
  `fromBufferKey(bufferKey, multiple)` - This accepts a Buffer and returns a JavaScript primitive value. This can also parse buffers that hold multiple values delimited by a byte `30`, by setting the second argument to true (in which case it will return an array).
package/dist/index.cjs ADDED
@@ -0,0 +1,344 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /*
6
+ control character types:
7
+ 1 - metadata
8
+ 2 - symbols
9
+ 6 - false
10
+ 7 - true
11
+ 8- 16 - negative doubles
12
+ 16-24 positive doubles
13
+ 27 - String starts with a character 27 or less or is an empty string
14
+ 0 - multipart separator
15
+ > 27 normal string characters
16
+ */
17
+ /*
18
+ * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
19
+ */
20
+
21
+ const float64Array = new Float64Array(2);
22
+ const int32Array = new Int32Array(float64Array.buffer, 0, 4);
23
+ let nullTerminate = false;
24
+ let textEncoder;
25
+ try {
26
+ textEncoder = new TextEncoder();
27
+ } catch (error) {}
28
+
29
+ /*
30
+ * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
31
+ */
32
+ function writeKey(key, target, position, inSequence) {
33
+ let targetView = target.dataView;
34
+ if (!targetView)
35
+ targetView = target.dataView = new DataView(target.buffer, target.byteOffset, ((target.byteLength + 3) >> 2) << 2);
36
+ switch (typeof key) {
37
+ case 'string':
38
+ let strLength = key.length;
39
+ let c1 = key.charCodeAt(0);
40
+ if (!(c1 >= 28)) // escape character
41
+ target[position++] = 27;
42
+ if (strLength < 0x40) {
43
+ let i, c2;
44
+ for (i = 0; i < strLength; i++) {
45
+ c1 = key.charCodeAt(i);
46
+ if (c1 < 0x80) {
47
+ target[position++] = c1;
48
+ } else if (c1 < 0x800) {
49
+ target[position++] = c1 >> 6 | 0xc0;
50
+ target[position++] = c1 & 0x3f | 0x80;
51
+ } else if (
52
+ (c1 & 0xfc00) === 0xd800 &&
53
+ ((c2 = key.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
54
+ ) {
55
+ c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff);
56
+ i++;
57
+ target[position++] = c1 >> 18 | 0xf0;
58
+ target[position++] = c1 >> 12 & 0x3f | 0x80;
59
+ target[position++] = c1 >> 6 & 0x3f | 0x80;
60
+ target[position++] = c1 & 0x3f | 0x80;
61
+ } else {
62
+ target[position++] = c1 >> 12 | 0xe0;
63
+ target[position++] = c1 >> 6 & 0x3f | 0x80;
64
+ target[position++] = c1 & 0x3f | 0x80;
65
+ }
66
+ }
67
+ } else {
68
+ if (target.utf8Write)
69
+ position += target.utf8Write(key, position);
70
+ else
71
+ position += textEncoder.encodeInto(key, target.subarray(position)).written;
72
+ if (position > target.length - 4)
73
+ throw new RangeError('String does not fit in target buffer')
74
+ }
75
+ break
76
+ case 'number':
77
+ float64Array[0] = key;
78
+ let lowInt = int32Array[0];
79
+ let highInt = int32Array[1];
80
+ let length;
81
+ if (key < 0) {
82
+ targetView.setInt32(position + 4, ~((lowInt >>> 4) | (highInt << 28)));
83
+ targetView.setInt32(position + 0, (highInt ^ 0x7fffffff) >>> 4);
84
+ targetView.setInt32(position + 8, ((lowInt & 0xf) ^ 0xf) << 4, true); // just always do the null termination here
85
+ return position + 9
86
+ } else if ((lowInt & 0xf) || inSequence) {
87
+ length = 9;
88
+ } else if (lowInt & 0xfffff)
89
+ length = 8;
90
+ else if (lowInt || (highInt & 0xf))
91
+ length = 6;
92
+ else
93
+ length = 4;
94
+ // switching order to go to little endian
95
+ targetView.setInt32(position + 0, (highInt >>> 4) | 0x10000000);
96
+ targetView.setInt32(position + 4, (lowInt >>> 4) | (highInt << 28));
97
+ // if (length == 9 || nullTerminate)
98
+ targetView.setInt32(position + 8, (lowInt & 0xf) << 4, true);
99
+ return position + length;
100
+ case 'object':
101
+ if (key) {
102
+ if (key instanceof Array) {
103
+ for (let i = 0, l = key.length; i < l; i++) {
104
+ if (i > 0)
105
+ target[position++] = 0;
106
+ position = writeKey(key[i], target, position, true);
107
+ }
108
+ break
109
+ } else if (key instanceof Uint8Array) {
110
+ target.set(key, position);
111
+ position += key.length;
112
+ break
113
+ } else {
114
+ throw new Error('Unable to serialize object as a key')
115
+ }
116
+ } else // null
117
+ target[position++] = 0;
118
+ break
119
+ case 'boolean':
120
+ targetView.setUint32(position++, key ? 7 : 6, true);
121
+ return position
122
+ case 'bigint':
123
+ return writeKey(Number(key), target, position, inSequence)
124
+ case 'undefined':
125
+ return position
126
+ // undefined is interpreted as the absence of a key, signified by zero length
127
+ case 'symbol':
128
+ target[position++] = 2;
129
+ return writeKey(key.description, target, position, inSequence)
130
+ default:
131
+ throw new Error('Can not serialize key of type ' + typeof key)
132
+ }
133
+ if (nullTerminate && !inSequence)
134
+ targetView.setUint32(position, 0);
135
+ return position
136
+ }
137
+
138
+ let position;
139
+ function readKey(buffer, start, end, inSequence) {
140
+ buffer[end] = 0; // make sure it is null terminated
141
+ position = start;
142
+ let controlByte = buffer[position];
143
+ let value;
144
+ if (controlByte < 24) {
145
+ if (controlByte < 8) {
146
+ position++;
147
+ if (controlByte == 6) {
148
+ value = false;
149
+ } else if (controlByte == 7) {
150
+ value = true;
151
+ } else if (controlByte == 0) {
152
+ value = null;
153
+ } else if (controlByte == 2) {
154
+ value = Symbol.for(readString(buffer));
155
+ } else
156
+ return Uint8Array.prototype.slice.call(buffer, start, end)
157
+ } else {
158
+ let dataView = buffer.dataView || (buffer.dataView = new DataView(buffer.buffer, buffer.byteOffset, ((buffer.byteLength + 3) >> 2) << 2));
159
+ let highInt = dataView.getInt32(position) << 4;
160
+ let size = end - position;
161
+ let lowInt;
162
+ if (size > 4) {
163
+ lowInt = dataView.getInt32(position + 4);
164
+ highInt |= lowInt >>> 28;
165
+ if (size <= 6) { // clear the last bits
166
+ lowInt &= -0x1000;
167
+ }
168
+ lowInt = lowInt << 4;
169
+ if (size > 8) {
170
+ lowInt = lowInt | buffer[position + 8] >> 4;
171
+ }
172
+ } else
173
+ lowInt = 0;
174
+ if (controlByte < 16) {
175
+ // negative gets negated
176
+ highInt = highInt ^ 0x7fffffff;
177
+ lowInt = ~lowInt;
178
+ }
179
+ int32Array[1] = highInt;
180
+ int32Array[0] = lowInt;
181
+ value = float64Array[0];
182
+ position += 9;
183
+ }
184
+ } else {
185
+ if (controlByte == 27) {
186
+ position++;
187
+ }
188
+ value = readString(buffer);
189
+ /*let strStart = position
190
+ let strEnd = end
191
+ for (; position < end; position++) {
192
+ if (buffer[position] == 0) {
193
+ break
194
+ }
195
+ }
196
+ value = buffer.toString('utf8', strStart, position++)*/
197
+ }
198
+ while (position < end) {
199
+ if (buffer[position] === 0)
200
+ position++;
201
+ if (inSequence) {
202
+ encoder.position = position;
203
+ return value
204
+ }
205
+ let nextValue = readKey(buffer, position, end, true);
206
+ if (value instanceof Array) {
207
+ value.push(nextValue);
208
+ } else
209
+ value = [ value, nextValue ];
210
+ }
211
+ return value
212
+ }
213
+ const encoder = {
214
+ writeKey,
215
+ readKey,
216
+ };
217
+ const toBufferKey = (key) => {
218
+ let buffer = Buffer.alloc(2048);
219
+ return buffer.slice(0, writeKey(key, buffer, 0, 2048) + 1)
220
+ };
221
+ const fromBufferKey = (sourceBuffer) => {
222
+ return readKey(sourceBuffer, 0, sourceBuffer.length - 1)
223
+ };
224
+ const fromCharCode = String.fromCharCode;
225
+ function makeStringBuilder() {
226
+ let stringBuildCode = '(source) => {';
227
+ let previous = [];
228
+ for (let i = 0; i < 0x30; i++) {
229
+ let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97);
230
+ stringBuildCode += `
231
+ let ${v} = source[position++]
232
+ if (${v} === 0)
233
+ return fromCharCode(${previous})
234
+ else if (${v} >= 0x80)
235
+ ${v} = finishUtf8(${v}, source)
236
+ `;
237
+ previous.push(v);
238
+ if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8
239
+ finishUtf8();
240
+ }
241
+ stringBuildCode += `return fromCharCode(${previous}) + readString(source)}`;
242
+ return stringBuildCode
243
+ }
244
+
245
+ let pendingSurrogate;
246
+ function finishUtf8(byte1, src) {
247
+ if ((byte1 & 0xe0) === 0xc0) {
248
+ // 2 bytes
249
+ const byte2 = src[position++] & 0x3f;
250
+ return ((byte1 & 0x1f) << 6) | byte2
251
+ } else if ((byte1 & 0xf0) === 0xe0) {
252
+ // 3 bytes
253
+ const byte2 = src[position++] & 0x3f;
254
+ const byte3 = src[position++] & 0x3f;
255
+ return ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3
256
+ } else if ((byte1 & 0xf8) === 0xf0) {
257
+ // 4 bytes
258
+ if (pendingSurrogate) {
259
+ byte1 = pendingSurrogate;
260
+ pendingSurrogate = null;
261
+ position += 3;
262
+ return byte1
263
+ }
264
+ const byte2 = src[position++] & 0x3f;
265
+ const byte3 = src[position++] & 0x3f;
266
+ const byte4 = src[position++] & 0x3f;
267
+ let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
268
+ if (unit > 0xffff) {
269
+ unit -= 0x10000;
270
+ unit = 0xdc00 | (unit & 0x3ff);
271
+ pendingSurrogate = ((unit >>> 10) & 0x3ff) | 0xd800;
272
+ position -= 4; // reset so we can return the next part of the surrogate pair
273
+ }
274
+ return unit
275
+ } else {
276
+ return byte1
277
+ }
278
+ }
279
+
280
+ const enableNullTermination = () => nullTerminate = true;
281
+
282
+ const readString = eval(makeStringBuilder());
283
+
284
+ function compareKeys(a, b) {
285
+ // compare with type consistency that matches binary comparison
286
+ if (typeof a == 'object') {
287
+ if (!a) {
288
+ return b == null ? 0 : -1
289
+ }
290
+ if (a.compare) {
291
+ if (b == null) {
292
+ return 1
293
+ } else if (b.compare) {
294
+ return a.compare(b)
295
+ } else {
296
+ return -1
297
+ }
298
+ }
299
+ let arrayComparison;
300
+ if (b instanceof Array) {
301
+ let i = 0;
302
+ while((arrayComparison = compareKeys(a[i], b[i])) == 0 && i <= a.length) {
303
+ i++;
304
+ }
305
+ return arrayComparison
306
+ }
307
+ arrayComparison = compareKeys(a[0], b);
308
+ if (arrayComparison == 0 && a.length > 1)
309
+ return 1
310
+ return arrayComparison
311
+ } else if (typeof a == typeof b) {
312
+ if (typeof a === 'symbol') {
313
+ a = Symbol.keyFor(a);
314
+ b = Symbol.keyFor(b);
315
+ }
316
+ return a < b ? -1 : a === b ? 0 : 1
317
+ }
318
+ else if (typeof b == 'object') {
319
+ if (b instanceof Array)
320
+ return -compareKeys(b, a)
321
+ return 1
322
+ } else {
323
+ return typeOrder[typeof a] < typeOrder[typeof b] ? -1 : 1
324
+ }
325
+ }
326
+ const typeOrder = {
327
+ symbol: 0,
328
+ undefined: 1,
329
+ boolean: 2,
330
+ number: 3,
331
+ string: 4
332
+ };
333
+ const MINIMUM_KEY = null;
334
+ const MAXIMUM_KEY = Buffer.from([0xff]);
335
+
336
+ exports.MAXIMUM_KEY = MAXIMUM_KEY;
337
+ exports.MINIMUM_KEY = MINIMUM_KEY;
338
+ exports.compareKeys = compareKeys;
339
+ exports.enableNullTermination = enableNullTermination;
340
+ exports.encoder = encoder;
341
+ exports.fromBufferKey = fromBufferKey;
342
+ exports.readKey = readKey;
343
+ exports.toBufferKey = toBufferKey;
344
+ exports.writeKey = writeKey;
package/index.js CHANGED
@@ -19,10 +19,15 @@ const int32Array = new Int32Array(float64Array.buffer, 0, 4)
19
19
  const uint8Array6 = new Uint8Array(float64Array.buffer, 2, 6)
20
20
  const uint8Array8 = new Uint8Array(float64Array.buffer, 0, 8)
21
21
  let nullTerminate = false
22
+ let textEncoder
23
+ try {
24
+ textEncoder = new TextEncoder()
25
+ } catch (error) {}
26
+
22
27
  /*
23
28
  * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
24
29
  */
25
- function writeKey(key, target, position, inSequence) {
30
+ export function writeKey(key, target, position, inSequence) {
26
31
  let targetView = target.dataView
27
32
  if (!targetView)
28
33
  targetView = target.dataView = new DataView(target.buffer, target.byteOffset, ((target.byteLength + 3) >> 2) << 2)
@@ -30,9 +35,9 @@ function writeKey(key, target, position, inSequence) {
30
35
  case 'string':
31
36
  let strLength = key.length
32
37
  let c1 = key.charCodeAt(0)
33
- if (c1 < 28) // escape character
38
+ if (!(c1 >= 28)) // escape character
34
39
  target[position++] = 27
35
- if (strLength < 0x20) {
40
+ if (strLength < 0x40) {
36
41
  let i, c2
37
42
  for (i = 0; i < strLength; i++) {
38
43
  c1 = key.charCodeAt(i)
@@ -58,7 +63,12 @@ function writeKey(key, target, position, inSequence) {
58
63
  }
59
64
  }
60
65
  } else {
61
- position += target.utf8Write(key, position, 2000)
66
+ if (target.utf8Write)
67
+ position += target.utf8Write(key, position)
68
+ else
69
+ position += textEncoder.encodeInto(key, target.subarray(position)).written
70
+ if (position > target.length - 4)
71
+ throw new RangeError('String does not fit in target buffer')
62
72
  }
63
73
  break
64
74
  case 'number':
@@ -124,7 +134,7 @@ function writeKey(key, target, position, inSequence) {
124
134
  }
125
135
 
126
136
  let position
127
- function readKey(buffer, start, end, inSequence) {
137
+ export function readKey(buffer, start, end, inSequence) {
128
138
  buffer[end] = 0 // make sure it is null terminated
129
139
  position = start
130
140
  let controlByte = buffer[position]
@@ -187,7 +197,7 @@ function readKey(buffer, start, end, inSequence) {
187
197
  if (buffer[position] === 0)
188
198
  position++
189
199
  if (inSequence) {
190
- exports.position = position
200
+ encoder.position = position
191
201
  return value
192
202
  }
193
203
  let nextValue = readKey(buffer, position, end, true)
@@ -198,13 +208,15 @@ function readKey(buffer, start, end, inSequence) {
198
208
  }
199
209
  return value
200
210
  }
201
- exports.writeKey = writeKey
202
- exports.readKey = readKey
203
- exports.toBufferKey = (key) => {
211
+ export const encoder = {
212
+ writeKey,
213
+ readKey,
214
+ }
215
+ export const toBufferKey = (key) => {
204
216
  let buffer = Buffer.alloc(2048)
205
217
  return buffer.slice(0, writeKey(key, buffer, 0, 2048) + 1)
206
218
  }
207
- exports.fromBufferKey = (sourceBuffer) => {
219
+ export const fromBufferKey = (sourceBuffer) => {
208
220
  return readKey(sourceBuffer, 0, sourceBuffer.length - 1)
209
221
  }
210
222
  const fromCharCode = String.fromCharCode
@@ -212,15 +224,17 @@ function makeStringBuilder() {
212
224
  let stringBuildCode = '(source) => {'
213
225
  let previous = []
214
226
  for (let i = 0; i < 0x30; i++) {
215
- v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97)
216
- stringBuildCode += `
217
- let ${v} = source[position++]
218
- if (${v} === 0)
219
- return fromCharCode(${previous})
220
- else if (${v} >= 0x80)
221
- ${v} = finishUtf8(${v}, source)
222
- `
223
- previous.push(v)
227
+ let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97)
228
+ stringBuildCode += `
229
+ let ${v} = source[position++]
230
+ if (${v} === 0)
231
+ return fromCharCode(${previous})
232
+ else if (${v} >= 0x80)
233
+ ${v} = finishUtf8(${v}, source)
234
+ `
235
+ previous.push(v)
236
+ if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8
237
+ finishUtf8()
224
238
  }
225
239
  stringBuildCode += `return fromCharCode(${previous}) + readString(source)}`
226
240
  return stringBuildCode
@@ -261,12 +275,12 @@ function finishUtf8(byte1, src) {
261
275
  }
262
276
  }
263
277
 
264
- exports.enableNullTermination = () => nullTerminate = true
278
+ export const enableNullTermination = () => nullTerminate = true
265
279
 
266
280
  const readString = eval(makeStringBuilder())
267
281
 
268
- function compareKeys(a, b) {
269
- // compare with type consistency that matches ordered-binary
282
+ export function compareKeys(a, b) {
283
+ // compare with type consistency that matches binary comparison
270
284
  if (typeof a == 'object') {
271
285
  if (!a) {
272
286
  return b == null ? 0 : -1
@@ -307,7 +321,6 @@ function compareKeys(a, b) {
307
321
  return typeOrder[typeof a] < typeOrder[typeof b] ? -1 : 1
308
322
  }
309
323
  }
310
- exports.compareKeys = compareKeys
311
324
  const typeOrder = {
312
325
  symbol: 0,
313
326
  undefined: 1,
@@ -315,3 +328,5 @@ const typeOrder = {
315
328
  number: 3,
316
329
  string: 4
317
330
  }
331
+ export const MINIMUM_KEY = null
332
+ export const MAXIMUM_KEY = Buffer.from([0xff])
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ordered-binary",
3
3
  "author": "Kris Zyp",
4
- "version": "1.1.1",
4
+ "version": "1.2.1",
5
5
  "description": "Conversion of JavaScript primitives to and from Buffer with binary order matching natural primitive order",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -9,15 +9,28 @@
9
9
  "url": "http://github.com/kriszyp/ordered-binary"
10
10
  },
11
11
  "scripts": {
12
+ "build": "rollup -c",
13
+ "prepare": "rollup -c",
12
14
  "test": "mocha tests -u tdd"
13
15
  },
14
- "main": "./index.js",
16
+ "type": "module",
17
+ "module": "index.js",
18
+ "exports": {
19
+ ".": {
20
+ "require": "./dist/index.cjs",
21
+ "import": "./index.js"
22
+ },
23
+ "./index.js": {
24
+ "require": "./dist/index.cjs",
25
+ "import": "./index.js"
26
+ }
27
+ },
15
28
  "typings": "./index.d.ts",
16
- "dependencies": {},
17
29
  "optionalDependencies": {},
18
30
  "devDependencies": {
19
- "mocha": "^4",
31
+ "@types/node": "latest",
20
32
  "chai": "^4",
21
- "@types/node": "latest"
33
+ "mocha": "^8.1.3",
34
+ "rollup": "^1.20.3"
22
35
  }
23
36
  }
@@ -0,0 +1,11 @@
1
+ export default [
2
+ {
3
+ input: "index.js",
4
+ output: [
5
+ {
6
+ file: "dist/index.cjs",
7
+ format: "cjs"
8
+ }
9
+ ]
10
+ }
11
+ ];
package/tests/test.js CHANGED
@@ -1,6 +1,6 @@
1
- const { assert } = require('chai')
1
+ import { assert } from 'chai'
2
2
 
3
- const { toBufferKey, fromBufferKey, readKey, writeKey } = require('../index')
3
+ import { toBufferKey, fromBufferKey, readKey, writeKey } from '../index.js'
4
4
 
5
5
  function assertBufferComparison(lesser, greater) {
6
6
  for (let i = 0; i < lesser.length; i++) {
@@ -43,6 +43,7 @@ suite('key buffers', () => {
43
43
  test('string equivalence', () => {
44
44
  assert.strictEqual(fromBufferKey(toBufferKey('4')), '4')
45
45
  assert.strictEqual(fromBufferKey(toBufferKey('hello')), 'hello')
46
+ assert.strictEqual(fromBufferKey(toBufferKey('')), '')
46
47
  })
47
48
  test('string comparison', () => {
48
49
  assertBufferComparison(toBufferKey('4'), toBufferKey('5'))