@peerbit/log 1.0.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/LICENSE +23 -0
- package/README.md +11 -0
- package/lib/esm/change.d.ts +5 -0
- package/lib/esm/change.js +2 -0
- package/lib/esm/change.js.map +1 -0
- package/lib/esm/clock.d.ts +87 -0
- package/lib/esm/clock.js +260 -0
- package/lib/esm/clock.js.map +1 -0
- package/lib/esm/difference.d.ts +1 -0
- package/lib/esm/difference.js +20 -0
- package/lib/esm/difference.js.map +1 -0
- package/lib/esm/encoding.d.ts +7 -0
- package/lib/esm/encoding.js +20 -0
- package/lib/esm/encoding.js.map +1 -0
- package/lib/esm/entry-index.d.ts +21 -0
- package/lib/esm/entry-index.js +63 -0
- package/lib/esm/entry-index.js.map +1 -0
- package/lib/esm/entry-with-refs.d.ts +5 -0
- package/lib/esm/entry-with-refs.js +2 -0
- package/lib/esm/entry-with-refs.js.map +1 -0
- package/lib/esm/entry.d.ts +179 -0
- package/lib/esm/entry.js +591 -0
- package/lib/esm/entry.js.map +1 -0
- package/lib/esm/find-uniques.d.ts +1 -0
- package/lib/esm/find-uniques.js +12 -0
- package/lib/esm/find-uniques.js.map +1 -0
- package/lib/esm/heads-cache.d.ts +64 -0
- package/lib/esm/heads-cache.js +317 -0
- package/lib/esm/heads-cache.js.map +1 -0
- package/lib/esm/heads.d.ts +63 -0
- package/lib/esm/heads.js +143 -0
- package/lib/esm/heads.js.map +1 -0
- package/lib/esm/hrtime.d.ts +5 -0
- package/lib/esm/hrtime.js +71 -0
- package/lib/esm/hrtime.js.map +1 -0
- package/lib/esm/index.d.ts +11 -0
- package/lib/esm/index.js +11 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/is-defined.d.ts +1 -0
- package/lib/esm/is-defined.js +2 -0
- package/lib/esm/is-defined.js.map +1 -0
- package/lib/esm/log-errors.d.ts +5 -0
- package/lib/esm/log-errors.js +6 -0
- package/lib/esm/log-errors.js.map +1 -0
- package/lib/esm/log-sorting.d.ts +44 -0
- package/lib/esm/log-sorting.js +86 -0
- package/lib/esm/log-sorting.js.map +1 -0
- package/lib/esm/log.d.ts +205 -0
- package/lib/esm/log.js +1004 -0
- package/lib/esm/log.js.map +1 -0
- package/lib/esm/logger.d.ts +2 -0
- package/lib/esm/logger.js +4 -0
- package/lib/esm/logger.js.map +1 -0
- package/lib/esm/package.json +3 -0
- package/lib/esm/snapshot.d.ts +22 -0
- package/lib/esm/snapshot.js +83 -0
- package/lib/esm/snapshot.js.map +1 -0
- package/lib/esm/trim.d.ts +49 -0
- package/lib/esm/trim.js +203 -0
- package/lib/esm/trim.js.map +1 -0
- package/lib/esm/types.d.ts +6 -0
- package/lib/esm/types.js +23 -0
- package/lib/esm/types.js.map +1 -0
- package/lib/esm/utils.d.ts +2 -0
- package/lib/esm/utils.js +3 -0
- package/lib/esm/utils.js.map +1 -0
- package/lib/esm/values.d.ts +33 -0
- package/lib/esm/values.js +141 -0
- package/lib/esm/values.js.map +1 -0
- package/package.json +79 -0
- package/src/change.ts +2 -0
- package/src/clock.ts +280 -0
- package/src/difference.ts +22 -0
- package/src/encoding.ts +27 -0
- package/src/entry-index.ts +78 -0
- package/src/entry-with-refs.ts +6 -0
- package/src/entry.ts +749 -0
- package/src/find-uniques.ts +14 -0
- package/src/heads-cache.ts +400 -0
- package/src/heads.ts +208 -0
- package/src/hrtime.ts +78 -0
- package/src/index.ts +11 -0
- package/src/is-defined.ts +1 -0
- package/src/log-errors.ts +9 -0
- package/src/log-sorting.ts +108 -0
- package/src/log.ts +1262 -0
- package/src/logger.ts +3 -0
- package/src/snapshot.ts +103 -0
- package/src/trim.ts +269 -0
- package/src/types.ts +12 -0
- package/src/utils.ts +2 -0
- package/src/values.ts +193 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import yallist from "yallist";
|
|
2
|
+
export class Values {
|
|
3
|
+
/**
|
|
4
|
+
* Keep track of sorted elements in descending sort order (i.e. newest elements)
|
|
5
|
+
*/
|
|
6
|
+
_values;
|
|
7
|
+
_sortFn;
|
|
8
|
+
_byteLength;
|
|
9
|
+
_entryIndex;
|
|
10
|
+
constructor(entryIndex, sortFn, entries = []) {
|
|
11
|
+
this._values = yallist.create(entries
|
|
12
|
+
.slice()
|
|
13
|
+
.sort(sortFn)
|
|
14
|
+
.reverse()
|
|
15
|
+
.map((x) => {
|
|
16
|
+
if (!x.hash)
|
|
17
|
+
throw new Error("Unexpected");
|
|
18
|
+
return {
|
|
19
|
+
hash: x.hash,
|
|
20
|
+
byteLength: x._payload.byteLength,
|
|
21
|
+
gid: x.gid,
|
|
22
|
+
};
|
|
23
|
+
}));
|
|
24
|
+
this._byteLength = 0;
|
|
25
|
+
entries.forEach((entry) => {
|
|
26
|
+
this._byteLength += entry._payload.byteLength;
|
|
27
|
+
});
|
|
28
|
+
this._sortFn = sortFn;
|
|
29
|
+
this._entryIndex = entryIndex;
|
|
30
|
+
}
|
|
31
|
+
toArray() {
|
|
32
|
+
return Promise.all(this._values.toArrayReverse().map((x) => this._entryIndex.get(x.hash))).then((arr) => arr.filter((x) => !!x)); // we do reverse because we assume the log is only meaningful if we read it from start to end
|
|
33
|
+
}
|
|
34
|
+
get head() {
|
|
35
|
+
return this._values.head;
|
|
36
|
+
}
|
|
37
|
+
get tail() {
|
|
38
|
+
return this._values.tail;
|
|
39
|
+
}
|
|
40
|
+
get length() {
|
|
41
|
+
return this._values.length;
|
|
42
|
+
}
|
|
43
|
+
_putPromise = new Map();
|
|
44
|
+
async put(value) {
|
|
45
|
+
let promise = this._putPromise.get(value.hash);
|
|
46
|
+
if (promise) {
|
|
47
|
+
return promise;
|
|
48
|
+
}
|
|
49
|
+
promise = this._put(value).then((v) => {
|
|
50
|
+
this._putPromise.delete(value.hash);
|
|
51
|
+
return v;
|
|
52
|
+
});
|
|
53
|
+
this._putPromise.set(value.hash, promise);
|
|
54
|
+
return promise;
|
|
55
|
+
}
|
|
56
|
+
async _put(value) {
|
|
57
|
+
// assume we want to insert at head (or somehere close)
|
|
58
|
+
let walker = this._values.head;
|
|
59
|
+
let last = undefined;
|
|
60
|
+
while (walker) {
|
|
61
|
+
const walkerValue = await this.getEntry(walker);
|
|
62
|
+
if (!walkerValue) {
|
|
63
|
+
throw new Error("Missing walker value");
|
|
64
|
+
}
|
|
65
|
+
if (walkerValue.hash === value.hash) {
|
|
66
|
+
return; // already exist!
|
|
67
|
+
}
|
|
68
|
+
if (this._sortFn(walkerValue, value) < 0) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
last = walker;
|
|
72
|
+
walker = walker.next;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
this._byteLength += value._payload.byteLength;
|
|
76
|
+
if (!value.hash) {
|
|
77
|
+
throw new Error("Unexpected");
|
|
78
|
+
}
|
|
79
|
+
_insertAfter(this._values, last, {
|
|
80
|
+
byteLength: value._payload.byteLength,
|
|
81
|
+
gid: value.gid,
|
|
82
|
+
hash: value.hash,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async delete(value) {
|
|
86
|
+
const hash = typeof value === "string" ? value : value.hash;
|
|
87
|
+
// Assume we want to delete at tail (or somwhere close)
|
|
88
|
+
let walker = this._values.tail;
|
|
89
|
+
while (walker) {
|
|
90
|
+
const walkerValue = await this.getEntry(walker);
|
|
91
|
+
if (!walkerValue) {
|
|
92
|
+
throw new Error("Missing walker value");
|
|
93
|
+
}
|
|
94
|
+
if (walkerValue.hash === hash) {
|
|
95
|
+
this._values.removeNode(walker);
|
|
96
|
+
this._byteLength -= walkerValue._payload.byteLength;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
walker = walker.prev; // prev will be undefined if you do removeNode(walker)
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Failed to delete, entry does not exist" +
|
|
102
|
+
" ??? " +
|
|
103
|
+
this.length +
|
|
104
|
+
" ??? " +
|
|
105
|
+
hash);
|
|
106
|
+
}
|
|
107
|
+
deleteNode(node) {
|
|
108
|
+
this._values.removeNode(node);
|
|
109
|
+
this._byteLength -= node.value.byteLength;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
pop() {
|
|
113
|
+
const value = this._values.pop();
|
|
114
|
+
if (value) {
|
|
115
|
+
this._byteLength -= value.byteLength;
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
get byteLength() {
|
|
120
|
+
return this._byteLength;
|
|
121
|
+
}
|
|
122
|
+
async getEntry(node) {
|
|
123
|
+
return this._entryIndex.get(node.value.hash);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function _insertAfter(self, node, value) {
|
|
127
|
+
const inserted = !node
|
|
128
|
+
? new yallist.Node(value, null, self.head, self)
|
|
129
|
+
: new yallist.Node(value, node, node.next, self);
|
|
130
|
+
// is tail
|
|
131
|
+
if (inserted.next === null) {
|
|
132
|
+
self.tail = inserted;
|
|
133
|
+
}
|
|
134
|
+
// is head
|
|
135
|
+
if (inserted.prev === null) {
|
|
136
|
+
self.head = inserted;
|
|
137
|
+
}
|
|
138
|
+
self.length++;
|
|
139
|
+
return inserted;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","sourceRoot":"","sources":["../../src/values.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peerbit/log",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Append-only log CRDT",
|
|
5
|
+
"author": "dao.xyz",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"module": "lib/esm/index.js",
|
|
9
|
+
"types": "lib/esm/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
"import": "./lib/esm/index.js",
|
|
12
|
+
"require": "./lib/cjs/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"src",
|
|
17
|
+
"!src/**/__tests__",
|
|
18
|
+
"!lib/**/__tests__",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16.15.1"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"ipfs",
|
|
29
|
+
"log",
|
|
30
|
+
"crdts",
|
|
31
|
+
"crdt"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@dao-xyz/borsh": "^5.1.5",
|
|
35
|
+
"@peerbit/blocks-interface": "^1.0.1",
|
|
36
|
+
"@peerbit/cache": "1.0.0",
|
|
37
|
+
"@peerbit/crypto": "1.0.1",
|
|
38
|
+
"@peerbit/logger": "1.0.0",
|
|
39
|
+
"libp2p": "^0.45.9",
|
|
40
|
+
"p-queue": "^7.3.3",
|
|
41
|
+
"path-browserify": "^1.0.1",
|
|
42
|
+
"yallist": "^4.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@peerbit/test-utils": "1.0.2",
|
|
46
|
+
"@types/yallist": "^4.0.1",
|
|
47
|
+
"assert": "^2.0.0",
|
|
48
|
+
"json-stringify-deterministic": "^1.0.7"
|
|
49
|
+
},
|
|
50
|
+
"contributors": [
|
|
51
|
+
"haadcode",
|
|
52
|
+
"aphelionz",
|
|
53
|
+
"shamb0t",
|
|
54
|
+
"thiagodelgado111",
|
|
55
|
+
"mistakia",
|
|
56
|
+
"satazor",
|
|
57
|
+
"RichardLitt",
|
|
58
|
+
"greenkeeperio-bot",
|
|
59
|
+
"chrisdostert",
|
|
60
|
+
"zachferland",
|
|
61
|
+
"kaibakker",
|
|
62
|
+
"dignifiedquire",
|
|
63
|
+
"adam-palazzo"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"clean": "shx rm -rf lib/*",
|
|
67
|
+
"build": "yarn clean && tsc -p tsconfig.json",
|
|
68
|
+
"postbuild": "echo '{\"type\":\"module\"} ' | node ../../node_modules/.bin/json > lib/esm/package.json",
|
|
69
|
+
"test": "node --experimental-vm-modules ./../../node_modules/.bin/jest test -c ../../jest.config.ts --runInBand --forceExit",
|
|
70
|
+
"test:unit": "node --experimental-vm-modules ../../node_modules/.bin/jest test -c ../../jest.config.unit.ts --runInBand --forceExit",
|
|
71
|
+
"test:integration": "node --experimental-vm-modules ../node_modules/.bin/jest test -c ../../jest.config.integration.ts --runInBand --forceExit",
|
|
72
|
+
"benchmark": "nyc --require ts-node/register benchmark-runner -r -b --baselineLimit 1000",
|
|
73
|
+
"benchmark:stress": "benchmark-runner -r --grep stress"
|
|
74
|
+
},
|
|
75
|
+
"localMaintainers": [
|
|
76
|
+
"dao.xyz"
|
|
77
|
+
],
|
|
78
|
+
"gitHead": "595db9f1efebf604393eddfff5f678f5d8f16142"
|
|
79
|
+
}
|
package/src/change.ts
ADDED
package/src/clock.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021 Martin Heidegger
|
|
5
|
+
Copyright (c) 2022 dao.xyz
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { field, variant } from "@dao-xyz/borsh";
|
|
27
|
+
import { compare, equals } from "@peerbit/uint8arrays";
|
|
28
|
+
import hrtime from "./hrtime.js";
|
|
29
|
+
|
|
30
|
+
const hrTimeNow = hrtime.bigint();
|
|
31
|
+
const startTime = BigInt(Date.now()) * BigInt(1e6) - hrTimeNow;
|
|
32
|
+
const bigintTime = () => startTime + hrtime.bigint();
|
|
33
|
+
|
|
34
|
+
export function fromBits(low, high, unsigned, target?) {
|
|
35
|
+
if (target === undefined || target === null) {
|
|
36
|
+
return {
|
|
37
|
+
low: low | 0,
|
|
38
|
+
high: high | 0,
|
|
39
|
+
unsigned: !!unsigned,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
target.low = low | 0;
|
|
43
|
+
target.high = high | 0;
|
|
44
|
+
target.unsigned = !!unsigned;
|
|
45
|
+
return target;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const n1e6 = BigInt(1e6);
|
|
49
|
+
const UINT64_MAX = 18446744073709551615n;
|
|
50
|
+
const UINT32_MAX = 0xffffffff;
|
|
51
|
+
|
|
52
|
+
function bigIntCoerce(input, fallback) {
|
|
53
|
+
if (typeof input === "bigint") return input;
|
|
54
|
+
if (typeof input === "number" || typeof input === "string")
|
|
55
|
+
return BigInt(input);
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@variant(0)
|
|
60
|
+
export class Timestamp {
|
|
61
|
+
@field({ type: "u64" })
|
|
62
|
+
wallTime: bigint;
|
|
63
|
+
|
|
64
|
+
@field({ type: "u32" })
|
|
65
|
+
logical: number;
|
|
66
|
+
|
|
67
|
+
constructor(properties?: { wallTime: bigint; logical?: number }) {
|
|
68
|
+
if (properties) {
|
|
69
|
+
this.wallTime = properties.wallTime;
|
|
70
|
+
this.logical = properties.logical || 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static compare(a: Timestamp, b: Timestamp) {
|
|
75
|
+
if (a.wallTime > b.wallTime) return 1;
|
|
76
|
+
if (a.wallTime < b.wallTime) return -1;
|
|
77
|
+
if (a.logical > b.logical) return 1;
|
|
78
|
+
if (a.logical < b.logical) return -1;
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static bigger(a: Timestamp, b: Timestamp) {
|
|
83
|
+
return a.compare(b) === -1 ? b : a;
|
|
84
|
+
}
|
|
85
|
+
static smaller(a: Timestamp, b: Timestamp) {
|
|
86
|
+
return a.compare(b) === 1 ? b : a;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
compare(other: Timestamp) {
|
|
90
|
+
return Timestamp.compare(this, other);
|
|
91
|
+
}
|
|
92
|
+
clone(): Timestamp {
|
|
93
|
+
return new Timestamp({
|
|
94
|
+
wallTime: this.wallTime,
|
|
95
|
+
logical: this.logical,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
toString() {
|
|
100
|
+
return `Timestamp: wallTime: ${this.wallTime}, logical: ${this.logical}`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class HLC {
|
|
105
|
+
maxOffset: bigint;
|
|
106
|
+
wallTimeUpperBound: bigint;
|
|
107
|
+
toleratedForwardClockJump: bigint;
|
|
108
|
+
last: Timestamp;
|
|
109
|
+
wallTime: () => bigint;
|
|
110
|
+
constructor(
|
|
111
|
+
properties: {
|
|
112
|
+
wallTime?: () => bigint;
|
|
113
|
+
maxOffset?: bigint;
|
|
114
|
+
wallTimeUpperBound?: bigint;
|
|
115
|
+
toleratedForwardClockJump?: bigint;
|
|
116
|
+
last?: Timestamp;
|
|
117
|
+
} = {}
|
|
118
|
+
) {
|
|
119
|
+
this.wallTime = properties.wallTime || bigintTime;
|
|
120
|
+
this.maxOffset = bigIntCoerce(properties.maxOffset, 0n);
|
|
121
|
+
this.wallTimeUpperBound = bigIntCoerce(properties.wallTimeUpperBound, 0n);
|
|
122
|
+
this.toleratedForwardClockJump = bigIntCoerce(
|
|
123
|
+
properties.toleratedForwardClockJump,
|
|
124
|
+
0n
|
|
125
|
+
);
|
|
126
|
+
this.last = new Timestamp({ wallTime: this.wallTime() });
|
|
127
|
+
if (properties.last) {
|
|
128
|
+
this.last = Timestamp.bigger(properties.last, this.last);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
now() {
|
|
133
|
+
return this.update(this.last);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
validateOffset(offset: bigint) {
|
|
137
|
+
if (
|
|
138
|
+
this.toleratedForwardClockJump > 0n &&
|
|
139
|
+
-offset > this.toleratedForwardClockJump
|
|
140
|
+
) {
|
|
141
|
+
throw new ForwardJumpError(-offset, this.toleratedForwardClockJump);
|
|
142
|
+
}
|
|
143
|
+
if (this.maxOffset > 0n && offset > this.maxOffset) {
|
|
144
|
+
throw new ClockOffsetError(offset, this.maxOffset);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
update(other: Timestamp) {
|
|
149
|
+
const last = Timestamp.bigger(other, this.last);
|
|
150
|
+
let wallTime = this.wallTime();
|
|
151
|
+
const offset = last.wallTime - wallTime;
|
|
152
|
+
this.validateOffset(offset);
|
|
153
|
+
let logical: number;
|
|
154
|
+
if (offset < 0n) {
|
|
155
|
+
logical = 0;
|
|
156
|
+
} else {
|
|
157
|
+
wallTime = last.wallTime;
|
|
158
|
+
logical = last.logical + 1;
|
|
159
|
+
if (logical > UINT32_MAX) {
|
|
160
|
+
wallTime += 1n;
|
|
161
|
+
logical = 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const maxWallTime =
|
|
165
|
+
this.wallTimeUpperBound > 0n ? this.wallTimeUpperBound : UINT64_MAX;
|
|
166
|
+
if (wallTime > maxWallTime) {
|
|
167
|
+
throw new WallTimeOverflowError(wallTime, maxWallTime);
|
|
168
|
+
}
|
|
169
|
+
this.last = new Timestamp({ wallTime, logical });
|
|
170
|
+
return this.last;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export class ClockOffsetError extends Error {
|
|
175
|
+
offset: bigint;
|
|
176
|
+
maxOffset: bigint;
|
|
177
|
+
constructor(offset: bigint, maxOffset: bigint) {
|
|
178
|
+
super(
|
|
179
|
+
`The received time is ${
|
|
180
|
+
offset / n1e6
|
|
181
|
+
}ms ahead of the wall time, exceeding the 'maxOffset' limit of ${
|
|
182
|
+
maxOffset / n1e6
|
|
183
|
+
}ms.`
|
|
184
|
+
);
|
|
185
|
+
this.offset = offset;
|
|
186
|
+
this.maxOffset = maxOffset;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export class WallTimeOverflowError extends Error {
|
|
191
|
+
time: bigint;
|
|
192
|
+
maxTime: bigint;
|
|
193
|
+
constructor(time: bigint, maxTime: bigint) {
|
|
194
|
+
super(
|
|
195
|
+
`The wall time ${time / n1e6}ms exceeds the max time of ${
|
|
196
|
+
maxTime / n1e6
|
|
197
|
+
}ms.`
|
|
198
|
+
);
|
|
199
|
+
this.time = time;
|
|
200
|
+
this.maxTime = maxTime;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export class ForwardJumpError extends Error {
|
|
205
|
+
timejump: bigint;
|
|
206
|
+
tolerance: bigint;
|
|
207
|
+
constructor(timejump: bigint, tolerance: bigint) {
|
|
208
|
+
super(
|
|
209
|
+
`Detected a forward time jump of ${
|
|
210
|
+
timejump / n1e6
|
|
211
|
+
}ms, which exceed the allowed tolerance of ${tolerance / n1e6}ms.`
|
|
212
|
+
);
|
|
213
|
+
this.timejump = timejump;
|
|
214
|
+
this.tolerance = tolerance;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@variant(0)
|
|
219
|
+
export class LamportClock {
|
|
220
|
+
@field({ type: Uint8Array })
|
|
221
|
+
id: Uint8Array;
|
|
222
|
+
|
|
223
|
+
@field({ type: Timestamp })
|
|
224
|
+
timestamp: Timestamp;
|
|
225
|
+
|
|
226
|
+
constructor(properties?: { id: Uint8Array; timestamp?: Timestamp | number }) {
|
|
227
|
+
if (properties) {
|
|
228
|
+
this.id = properties.id;
|
|
229
|
+
if (!properties.timestamp) {
|
|
230
|
+
this.timestamp = new Timestamp({
|
|
231
|
+
wallTime: bigintTime(),
|
|
232
|
+
logical: 0,
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
if (typeof properties.timestamp === "number") {
|
|
236
|
+
this.timestamp = new Timestamp({
|
|
237
|
+
wallTime: bigintTime(),
|
|
238
|
+
logical: properties.timestamp,
|
|
239
|
+
});
|
|
240
|
+
} else {
|
|
241
|
+
this.timestamp = properties.timestamp;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
clone() {
|
|
248
|
+
return new LamportClock({
|
|
249
|
+
id: this.id,
|
|
250
|
+
timestamp: this.timestamp.clone(),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
equals(other: LamportClock): boolean {
|
|
255
|
+
return (
|
|
256
|
+
equals(this.id, other.id) && this.timestamp.compare(other.timestamp) === 0
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Not optimized, dont use for performance critical things
|
|
262
|
+
* @returns
|
|
263
|
+
*/
|
|
264
|
+
advance() {
|
|
265
|
+
const h = new HLC();
|
|
266
|
+
h.update(new Timestamp(this.timestamp));
|
|
267
|
+
return new LamportClock({ id: this.id, timestamp: h.now() });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static compare(a: LamportClock, b: LamportClock) {
|
|
271
|
+
// Calculate the "distance" based on the clock, ie. lower or greater
|
|
272
|
+
|
|
273
|
+
const timestamp = a.timestamp.compare(b.timestamp);
|
|
274
|
+
if (timestamp !== 0) return timestamp;
|
|
275
|
+
|
|
276
|
+
// If the sequence number is the same (concurrent events),
|
|
277
|
+
// and the IDs are different, take the one with a "lower" id
|
|
278
|
+
return compare(a.id, b.id);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const difference = (a: any, b: any, key: string) => {
|
|
2
|
+
// Indices for quick lookups
|
|
3
|
+
const processed: { [key: string]: any } = {};
|
|
4
|
+
const existing: { [key: string]: any } = {};
|
|
5
|
+
|
|
6
|
+
// Create an index of the first collection
|
|
7
|
+
const addToIndex = (e: any) => (existing[key ? e[key] : e] = true);
|
|
8
|
+
a.forEach(addToIndex);
|
|
9
|
+
|
|
10
|
+
// Reduce to entries that are not in the first collection
|
|
11
|
+
const reducer = (res: any, entry: any) => {
|
|
12
|
+
const isInFirst = existing[key ? entry[key] : entry] !== undefined;
|
|
13
|
+
const hasBeenProcessed = processed[key ? entry[key] : entry] !== undefined;
|
|
14
|
+
if (!isInFirst && !hasBeenProcessed) {
|
|
15
|
+
res.push(entry);
|
|
16
|
+
processed[key ? entry[key] : entry] = true;
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return b.reduce(reducer, []);
|
|
22
|
+
};
|
package/src/encoding.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AbstractType, deserialize, serialize } from "@dao-xyz/borsh";
|
|
2
|
+
|
|
3
|
+
export interface Encoding<T> {
|
|
4
|
+
encoder: (data: T) => Uint8Array;
|
|
5
|
+
decoder: (bytes: Uint8Array) => T;
|
|
6
|
+
}
|
|
7
|
+
export const NO_ENCODING: Encoding<any> = {
|
|
8
|
+
encoder: (obj: Uint8Array) => {
|
|
9
|
+
if (obj instanceof Uint8Array === false) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"With NO_ENCODING only Uint8arrays are allowed, recieved: " +
|
|
12
|
+
(typeof obj === "object" ? obj.constructor.name : typeof obj)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return obj;
|
|
16
|
+
},
|
|
17
|
+
decoder: (bytes: Uint8Array) => {
|
|
18
|
+
return bytes;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const BORSH_ENCODING = <T>(clazz: AbstractType<T>): Encoding<T> => {
|
|
23
|
+
return {
|
|
24
|
+
decoder: (bytes: Uint8Array) => deserialize(bytes, clazz),
|
|
25
|
+
encoder: (data: any) => serialize(data),
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Cache } from "@peerbit/cache";
|
|
2
|
+
import { Entry } from "./entry.js";
|
|
3
|
+
import { deserialize } from "@dao-xyz/borsh";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
import { Blocks } from "@peerbit/blocks-interface";
|
|
6
|
+
|
|
7
|
+
export class EntryIndex<T> {
|
|
8
|
+
_cache: Cache<Entry<T> | null>;
|
|
9
|
+
_store: Blocks;
|
|
10
|
+
_init: (entry: Entry<T>) => void;
|
|
11
|
+
_index: Set<string>;
|
|
12
|
+
|
|
13
|
+
constructor(properties: {
|
|
14
|
+
store: Blocks;
|
|
15
|
+
init: (entry: Entry<T>) => void;
|
|
16
|
+
cache: Cache<Entry<T>>;
|
|
17
|
+
}) {
|
|
18
|
+
this._cache = properties.cache;
|
|
19
|
+
this._store = properties.store;
|
|
20
|
+
this._init = properties.init;
|
|
21
|
+
this._index = new Set();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async set(v: Entry<T>, toMultihash = true) {
|
|
25
|
+
if (toMultihash) {
|
|
26
|
+
const existingHash = v.hash;
|
|
27
|
+
v.hash = undefined as any;
|
|
28
|
+
try {
|
|
29
|
+
const hash = await Entry.toMultihash(this._store, v);
|
|
30
|
+
v.hash = existingHash;
|
|
31
|
+
if (v.hash === undefined) {
|
|
32
|
+
v.hash = hash; // can happen if you sync entries that you load directly from ipfs
|
|
33
|
+
} else if (existingHash !== v.hash) {
|
|
34
|
+
logger.error("Head hash didn't match the contents");
|
|
35
|
+
throw new Error("Head hash didn't match the contents");
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error(error);
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this._cache.add(v.hash, v);
|
|
43
|
+
this._index.add(v.hash);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async get(
|
|
47
|
+
k: string,
|
|
48
|
+
options?: { replicate?: boolean; timeout?: number }
|
|
49
|
+
): Promise<Entry<T> | undefined> {
|
|
50
|
+
if (this._index.has(k)) {
|
|
51
|
+
let mem = this._cache.get(k);
|
|
52
|
+
if (mem === undefined) {
|
|
53
|
+
mem = await this.getFromStore(k, options);
|
|
54
|
+
if (mem) {
|
|
55
|
+
this._init(mem);
|
|
56
|
+
mem.hash = k;
|
|
57
|
+
}
|
|
58
|
+
this._cache.add(k, mem);
|
|
59
|
+
}
|
|
60
|
+
return mem ? mem : undefined;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private async getFromStore(
|
|
66
|
+
k: string,
|
|
67
|
+
options?: { replicate?: boolean; timeout?: number }
|
|
68
|
+
): Promise<Entry<T> | null> {
|
|
69
|
+
const value = await this._store.get(k, options);
|
|
70
|
+
return value ? deserialize(value, Entry) : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async delete(k: string) {
|
|
74
|
+
this._cache.del(k);
|
|
75
|
+
this._index.delete(k);
|
|
76
|
+
return this._store.rm(k);
|
|
77
|
+
}
|
|
78
|
+
}
|