ordered-binary 1.1.2 → 1.2.2

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 CHANGED
@@ -20,7 +20,14 @@ control character types:
20
20
 
21
21
  const float64Array = new Float64Array(2);
22
22
  const int32Array = new Int32Array(float64Array.buffer, 0, 4);
23
+ new Uint8Array(float64Array.buffer, 2, 6);
24
+ new Uint8Array(float64Array.buffer, 0, 8);
23
25
  let nullTerminate = false;
26
+ let textEncoder;
27
+ try {
28
+ textEncoder = new TextEncoder();
29
+ } catch (error) {}
30
+
24
31
  /*
25
32
  * Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
26
33
  */
@@ -32,9 +39,9 @@ function writeKey(key, target, position, inSequence) {
32
39
  case 'string':
33
40
  let strLength = key.length;
34
41
  let c1 = key.charCodeAt(0);
35
- if (c1 < 28) // escape character
42
+ if (!(c1 >= 28)) // escape character
36
43
  target[position++] = 27;
37
- if (strLength < 0x20) {
44
+ if (strLength < 0x40) {
38
45
  let i, c2;
39
46
  for (i = 0; i < strLength; i++) {
40
47
  c1 = key.charCodeAt(i);
@@ -60,7 +67,12 @@ function writeKey(key, target, position, inSequence) {
60
67
  }
61
68
  }
62
69
  } else {
63
- position += target.utf8Write(key, position, 2000);
70
+ if (target.utf8Write)
71
+ position += target.utf8Write(key, position);
72
+ else
73
+ position += textEncoder.encodeInto(key, target.subarray(position)).written;
74
+ if (position > target.length - 4)
75
+ throw new RangeError('String does not fit in target buffer')
64
76
  }
65
77
  break
66
78
  case 'number':
@@ -200,9 +212,12 @@ function readKey(buffer, start, end, inSequence) {
200
212
  }
201
213
  return value
202
214
  }
215
+ const enableNullTermination = () => nullTerminate = true;
216
+
203
217
  const encoder = {
204
218
  writeKey,
205
219
  readKey,
220
+ enableNullTermination,
206
221
  };
207
222
  const toBufferKey = (key) => {
208
223
  let buffer = Buffer.alloc(2048);
@@ -216,26 +231,61 @@ function makeStringBuilder() {
216
231
  let stringBuildCode = '(source) => {';
217
232
  let previous = [];
218
233
  for (let i = 0; i < 0x30; i++) {
219
- let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97);
220
- stringBuildCode += `
221
- let ${v} = source[position++]
222
- if (${v} === 0)
223
- return fromCharCode(${previous})
224
- else if (${v} >= 0x80)
225
- ${v} = finishUtf8(${v}, source)
226
- `;
227
- previous.push(v);
234
+ let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97);
235
+ stringBuildCode += `
236
+ let ${v} = source[position++]
237
+ if (${v} === 0)
238
+ return fromCharCode(${previous})
239
+ else if (${v} >= 0x80)
240
+ ${v} = finishUtf8(${v}, source)
241
+ `;
242
+ previous.push(v);
243
+ if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8
244
+ finishUtf8();
228
245
  }
229
246
  stringBuildCode += `return fromCharCode(${previous}) + readString(source)}`;
230
247
  return stringBuildCode
231
248
  }
232
249
 
233
- const enableNullTermination = () => nullTerminate = true;
250
+ let pendingSurrogate;
251
+ function finishUtf8(byte1, src) {
252
+ if ((byte1 & 0xe0) === 0xc0) {
253
+ // 2 bytes
254
+ const byte2 = src[position++] & 0x3f;
255
+ return ((byte1 & 0x1f) << 6) | byte2
256
+ } else if ((byte1 & 0xf0) === 0xe0) {
257
+ // 3 bytes
258
+ const byte2 = src[position++] & 0x3f;
259
+ const byte3 = src[position++] & 0x3f;
260
+ return ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3
261
+ } else if ((byte1 & 0xf8) === 0xf0) {
262
+ // 4 bytes
263
+ if (pendingSurrogate) {
264
+ byte1 = pendingSurrogate;
265
+ pendingSurrogate = null;
266
+ position += 3;
267
+ return byte1
268
+ }
269
+ const byte2 = src[position++] & 0x3f;
270
+ const byte3 = src[position++] & 0x3f;
271
+ const byte4 = src[position++] & 0x3f;
272
+ let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
273
+ if (unit > 0xffff) {
274
+ unit -= 0x10000;
275
+ unit = 0xdc00 | (unit & 0x3ff);
276
+ pendingSurrogate = ((unit >>> 10) & 0x3ff) | 0xd800;
277
+ position -= 4; // reset so we can return the next part of the surrogate pair
278
+ }
279
+ return unit
280
+ } else {
281
+ return byte1
282
+ }
283
+ }
234
284
 
