-
Notifications
You must be signed in to change notification settings - Fork 0
/
timeserial.ts
155 lines (131 loc) · 4.96 KB
/
timeserial.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import * as Ably from 'ably';
/**
* Represents a parsed timeserial.
*/
export interface Timeserial {
/**
* The series ID of the timeserial.
*/
readonly seriesId: string;
/**
* The timestamp of the timeserial.
*/
readonly timestamp: number;
/**
* The counter of the timeserial.
*/
readonly counter: number;
/**
* The index of the timeserial.
*/
readonly index?: number;
toString(): string;
before(timeserial: Timeserial | string): boolean;
after(timeserial: Timeserial | string): boolean;
equal(timeserial: Timeserial | string): boolean;
}
/**
* Default implementation of the Timeserial interface. Used internally to parse and compare timeserials.
*
* @internal
*/
export class DefaultTimeserial implements Timeserial {
public readonly seriesId: string;
public readonly timestamp: number;
public readonly counter: number;
public readonly index?: number;
private constructor(seriesId: string, timestamp: number, counter: number, index?: number) {
this.seriesId = seriesId;
this.timestamp = timestamp;
this.counter = counter;
this.index = index;
}
/**
* Returns the string representation of the timeserial object.
* @returns The timeserial string.
*/
toString(): string {
return `${this.seriesId}@${this.timestamp.toString()}-${this.counter.toString()}${this.index ? `:${this.index.toString()}` : ''}`;
}
/**
* Calculate the timeserial object from a timeserial string.
*
* @param timeserial The timeserial string to parse.
* @returns The parsed timeserial object.
* @throws {@link ErrorInfo} if timeserial is invalid.
*/
public static calculateTimeserial(timeserial: string): Timeserial {
const [seriesId, rest] = timeserial.split('@');
if (!seriesId || !rest) {
throw new Ably.ErrorInfo('invalid timeserial', 50000, 500);
}
const [timestamp, counterAndIndex] = rest.split('-');
if (!timestamp || !counterAndIndex) {
throw new Ably.ErrorInfo('invalid timeserial', 50000, 500);
}
const [counter, index] = counterAndIndex.split(':');
if (!counter) {
throw new Ably.ErrorInfo('invalid timeserial', 50000, 500);
}
return new DefaultTimeserial(seriesId, Number(timestamp), Number(counter), index ? Number(index) : undefined);
}
/**
* Compares this timeserial to the supplied timeserial, returning a number indicating their relative order.
* @param timeserialToCompare The timeserial to compare against. Can be a string or a Timeserial object.
* @returns 0 if the timeserials are equal, <0 if the first timeserial is less than the second, >0 if the first timeserial is greater than the second.
* @throws {@link ErrorInfo} if comparison timeserial is invalid.
*/
private _timeserialCompare(timeserialToCompare: string | Timeserial): number {
const secondTimeserial =
typeof timeserialToCompare === 'string'
? DefaultTimeserial.calculateTimeserial(timeserialToCompare)
: timeserialToCompare;
// Compare the timestamp
const timestampDiff = this.timestamp - secondTimeserial.timestamp;
if (timestampDiff) {
return timestampDiff;
}
// Compare the counter
const counterDiff = this.counter - secondTimeserial.counter;
if (counterDiff) {
return counterDiff;
}
// Compare the seriesId lexicographically
const seriesIdDiff =
this.seriesId === secondTimeserial.seriesId ? 0 : this.seriesId < secondTimeserial.seriesId ? -1 : 1;
if (seriesIdDiff) {
return seriesIdDiff;
}
// Compare the index, if present
return this.index !== undefined && secondTimeserial.index !== undefined ? this.index - secondTimeserial.index : 0;
}
/**
* Determines if this timeserial occurs logically before the given timeserial.
*
* @param timeserial The timeserial to compare against. Can be a string or a Timeserial object.
* @returns true if this timeserial precedes the given timeserial, in global order.
* @throws {@link ErrorInfo} if the given timeserial is invalid.
*/
before(timeserial: Timeserial | string): boolean {
return this._timeserialCompare(timeserial) < 0;
}
/**
* Determines if this timeserial occurs logically after the given timeserial.
*
* @param timeserial The timeserial to compare against. Can be a string or a Timeserial object.
* @returns true if this timeserial follows the given timeserial, in global order.
* @throws {@link ErrorInfo} if the given timeserial is invalid.
*/
after(timeserial: Timeserial | string): boolean {
return this._timeserialCompare(timeserial) > 0;
}
/**
* Determines if this timeserial is equal to the given timeserial.
* @param timeserial The timeserial to compare against. Can be a string or a Timeserial object.
* @returns true if this timeserial is equal to the given timeserial.
* @throws {@link ErrorInfo} if the given timeserial is invalid.
*/
equal(timeserial: Timeserial | string): boolean {
return this._timeserialCompare(timeserial) === 0;
}
}