Skip to content

Commit bd8fe29

Browse files
committed
Changing to Id derivative of Uint8Array in order to provide operator overloading
1 parent e6bf8c8 commit bd8fe29

12 files changed

+349
-135
lines changed

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"no-constant-condition": 0,
2828
"no-useless-escape": 0,
2929
"no-console": "error",
30-
"eqeqeq": ["error", "smart"],
3130
"capitalized-comments": [
3231
"warn",
3332
"always",
@@ -66,6 +65,7 @@
6665
{
6766
"selector": "parameter",
6867
"format": ["camelCase"],
68+
"leadingUnderscore": "allowSingleOrDouble",
6969
"trailingUnderscore": "allowSingleOrDouble"
7070
},
7171
{

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Id.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as utils from './utils';
2+
3+
/**
4+
* IdInternal can be used as a string primitive
5+
* This type hack prevents TS from complaining
6+
* See: https://github.com/microsoft/TypeScript/issues/4538
7+
*/
8+
type Id = IdInternal & number;
9+
10+
class IdInternal extends Uint8Array {
11+
public static create(): Id;
12+
public static create(length: number): Id;
13+
public static create(array: ArrayLike<number> | ArrayBufferLike): Id;
14+
public static create(
15+
buffer: ArrayBufferLike,
16+
byteOffset?: number,
17+
length?: number,
18+
): Id;
19+
public static create(...args: Array<any>): Id {
20+
// @ts-ignore: spreading into Uint8Array constructor
21+
return new IdInternal(...args) as Id;
22+
}
23+
24+
public [Symbol.toPrimitive](_hint: 'string' | 'number' | 'default'): string {
25+
return utils.toString(this as unknown as Id);
26+
}
27+
28+
public toString(): string {
29+
return utils.toString(this as unknown as Id);
30+
}
31+
}
32+
33+
export default IdInternal;
34+
35+
export type { Id };

src/IdDeterministic.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import type { Id } from './Id';
2+
13
import { v5 as uuidv5, NIL } from 'uuid';
4+
import IdInternal from './Id';
25

36
/**
47
* This produces deterministic ids based on:
@@ -8,7 +11,7 @@ import { v5 as uuidv5, NIL } from 'uuid';
811
* namespaceUUID is SHA1(NIL UUID + namespace)
912
* )
1013
*/
11-
class IdDeterministic implements IterableIterator<ArrayBuffer> {
14+
class IdDeterministic implements IterableIterator<Id> {
1215
protected namespaceData: Uint8Array;
1316

1417
public constructor({
@@ -21,20 +24,20 @@ class IdDeterministic implements IterableIterator<ArrayBuffer> {
2124
this.namespaceData = namespaceData;
2225
}
2326

24-
public get(name?: string): ArrayBuffer {
25-
return this.next(name).value as ArrayBuffer;
27+
public get(name?: string): Id {
28+
return this.next(name).value as Id;
2629
}
2730

28-
public next(name: string = ''): IteratorResult<ArrayBuffer, void> {
29-
const idData = new ArrayBuffer(16);
30-
uuidv5(name, this.namespaceData, new Uint8Array(idData));
31+
public next(name: string = ''): IteratorResult<Id, void> {
32+
const id = IdInternal.create(16);
33+
uuidv5(name, this.namespaceData, id);
3134
return {
32-
value: idData,
35+
value: id,
3336
done: false,
3437
};
3538
}
3639

37-
public [Symbol.iterator](): IterableIterator<ArrayBuffer> {
40+
public [Symbol.iterator](): IterableIterator<Id> {
3841
return this;
3942
}
4043
}

src/IdRandom.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import type { Id } from './Id';
2+
13
import { v4 as uuidv4 } from 'uuid';
24
import * as utils from './utils';
5+
import IdInternal from './Id';
36

4-
class IdRandom implements IterableIterator<ArrayBuffer> {
7+
class IdRandom implements IterableIterator<Id> {
58
protected randomSource: (size: number) => Uint8Array;
69

710
public constructor({
@@ -12,26 +15,26 @@ class IdRandom implements IterableIterator<ArrayBuffer> {
1215
this.randomSource = randomSource;
1316
}
1417

15-
public get(): ArrayBuffer {
16-
return this.next().value as ArrayBuffer;
18+
public get(): Id {
19+
return this.next().value as Id;
1720
}
1821

19-
public next(): IteratorResult<ArrayBuffer, void> {
20-
const idData = new ArrayBuffer(16);
21-
// Uuidv4 does mutate the random data
22+
public next(): IteratorResult<Id, void> {
23+
const id = IdInternal.create(16);
24+
// `uuidv4` mutates the random data
2225
uuidv4(
2326
{
2427
rng: () => this.randomSource(16),
2528
},
26-
new Uint8Array(idData),
29+
id,
2730
);
2831
return {
29-
value: idData,
32+
value: id,
3033
done: false,
3134
};
3235
}
3336

34-
public [Symbol.iterator](): IterableIterator<ArrayBuffer> {
37+
public [Symbol.iterator](): IterableIterator<Id> {
3538
return this;
3639
}
3740
}

src/IdSortable.ts

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { Id } from './Id';
2+
3+
import IdInternal from './Id';
14
import * as utils from './utils';
25

36
/**
@@ -22,11 +25,12 @@ const msecPrecision = 3;
2225
const variantBits = '10';
2326
const versionBits = '0111';
2427

25-
function extractTs(idBytes: ArrayBuffer): number {
28+
function extractTs(idBytes: Uint8Array): number {
2629
// Decode the timestamp from the last id
2730
// the timestamp bits is 48 bits or 6 bytes
28-
const idTsBytes = new Uint8Array(idBytes, 0, (unixtsSize + msecSize) / 8);
29-
const idTsBits = utils.bytes2bin(idTsBytes);
31+
// this creates a new zero-copy view
32+
const idTsBytes = idBytes.subarray(0, (unixtsSize + msecSize) / 8);
33+
const idTsBits = utils.bytes2bits(idTsBytes);
3034
const unixtsBits = idTsBits.substr(0, unixtsSize);
3135
const msecBits = idTsBits.substr(unixtsSize, unixtsSize + msecSize);
3236
const unixts = parseInt(unixtsBits, 2);
@@ -35,23 +39,21 @@ function extractTs(idBytes: ArrayBuffer): number {
3539
return utils.fromFixedPoint([unixts, msec], msecSize, msecPrecision);
3640
}
3741

38-
function extractSeq(idBytes: ArrayBuffer): number {
39-
const idSeqBytes = new Uint8Array(
40-
idBytes,
42+
function extractSeq(idBytes: Uint8Array): number {
43+
const idSeqBytes = idBytes.subarray(
4144
(unixtsSize + msecSize) / 8,
4245
(unixtsSize + msecSize + 4 + seqSize) / 8,
4346
);
44-
const idSeqBits = utils.bytes2bin(idSeqBytes).substr(4, seqSize);
47+
const idSeqBits = utils.bytes2bits(idSeqBytes).substr(4, seqSize);
4548
const seq = parseInt(idSeqBits, 2);
4649
return seq;
4750
}
4851

49-
function extractRand(idBytes: ArrayBuffer): string {
50-
const idRandBytes = new Uint8Array(
51-
idBytes,
52+
function extractRand(idBytes: Uint8Array): string {
53+
const idRandBytes = idBytes.subarray(
5254
(unixtsSize + msecSize + 4 + seqSize) / 8,
5355
);
54-
const idRandBits = utils.bytes2bin(idRandBytes).substr(2);
56+
const idRandBits = utils.bytes2bits(idRandBytes).substr(2);
5557
return idRandBits;
5658
}
5759

@@ -63,12 +65,12 @@ function extractRand(idBytes: ArrayBuffer): string {
6365
* 12 bits seq enables 4096 ids per millisecond
6466
* After 4096, it rolls over
6567
*/
66-
class IdSortable implements IterableIterator<ArrayBuffer> {
68+
class IdSortable implements IterableIterator<Id> {
6769
protected randomSource: (size: number) => Uint8Array;
6870
protected clock: () => number;
6971
protected nodeBits?: string;
7072
protected lastTs?: [number, number];
71-
protected lastId_?: ArrayBuffer;
73+
protected _lastId?: Id;
7274
protected seqCounter: number;
7375

7476
public constructor({
@@ -77,8 +79,8 @@ class IdSortable implements IterableIterator<ArrayBuffer> {
7779
timeSource = utils.timeSource,
7880
randomSource = utils.randomBytes,
7981
}: {
80-
lastId?: ArrayBuffer;
81-
nodeId?: ArrayBuffer;
82+
lastId?: Uint8Array;
83+
nodeId?: Uint8Array;
8284
timeSource?: (lastTs?: number) => () => number;
8385
randomSource?: (size: number) => Uint8Array;
8486
} = {}) {
@@ -96,24 +98,24 @@ class IdSortable implements IterableIterator<ArrayBuffer> {
9698
}
9799
}
98100

99-
get lastId(): ArrayBuffer {
100-
if (this.lastId_ == null) {
101+
get lastId(): Id {
102+
if (this._lastId == null) {
101103
throw new ReferenceError('lastId has not yet been generated');
102104
}
103-
return this.lastId_;
105+
return this._lastId;
104106
}
105107

106-
public get(): ArrayBuffer {
107-
return this.next().value as ArrayBuffer;
108+
public get(): Id {
109+
return this.next().value as Id;
108110
}
109111

110-
public next(): IteratorResult<ArrayBuffer, void> {
112+
public next(): IteratorResult<Id, void> {
111113
// Clock returns millisecond precision
112114
const ts = this.clock() / 10 ** msecPrecision;
113115
// Converting to seconds and subseconds
114116
const [unixts, msec] = utils.toFixedPoint(ts, msecSize, msecPrecision);
115-
const unixtsBits = utils.dec2bin(unixts, unixtsSize);
116-
const msecBits = utils.dec2bin(msec, msecSize);
117+
const unixtsBits = utils.dec2bits(unixts, unixtsSize);
118+
const msecBits = utils.dec2bits(msec, msecSize);
117119
if (
118120
this.lastTs != null &&
119121
this.lastTs[0] >= unixts &&
@@ -123,7 +125,7 @@ class IdSortable implements IterableIterator<ArrayBuffer> {
123125
} else {
124126
this.seqCounter = 0;
125127
}
126-
const seqBits = utils.dec2bin(this.seqCounter, seqSize);
128+
const seqBits = utils.dec2bits(this.seqCounter, seqSize);
127129
// NodeBits can be written to the most significant rand portion
128130
let randBits: string;
129131
if (this.nodeBits != null) {
@@ -137,17 +139,18 @@ class IdSortable implements IterableIterator<ArrayBuffer> {
137139
}
138140
const idBits =
139141
unixtsBits + msecBits + versionBits + seqBits + variantBits + randBits;
140-
const idBytes = utils.bin2bytes(idBits);
142+
const idBytes = utils.bits2bytes(idBits);
143+
const id = IdInternal.create(idBytes.buffer);
141144
// Save the fixed point timestamp
142145
this.lastTs = [unixts, msec];
143-
this.lastId_ = idBytes.buffer;
146+
this._lastId = id;
144147
return {
145-
value: idBytes.buffer,
148+
value: id,
146149
done: false,
147150
};
148151
}
149152

150-
public [Symbol.iterator](): IterableIterator<ArrayBuffer> {
153+
public [Symbol.iterator](): IterableIterator<Id> {
151154
return this;
152155
}
153156
}

0 commit comments

Comments
 (0)