235
285
  const readString = eval(makeStringBuilder());
236
286
 
237
287
  function compareKeys(a, b) {
238
- // compare with type consistency that matches ordered-binary
288
+ // compare with type consistency that matches binary comparison
239
289
  if (typeof a == 'object') {
240
290
  if (!a) {
241
291
  return b == null ? 0 : -1
@@ -282,8 +332,12 @@ const typeOrder = {
282
332
  boolean: 2,
283
333
  number: 3,
284
334
  string: 4
285
- };
335
+ };
336
+ const MINIMUM_KEY = null;
337
+ const MAXIMUM_KEY = new Uint8Array([0xff]);
286
338
 
339
+ exports.MAXIMUM_KEY = MAXIMUM_KEY;
340
+ exports.MINIMUM_KEY = MINIMUM_KEY;
287
341
  exports.compareKeys = compareKeys;
288
342
  exports.enableNullTermination = enableNullTermination;
289
343
  exports.encoder = encoder;
package/index.js CHANGED
@@ -19,6 +19,11 @@ 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
  */
@@ -30,9 +35,9 @@ export 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 @@ export 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':
@@ -198,9 +208,12 @@ export function readKey(buffer, start, end, inSequence) {
198
208
  }
199
209
  return value
200
210
  }
211
+ export const enableNullTermination = () => nullTerminate = true
212
+
201
213
  export const encoder = {
202
214
  writeKey,
203
215
  readKey,
216
+ enableNullTermination,
204
217
  }
205
218
  export const toBufferKey = (key) => {
206
219
  let buffer = Buffer.alloc(2048)
@@ -214,15 +227,17 @@ function makeStringBuilder() {
214
227
  let stringBuildCode = '(source) => {'
215
228
  let previous = []
216
229
  for (let i = 0; i < 0x30; i++) {
217
- let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97)
218
- stringBuildCode += `
219
- let ${v} = source[position++]
220
- if (${v} === 0)
221
- return fromCharCode(${previous})
222
- else if (${v} >= 0x80)
223
- ${v} = finishUtf8(${v}, source)
224
- `
225
- previous.push(v)
230
+ let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97)
231
+ stringBuildCode += `
232
+ let ${v} = source[position++]
233
+ if (${v} === 0)
234
+ return fromCharCode(${previous})
235
+ else if (${v} >= 0x80)
236
+ ${v} = finishUtf8(${v}, source)
237
+ `
238
+ previous.push(v)
239
+ if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8
240
+ finishUtf8()
226
241
  }
227
242
  stringBuildCode += `return fromCharCode(${previous}) + readString(source)}`
228
243
  return stringBuildCode
@@ -263,12 +278,10 @@ function finishUtf8(byte1, src) {
263
278
  }
264
279
  }
265
280
 
266
- export const enableNullTermination = () => nullTerminate = true
267
-
268
281
  const readString = eval(makeStringBuilder())
269
282
 
270
283
  export function compareKeys(a, b) {
271
- // compare with type consistency that matches ordered-binary
284
+ // compare with type consistency that matches binary comparison
272
285
  if (typeof a == 'object') {
273
286
  if (!a) {
274
287
  return b == null ? 0 : -1
@@ -316,3 +329,5 @@ const typeOrder = {
316
329
  number: 3,
317
330
  string: 4
318
331
  }
332
+ export const MINIMUM_KEY = null
333
+ export const MAXIMUM_KEY = new Uint8Array([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.2",
4
+ "version": "1.2.2",
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": {
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@types/node": "latest",
32
32
  "chai": "^4",
33
- "mocha": "^8.1.3",
34
- "rollup": "^1.20.3"
33
+ "mocha": "^9.1.3",
34
+ "rollup": "^2.61.1"
35
35
  }
36
36
  }
package/tests/test.js CHANGED
@@ -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'))