web-dc-api 0.1.5 → 0.1.7
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/dist/cjs/index.js +1 -1
- package/dist/dc.min.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/index.d.ts +934 -878
- package/package.json +4 -8
- package/dist/cjs/helia-core-B1Xqha7a.js +0 -1
- package/dist/cjs/helia-core-D8Uv1KjQ.js +0 -1
- package/dist/cjs/polkadot-api-7PhQf3ws.js +0 -1
- package/dist/cjs/polkadot-api-CtrJVWuZ.js +0 -1
- package/dist/esm/chunks/helia-core-BxMqyK2Y.js +0 -1
- package/dist/esm/chunks/helia-core-DMXRpcO-.js +0 -1
- package/dist/esm/chunks/polkadot-api-5Y9Bw8VT.js +0 -1
- package/dist/esm/chunks/polkadot-api-D69Ioun_.js +0 -1
- package/lib/common/blowfish/block.ts +0 -259
- package/lib/common/blowfish/cipher.ts +0 -144
- package/lib/common/blowfish/const.ts +0 -195
- package/lib/common/chain.ts +0 -469
- package/lib/common/commonclient.ts +0 -202
- package/lib/common/constants.ts +0 -55
- package/lib/common/dc-key/ed25519.ts +0 -343
- package/lib/common/dc-key/keyManager.ts +0 -424
- package/lib/common/dcapi.ts +0 -98
- package/lib/common/dcutil.ts +0 -627
- package/lib/common/define.ts +0 -70
- package/lib/common/error.ts +0 -67
- package/lib/common/grpc-dc.ts +0 -104
- package/lib/common/module-system.ts +0 -184
- package/lib/common/service-worker.ts +0 -234
- package/lib/common/types/types.ts +0 -344
- package/lib/dc.ts +0 -701
- package/lib/implements/account/client.ts +0 -185
- package/lib/implements/account/manager.ts +0 -683
- package/lib/implements/aiproxy/client.ts +0 -357
- package/lib/implements/aiproxy/manager.ts +0 -670
- package/lib/implements/cache/client.ts +0 -105
- package/lib/implements/cache/manager.ts +0 -127
- package/lib/implements/comment/client.ts +0 -982
- package/lib/implements/comment/manager.ts +0 -1151
- package/lib/implements/dc/client.ts +0 -51
- package/lib/implements/dc/manager.ts +0 -33
- package/lib/implements/file/client.ts +0 -253
- package/lib/implements/file/file-cache-manager.ts +0 -142
- package/lib/implements/file/manager.ts +0 -1240
- package/lib/implements/file/seekableFileStream.ts +0 -344
- package/lib/implements/file/streamwriter.ts +0 -322
- package/lib/implements/keyvalue/client.ts +0 -376
- package/lib/implements/keyvalue/manager.ts +0 -759
- package/lib/implements/message/client.ts +0 -250
- package/lib/implements/message/manager.ts +0 -215
- package/lib/implements/threaddb/cbor/coding.ts +0 -62
- package/lib/implements/threaddb/cbor/event.ts +0 -336
- package/lib/implements/threaddb/cbor/node.ts +0 -542
- package/lib/implements/threaddb/cbor/record.ts +0 -398
- package/lib/implements/threaddb/common/AsyncMutex.ts +0 -24
- package/lib/implements/threaddb/common/addrinfo.ts +0 -135
- package/lib/implements/threaddb/common/dispatcher.ts +0 -81
- package/lib/implements/threaddb/common/idbstore-adapter.ts +0 -260
- package/lib/implements/threaddb/common/json-patcher.ts +0 -204
- package/lib/implements/threaddb/common/key.ts +0 -290
- package/lib/implements/threaddb/common/level-adapter.ts +0 -235
- package/lib/implements/threaddb/common/lineReader.ts +0 -79
- package/lib/implements/threaddb/common/logstore.ts +0 -215
- package/lib/implements/threaddb/common/transformed-datastore.ts +0 -308
- package/lib/implements/threaddb/core/app.ts +0 -206
- package/lib/implements/threaddb/core/core.ts +0 -230
- package/lib/implements/threaddb/core/db.ts +0 -249
- package/lib/implements/threaddb/core/event.ts +0 -54
- package/lib/implements/threaddb/core/head.ts +0 -89
- package/lib/implements/threaddb/core/identity.ts +0 -171
- package/lib/implements/threaddb/core/logstore.ts +0 -137
- package/lib/implements/threaddb/core/options.ts +0 -14
- package/lib/implements/threaddb/core/record.ts +0 -54
- package/lib/implements/threaddb/db/collection.ts +0 -1910
- package/lib/implements/threaddb/db/db.ts +0 -698
- package/lib/implements/threaddb/db/json2Query.ts +0 -192
- package/lib/implements/threaddb/db/query.ts +0 -524
- package/lib/implements/threaddb/dbclient.ts +0 -543
- package/lib/implements/threaddb/dbmanager.ts +0 -1906
- package/lib/implements/threaddb/lsstoreds/addr_book.ts +0 -549
- package/lib/implements/threaddb/lsstoreds/cache.ts +0 -36
- package/lib/implements/threaddb/lsstoreds/cyclic_batch.ts +0 -87
- package/lib/implements/threaddb/lsstoreds/global.ts +0 -151
- package/lib/implements/threaddb/lsstoreds/headbook.ts +0 -373
- package/lib/implements/threaddb/lsstoreds/keybook.ts +0 -297
- package/lib/implements/threaddb/lsstoreds/logstore.ts +0 -29
- package/lib/implements/threaddb/lsstoreds/metadata.ts +0 -223
- package/lib/implements/threaddb/net/define.ts +0 -149
- package/lib/implements/threaddb/net/grpcClient.ts +0 -589
- package/lib/implements/threaddb/net/grpcserver.ts +0 -146
- package/lib/implements/threaddb/net/net.ts +0 -2047
- package/lib/implements/threaddb/pb/lstore.proto +0 -38
- package/lib/implements/threaddb/pb/lstore.ts +0 -393
- package/lib/implements/threaddb/pb/lstore_pb.d.ts +0 -433
- package/lib/implements/threaddb/pb/lstore_pb.js +0 -1085
- package/lib/implements/threaddb/pb/net.proto +0 -194
- package/lib/implements/threaddb/pb/net_pb.d.ts +0 -2349
- package/lib/implements/threaddb/pb/net_pb.js +0 -5525
- package/lib/implements/threaddb/pb/proto-custom-types.ts +0 -212
- package/lib/implements/util/client.ts +0 -72
- package/lib/implements/util/manager.ts +0 -146
- package/lib/implements/wallet/manager.ts +0 -671
- package/lib/index.ts +0 -57
- package/lib/interfaces/DCContext.ts +0 -51
- package/lib/interfaces/aiproxy-interface.ts +0 -145
- package/lib/interfaces/auth-interface.ts +0 -118
- package/lib/interfaces/cache-interface.ts +0 -22
- package/lib/interfaces/client-interface.ts +0 -11
- package/lib/interfaces/comment-interface.ts +0 -167
- package/lib/interfaces/components/news-component.ts +0 -0
- package/lib/interfaces/database-interface.ts +0 -169
- package/lib/interfaces/file-interface.ts +0 -120
- package/lib/interfaces/index.ts +0 -10
- package/lib/interfaces/keyvalue-interface.ts +0 -156
- package/lib/interfaces/message-interface.ts +0 -22
- package/lib/interfaces/util-interface.ts +0 -31
- package/lib/modules/aiproxy-module.ts +0 -246
- package/lib/modules/auth-module.ts +0 -753
- package/lib/modules/cache-module.ts +0 -99
- package/lib/modules/client-module.ts +0 -71
- package/lib/modules/comment-module.ts +0 -429
- package/lib/modules/components/news-components.ts +0 -390
- package/lib/modules/database-module.ts +0 -598
- package/lib/modules/file-module.ts +0 -291
- package/lib/modules/index.ts +0 -13
- package/lib/modules/keyvalue-module.ts +0 -379
- package/lib/modules/message-module.ts +0 -107
- package/lib/modules/util-module.ts +0 -148
- package/lib/polyfills/process-env-browser.ts +0 -1
- package/lib/proto/datasource.ts +0 -93
- package/lib/proto/dcnet.proto +0 -1601
- package/lib/proto/dcnet_proto.d.ts +0 -22857
- package/lib/proto/dcnet_proto.js +0 -55204
- package/lib/proto/dcnet_proto_sparse.js +0 -55166
- package/lib/proto/oidfetch.proto +0 -25
- package/lib/proto/oidfetch_proto.d.ts +0 -585
- package/lib/proto/oidfetch_proto.js +0 -1247
- package/lib/serverless/babel-browser.ts +0 -39
- package/lib/serverless/base_entity.ts +0 -78
- package/lib/serverless/base_repository.ts +0 -414
- package/lib/serverless/browser_schema_extractor.ts +0 -283
- package/lib/serverless/decorator_factory.ts +0 -322
- package/lib/util/BrowserLineReader.ts +0 -73
- package/lib/util/base64.ts +0 -105
- package/lib/util/bcrypt.ts +0 -206
- package/lib/util/curve25519Encryption.ts +0 -418
- package/lib/util/dccrypt.ts +0 -73
- package/lib/util/logger.ts +0 -104
- package/lib/util/utils.ts +0 -289
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { ThreadID } from '@textile/threads-id';
|
|
2
|
-
import type { PeerId } from "@libp2p/interface";
|
|
3
|
-
import { peerIdFromString } from "@libp2p/peer-id";
|
|
4
|
-
import { Datastore, Key, KeyQuery } from 'interface-datastore';
|
|
5
|
-
|
|
6
|
-
// 全局配置
|
|
7
|
-
export let AllowEmptyRestore = false;
|
|
8
|
-
export const EmptyEdgeValue = 0;
|
|
9
|
-
|
|
10
|
-
// 配置选项接口
|
|
11
|
-
export interface DSOptions {
|
|
12
|
-
CacheSize: number;
|
|
13
|
-
GCPurgeInterval: number; // 毫秒单位
|
|
14
|
-
GCInitialDelay: number; // 毫秒单位
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// 默认配置生成器
|
|
18
|
-
export function DefaultOpts(): DSOptions {
|
|
19
|
-
return {
|
|
20
|
-
CacheSize: 1024,
|
|
21
|
-
GCPurgeInterval: 2 * 60 * 60 * 1000, // 2小时
|
|
22
|
-
GCInitialDelay: 60 * 1000 // 60秒
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 唯一线程ID提取
|
|
27
|
-
export async function uniqueThreadIds(
|
|
28
|
-
ds: Datastore,
|
|
29
|
-
prefix: Key,
|
|
30
|
-
extractor: (result: Key) => string
|
|
31
|
-
): Promise<ThreadID[]> {
|
|
32
|
-
const q: KeyQuery = { prefix: prefix.toString() };
|
|
33
|
-
const results = await ds.queryKeys(q);
|
|
34
|
-
|
|
35
|
-
const idSet = new Set<string>();
|
|
36
|
-
for await (const result of results) {
|
|
37
|
-
idSet.add(extractor(result));
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return Array.from(idSet)
|
|
41
|
-
.map(id => parseThreadID(id))
|
|
42
|
-
.filter(Boolean) as ThreadID[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 唯一日志ID提取
|
|
46
|
-
export async function uniqueLogIds(
|
|
47
|
-
ds: Datastore,
|
|
48
|
-
prefix: Key,
|
|
49
|
-
extractor: (result: Key) => string
|
|
50
|
-
): Promise<PeerId[]> {
|
|
51
|
-
const q: KeyQuery = { prefix: prefix.toString() };
|
|
52
|
-
const results = ds.queryKeys(q);
|
|
53
|
-
|
|
54
|
-
const idSet = new Set<string>();
|
|
55
|
-
for await (const result of results) {
|
|
56
|
-
idSet.add(extractor(result));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const parsedIds = await Promise.all(
|
|
60
|
-
Array.from(idSet).map(async id => {
|
|
61
|
-
try {
|
|
62
|
-
return await parseLogID(id);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return parsedIds.filter(Boolean) as PeerId[];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function parseThreadID(id: string): ThreadID {
|
|
73
|
-
try {
|
|
74
|
-
return ThreadID.fromString(id);
|
|
75
|
-
} catch {
|
|
76
|
-
return undefined as any;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function parseLogID(id: string): Promise<PeerId> {
|
|
81
|
-
try {
|
|
82
|
-
return peerIdFromString(id);
|
|
83
|
-
} catch {
|
|
84
|
-
throw new Error('Invalid log ID format');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function dsThreadKey(t: ThreadID, baseKey: Key): Key {
|
|
89
|
-
return baseKey.child(new Key(t.toString()));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function dsLogKey(t: ThreadID, p: PeerId, baseKey: Key): Key {
|
|
93
|
-
return baseKey
|
|
94
|
-
.child(new Key(t.toString()))
|
|
95
|
-
.child(new Key(p.toString()));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function uvError(n: number): Error | null {
|
|
99
|
-
return n <= 0 ? new Error('Invalid varint') : null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function getVersion(data: Uint8Array): [number, number, Error | null] {
|
|
103
|
-
const [vers, n] = binaryUvarint(data);
|
|
104
|
-
const err = uvError(n);
|
|
105
|
-
return [vers, n, err];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function binaryUvarint(data: Uint8Array): [number, number] {
|
|
109
|
-
let x = 0;
|
|
110
|
-
let s = 0;
|
|
111
|
-
for (let i = 0; i < data.length; i++) {
|
|
112
|
-
const b = data[i]!;
|
|
113
|
-
if (b < 0x80) {
|
|
114
|
-
if (i > 9 || (i === 9 && b > 1)) {
|
|
115
|
-
return [0, -(i + 1)]; // overflow
|
|
116
|
-
}
|
|
117
|
-
return [x | (b << s), i + 1];
|
|
118
|
-
}
|
|
119
|
-
x |= (b & 0x7f) << s;
|
|
120
|
-
s += 7;
|
|
121
|
-
}
|
|
122
|
-
return [0, 0];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function validateIDData(data: Uint8Array): Error | null {
|
|
126
|
-
const [vers, n, err] = getVersion(data);
|
|
127
|
-
if (err) {
|
|
128
|
-
return err;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (vers !== 1) {
|
|
132
|
-
return new Error(`expected 1 as the id version number, got: ${vers}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const [variant, cn] = binaryUvarint(data.subarray(n));
|
|
136
|
-
const variantErr = uvError(cn);
|
|
137
|
-
if (variantErr) {
|
|
138
|
-
return variantErr;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (variant !== 0 && variant !== 1) {
|
|
142
|
-
return new Error(`expected Raw or AccessControlled as the id variant, got: ${variant}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const id = data.subarray(n + cn);
|
|
146
|
-
if (id.length === 0) {
|
|
147
|
-
return new Error('expected random id bytes but there are none');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import { CID } from 'multiformats/cid';
|
|
2
|
-
import { Key,Pair } from 'interface-datastore';
|
|
3
|
-
import { bases } from 'multiformats/basics';
|
|
4
|
-
|
|
5
|
-
import { Head, HeadBookRecord, serializeHeadBookRecord, deserializeHeadBookRecord } from '../core/head';
|
|
6
|
-
import {
|
|
7
|
-
TxnDatastoreExtended,
|
|
8
|
-
Transaction,
|
|
9
|
-
} from '../core/db';
|
|
10
|
-
import { HeadBook, DumpHeadBook } from '../core/logstore';
|
|
11
|
-
import { ThreadID } from '@textile/threads-id';
|
|
12
|
-
import type { PeerId } from "@libp2p/interface";
|
|
13
|
-
import { dsLogKey, dsThreadKey, AllowEmptyRestore } from './global';
|
|
14
|
-
import * as buffer from "buffer/";
|
|
15
|
-
const { Buffer } = buffer;
|
|
16
|
-
|
|
17
|
-
const hbBase = new Key('/thread/heads');
|
|
18
|
-
const hbEdge = new Key('/thread/heads:edge');
|
|
19
|
-
type LogHead = {
|
|
20
|
-
logId: string;
|
|
21
|
-
head: Head;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
26
|
-
const FNV_PRIME = 0x100000001b3n;
|
|
27
|
-
const MOD64 = 2n ** 64n;
|
|
28
|
-
|
|
29
|
-
export function newHeadBook(ds: TxnDatastoreExtended): DsHeadBook {
|
|
30
|
-
return new DsHeadBook(ds);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class DsHeadBook implements HeadBook {
|
|
34
|
-
private ds: TxnDatastoreExtended;
|
|
35
|
-
|
|
36
|
-
constructor(datastore: TxnDatastoreExtended) {
|
|
37
|
-
this.ds = datastore;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async addHead(tid: ThreadID, pid: PeerId, head: Head): Promise<void> {
|
|
41
|
-
return this.addHeads(tid, pid, [head]);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async addHeads(tid: ThreadID, pid: PeerId, heads: Head[]): Promise<void> {
|
|
45
|
-
return this.withTransaction(async txn => {
|
|
46
|
-
const key = dsLogKey(tid, pid, hbBase);
|
|
47
|
-
let existing = await this.getHeadsRecord(txn, key);
|
|
48
|
-
if (!existing) {
|
|
49
|
-
existing = { heads: [] };
|
|
50
|
-
}
|
|
51
|
-
const newHeads = this.mergeHeads(existing, heads);
|
|
52
|
-
const record = { heads: newHeads };
|
|
53
|
-
await this.saveRecord(txn, key, record);
|
|
54
|
-
await this.invalidateEdge(txn, tid);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async setHead(tid: ThreadID, pid: PeerId, head: Head): Promise<void> {
|
|
59
|
-
return this.setHeads(tid, pid, [head]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async setHeads(tid: ThreadID, pid: PeerId, heads: Head[]): Promise<void> {
|
|
63
|
-
const txn = await this.ds.newTransactionExtended(false);
|
|
64
|
-
try {
|
|
65
|
-
const key = dsLogKey(tid, pid, hbBase);
|
|
66
|
-
const hr: HeadBookRecord = { heads: [] };
|
|
67
|
-
|
|
68
|
-
for (const head of heads) {
|
|
69
|
-
if (!head.id) {
|
|
70
|
-
console.warn(`Ignoring head ${head} is undefined for ${key}`);
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
hr.heads.push({
|
|
74
|
-
id: head.id,
|
|
75
|
-
counter: head.counter,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const serializedRecord = serializeHeadBookRecord(hr);
|
|
80
|
-
const recordBytes = new TextEncoder().encode(serializedRecord);
|
|
81
|
-
await txn.put(key, recordBytes);
|
|
82
|
-
await this.invalidateEdge(txn, tid);
|
|
83
|
-
await txn.commit();
|
|
84
|
-
} catch (err: any) {
|
|
85
|
-
txn.discard();
|
|
86
|
-
throw new Error(`Error when setting heads: ${err.message}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async heads(tid: ThreadID, pid: PeerId): Promise<Head[]> {
|
|
91
|
-
const key = dsLogKey(tid, pid, hbBase);
|
|
92
|
-
try {
|
|
93
|
-
const data = await this.ds.get(key);
|
|
94
|
-
if (!data) return [];
|
|
95
|
-
const hr = deserializeHeadBookRecord(new TextDecoder().decode(data));
|
|
96
|
-
return hr.heads.map(h => ({
|
|
97
|
-
...(h.id ? { id:h.id }: {}),
|
|
98
|
-
counter: h.counter,
|
|
99
|
-
}));
|
|
100
|
-
} catch (err: any) {
|
|
101
|
-
if (err.code === 'ERR_NOT_FOUND') return [];
|
|
102
|
-
throw new Error(`Error when getting current heads from log ${key}: ${err.message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async clearHeads(tid: ThreadID, pid: PeerId): Promise<void> {
|
|
107
|
-
const txn = await this.ds.newTransactionExtended(false);
|
|
108
|
-
try {
|
|
109
|
-
const key = dsLogKey(tid, pid, hbBase);
|
|
110
|
-
await txn.delete(key);
|
|
111
|
-
await this.invalidateEdge(txn, tid);
|
|
112
|
-
await txn.commit();
|
|
113
|
-
} catch (err: any) {
|
|
114
|
-
txn.discard();
|
|
115
|
-
throw new Error(`Error when clearing heads: ${err.message}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private async withTransaction<T>(fn: (txn: Transaction) => Promise<T>): Promise<T> {
|
|
120
|
-
const txn = await this.ds.newTransactionExtended(false);
|
|
121
|
-
try {
|
|
122
|
-
const result = await fn(txn);
|
|
123
|
-
await txn.commit();
|
|
124
|
-
return result;
|
|
125
|
-
} catch (err: any) {
|
|
126
|
-
txn.discard();
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private async getHeadsRecord(txn: Transaction, key: Key): Promise<HeadBookRecord | null> {
|
|
132
|
-
try {
|
|
133
|
-
const data = await txn.get(key);
|
|
134
|
-
if (!data) return null;
|
|
135
|
-
return deserializeHeadBookRecord(new TextDecoder().decode(data));
|
|
136
|
-
} catch (err: any) {
|
|
137
|
-
if (err.code === 'ERR_NOT_FOUND') return null;
|
|
138
|
-
throw err;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private mergeHeads(existing: HeadBookRecord, newHeads: Head[]): Head[] {
|
|
143
|
-
const seen = new Set(existing.heads.map(h => h.id?.toString()));
|
|
144
|
-
return [
|
|
145
|
-
...existing.heads,
|
|
146
|
-
...newHeads.filter(h => !seen.has(h.id?.toString()))
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private async saveRecord(txn: Transaction, key: Key, record: HeadBookRecord): Promise<void> {
|
|
151
|
-
const serializedRecord = serializeHeadBookRecord(record);
|
|
152
|
-
const recordBytes = new TextEncoder().encode(serializedRecord);
|
|
153
|
-
await txn.put(key, recordBytes);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async headsEdge(tid: ThreadID, retries = 3): Promise<bigint> {
|
|
157
|
-
const key = dsThreadKey(tid, hbEdge);
|
|
158
|
-
|
|
159
|
-
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
160
|
-
try {
|
|
161
|
-
return await this.calculateEdge(tid, key);
|
|
162
|
-
} catch (err: any) {
|
|
163
|
-
if (err.code !== 'TX_CONFLICT') throw err;
|
|
164
|
-
await this.randomDelay(attempt);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
throw new Error('Edge computation failed');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private async calculateEdge(tid: ThreadID, key: Key): Promise<bigint> {
|
|
171
|
-
return this.withTransaction(async txn => {
|
|
172
|
-
try {
|
|
173
|
-
const data = await txn.get(key);
|
|
174
|
-
if (data) {
|
|
175
|
-
return this.decodeStoredEdge(data);
|
|
176
|
-
}
|
|
177
|
-
} catch (err: any) {
|
|
178
|
-
if (err.code !== 'ERR_NOT_FOUND') throw err;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const heads = await this.collectHeads(txn, tid);
|
|
182
|
-
if (heads.length === 0) throw new Error('Thread not found');
|
|
183
|
-
|
|
184
|
-
const edge = this.computeHeadsEdge(heads);
|
|
185
|
-
const buffer = Buffer.alloc(8);
|
|
186
|
-
this.writeEdgeValue(buffer, edge);
|
|
187
|
-
|
|
188
|
-
await txn.put(key, buffer);
|
|
189
|
-
return edge;
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private async collectHeads(txn: Transaction, tid: ThreadID): Promise<LogHead[]> {
|
|
194
|
-
const query = { prefix: dsThreadKey(tid, hbBase).toString() };
|
|
195
|
-
const results = txn.query(query);
|
|
196
|
-
|
|
197
|
-
const heads: LogHead[] = [];
|
|
198
|
-
for await (const entry of results) {
|
|
199
|
-
const rawKey = entry.key as any;
|
|
200
|
-
const keyObj = rawKey instanceof Key ? rawKey : new Key(rawKey);
|
|
201
|
-
const namespaces = keyObj.namespaces();
|
|
202
|
-
const logId = namespaces[namespaces.length - 1];
|
|
203
|
-
if (!logId) {
|
|
204
|
-
console.warn(`Skipping malformed head entry for thread ${tid.toString()}`);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
const record = deserializeHeadBookRecord(new TextDecoder().decode(entry.value));
|
|
208
|
-
for (const head of record.heads) {
|
|
209
|
-
if (head.counter === 0) continue;
|
|
210
|
-
heads.push({
|
|
211
|
-
logId,
|
|
212
|
-
head: {
|
|
213
|
-
...(head.id ? { id: head.id } : {}),
|
|
214
|
-
counter: head.counter,
|
|
215
|
-
},
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return heads;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private computeHeadsEdge(hs: LogHead[]): bigint {
|
|
223
|
-
const sorted = [...hs].sort((a, b) => {
|
|
224
|
-
if (a.logId === b.logId) {
|
|
225
|
-
return this.compareCidBytes(a.head.id?.bytes, b.head.id?.bytes);
|
|
226
|
-
}
|
|
227
|
-
return a.logId.localeCompare(b.logId);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const encoder = new TextEncoder();
|
|
231
|
-
let hash = FNV_OFFSET_BASIS;
|
|
232
|
-
|
|
233
|
-
for (const item of sorted) {
|
|
234
|
-
const logIdBytes = this.decodeLogId(item.logId, encoder);
|
|
235
|
-
hash = this.fnv1a64(logIdBytes, hash);
|
|
236
|
-
const headBytes = item.head.id?.bytes ?? new Uint8Array();
|
|
237
|
-
hash = this.fnv1a64(headBytes, hash);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return hash;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private compareCidBytes(left?: Uint8Array, right?: Uint8Array): number {
|
|
244
|
-
const a = left ?? new Uint8Array();
|
|
245
|
-
const b = right ?? new Uint8Array();
|
|
246
|
-
const min = Math.min(a.length, b.length);
|
|
247
|
-
for (let i = 0; i < min; i++) {
|
|
248
|
-
if (a[i] !== b[i]) {
|
|
249
|
-
return a[i] - b[i];
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return a.length - b.length;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private decodeLogId(logId: string, encoder: TextEncoder): Uint8Array {
|
|
256
|
-
if (!logId) {
|
|
257
|
-
return new Uint8Array();
|
|
258
|
-
}
|
|
259
|
-
try {
|
|
260
|
-
return bases.base58btc.baseDecode(logId);
|
|
261
|
-
} catch (err: any) {
|
|
262
|
-
return encoder.encode(logId);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private fnv1a64(data: Uint8Array, initial: bigint): bigint {
|
|
267
|
-
let hash = initial;
|
|
268
|
-
for (const byte of data) {
|
|
269
|
-
hash ^= BigInt(byte);
|
|
270
|
-
hash = (hash * FNV_PRIME) % MOD64;
|
|
271
|
-
}
|
|
272
|
-
return hash;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private async invalidateEdge(txn: Transaction, tid: ThreadID): Promise<void> {
|
|
276
|
-
const key = dsThreadKey(tid, hbEdge);
|
|
277
|
-
await txn.delete(key);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private decodeStoredEdge(data: Uint8Array): bigint {
|
|
281
|
-
const buf: any = Buffer.from(data);
|
|
282
|
-
const reader = buf as unknown as { readBigUInt64BE?: (offset?: number) => bigint };
|
|
283
|
-
if (buf.length >= 8 && typeof reader.readBigUInt64BE === 'function') {
|
|
284
|
-
return reader.readBigUInt64BE(0);
|
|
285
|
-
}
|
|
286
|
-
if (buf.length >= 4) {
|
|
287
|
-
return BigInt(buf.readUInt32BE(0));
|
|
288
|
-
}
|
|
289
|
-
throw new Error('Corrupted head edge value');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private writeEdgeValue(buf: any, value: bigint): void {
|
|
293
|
-
const writer = buf as unknown as { writeBigUInt64BE?: (val: bigint, offset?: number) => number };
|
|
294
|
-
if (typeof writer.writeBigUInt64BE === 'function') {
|
|
295
|
-
writer.writeBigUInt64BE(value, 0);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
buf.writeUInt32BE(Number(value & 0xffffffffn), 0);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private randomDelay(attempt: number): Promise<void> {
|
|
302
|
-
const delay = 50 * attempt + Math.random() * 30;
|
|
303
|
-
return new Promise(resolve =>
|
|
304
|
-
setTimeout(resolve, delay)
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async dumpHeads(): Promise<DumpHeadBook> {
|
|
309
|
-
const data = await this.traverse(true);
|
|
310
|
-
return { data: data };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async restoreHeads(dump: DumpHeadBook): Promise<void> {
|
|
314
|
-
if (!AllowEmptyRestore && Object.keys(dump.data).length === 0) {
|
|
315
|
-
throw new Error('Empty dump');
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const stored = await this.traverse(false);
|
|
319
|
-
|
|
320
|
-
for (const tid in stored) {
|
|
321
|
-
for (const lid in stored[tid]) {
|
|
322
|
-
await this.clearHeads(tid as unknown as ThreadID, lid as unknown as PeerId);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
for (const tid in dump.data) {
|
|
327
|
-
for (const lid in dump.data[tid]) {
|
|
328
|
-
await this.setHeads(tid as unknown as ThreadID, lid as unknown as PeerId, dump.data[tid][lid]!);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private async traverse(withHeads: boolean): Promise<Record<string, Record<string, Head[]>>> {
|
|
334
|
-
const data: Record<string, Record<string, Head[]>> = {};
|
|
335
|
-
const result = this.ds.query({ prefix: hbBase.toString() });
|
|
336
|
-
|
|
337
|
-
for await (const entry of result) {
|
|
338
|
-
const { tid, lid, heads } = await this.decodeHeadEntry(entry, withHeads);
|
|
339
|
-
if (!data[tid]) {
|
|
340
|
-
data[tid] = {};
|
|
341
|
-
}
|
|
342
|
-
data[tid][lid] = heads;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return data;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
private async decodeHeadEntry(
|
|
349
|
-
entry: Pair,
|
|
350
|
-
withHeads: boolean
|
|
351
|
-
): Promise<{ tid: string, lid: string, heads: Head[] }> {
|
|
352
|
-
const kns = entry.key.namespaces();
|
|
353
|
-
if (kns.length < 3) {
|
|
354
|
-
throw new Error(`bad headbook key detected: ${entry.key}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const ts = kns[kns.length - 2]!;
|
|
358
|
-
const ls = kns[kns.length - 1]!;
|
|
359
|
-
const tid = ts;
|
|
360
|
-
const lid = ls;
|
|
361
|
-
|
|
362
|
-
let heads: Head[] = [];
|
|
363
|
-
if (withHeads) {
|
|
364
|
-
const hr = deserializeHeadBookRecord(new TextDecoder().decode(entry.value));
|
|
365
|
-
heads = hr.heads.map(h => ({
|
|
366
|
-
...(h.id ? { id: h.id } : {}),
|
|
367
|
-
counter: h.counter
|
|
368
|
-
}));
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return { tid, lid, heads };
|
|
372
|
-
}
|
|
373
|
-
}
|