web-dc-api 0.0.87 → 0.0.89
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 -0
- package/dist/cjs/protobuf-BVBdi7Hh.js +1 -0
- package/dist/dc.min.js +1 -19
- package/dist/esm/chunks/protobuf-CbxDm-Gy.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +947 -886
- package/package.json +14 -17
- package/dist/index.cjs.js +0 -19
- package/dist/index.esm.js +0 -19
- 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 -595
- package/lib/common/define.ts +0 -66
- 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 -694
- 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 -202
- 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 -443
- package/lib/implements/threaddb/dbmanager.ts +0 -1901
- package/lib/implements/threaddb/lsstoreds/addr_book.ts +0 -452
- 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 -280
- 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 -138
- package/lib/implements/threaddb/net/grpcClient.ts +0 -582
- package/lib/implements/threaddb/net/grpcserver.ts +0 -146
- package/lib/implements/threaddb/net/net.ts +0 -2006
- 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 -664
- 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/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/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,1901 +0,0 @@
|
|
|
1
|
-
import { PeerId } from '@libp2p/interface';
|
|
2
|
-
import { multiaddr, Multiaddr as TMultiaddr } from '@multiformats/multiaddr';
|
|
3
|
-
import { ThreadID } from '@textile/threads-id';
|
|
4
|
-
import { peerIdFromPrivateKey, peerIdFromString } from "@libp2p/peer-id";
|
|
5
|
-
import { Key } from 'interface-datastore';
|
|
6
|
-
import { DB as ThreadDb } from './db/db';
|
|
7
|
-
import { Errors, Transaction } from './core/db';
|
|
8
|
-
import { Net } from './core/app';
|
|
9
|
-
import { ChainUtil } from "../../common/chain";
|
|
10
|
-
import { Ed25519PrivKey } from "../../common/dc-key/ed25519";
|
|
11
|
-
import type { Connection } from '@libp2p/interface'
|
|
12
|
-
import { keys } from "@libp2p/crypto";
|
|
13
|
-
import { SymmetricKey, Key as ThreadKey } from './common/key';
|
|
14
|
-
import { extractPeerIdFromMultiaddr } from "../../common/dc-key/keyManager";
|
|
15
|
-
import {IDBInfo, ThreadMuliaddr} from './core/core'
|
|
16
|
-
|
|
17
|
-
import {StoreunitInfo} from '../../common/chain';
|
|
18
|
-
import { createTransformedDatastore, PrefixTransform,TransformedDatastore} from './common/transformed-datastore'
|
|
19
|
-
import {NewOptions,ICollectionConfig,ManagedOptions,ThreadInfo,Context} from './core/core';
|
|
20
|
-
import {TxnDatastoreExtended,pullThreadBackgroundTimeout,PullTimeout} from './core/db';
|
|
21
|
-
import type { DCConnectInfo } from "../../common/types/types";
|
|
22
|
-
import { uint32ToLittleEndianBytes,uint64ToLittleEndianBytes } from "../../util/utils";
|
|
23
|
-
import {DcUtil} from '../../common/dcutil';
|
|
24
|
-
import {Type} from '../../common/constants';
|
|
25
|
-
import { NewThreadOptions } from './core/options';
|
|
26
|
-
import {ThreadToken} from './core/identity';
|
|
27
|
-
import { DBGrpcClient } from "./net/grpcClient";
|
|
28
|
-
import type { Client } from "../../common/dcapi";
|
|
29
|
-
import { jsonStringify } from '../../util/utils';
|
|
30
|
-
import {Protocol} from './net/define';
|
|
31
|
-
import {parseJsonToQuery} from './db/json2Query';
|
|
32
|
-
import * as buffer from "buffer/";
|
|
33
|
-
import { dial_timeout } from '../../common/define';
|
|
34
|
-
import multibase, {decode as multibaseDecode} from 'multibase';
|
|
35
|
-
import {net as net_pb} from "./pb/net_pb";
|
|
36
|
-
import { LineReader } from './common/lineReader';
|
|
37
|
-
import { FileManager } from '../file/manager';
|
|
38
|
-
import {newIterator} from './db/collection';
|
|
39
|
-
import { Query } from './db/query';
|
|
40
|
-
import { DCContext } from '../../interfaces';
|
|
41
|
-
const { Buffer } = buffer;
|
|
42
|
-
|
|
43
|
-
export const ThreadProtocol = "/dc/" + Protocol.name + "/" + Protocol.version
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// 常量
|
|
47
|
-
export const MaxLoadConcurrency = 100;
|
|
48
|
-
export const dsManagerBaseKey = new Key('/manager');
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
function newGrpcClient(client: Client,net:Net): DBGrpcClient {
|
|
54
|
-
if (client.p2pNode == null || client.p2pNode.peerId == null) {
|
|
55
|
-
throw new Error("p2pNode is null or node privateKey is null");
|
|
56
|
-
}
|
|
57
|
-
const grpcClient = new DBGrpcClient(
|
|
58
|
-
client.p2pNode,
|
|
59
|
-
client.peerAddr,
|
|
60
|
-
client.token,
|
|
61
|
-
net,
|
|
62
|
-
client.protocol
|
|
63
|
-
);
|
|
64
|
-
return grpcClient
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// 管理器类
|
|
70
|
-
export class DBManager {
|
|
71
|
-
private store: TxnDatastoreExtended;
|
|
72
|
-
private network: Net;
|
|
73
|
-
private dc: DcUtil;
|
|
74
|
-
private connectedDc:DCConnectInfo;
|
|
75
|
-
private opts: NewOptions;
|
|
76
|
-
private dbs: Map<string, ThreadDb>;
|
|
77
|
-
private lock: AsyncLock;
|
|
78
|
-
public chainUtil: ChainUtil;
|
|
79
|
-
private storagePrefix: string;
|
|
80
|
-
private context: DCContext;
|
|
81
|
-
|
|
82
|
-
constructor(
|
|
83
|
-
store: TxnDatastoreExtended, //实际上是一个LevelDatastore实例,用levelDatastoreAdapter包装
|
|
84
|
-
network: Net,
|
|
85
|
-
dc: DcUtil,
|
|
86
|
-
connectedDc:DCConnectInfo,
|
|
87
|
-
opts: NewOptions = {} ,
|
|
88
|
-
chainUtil: ChainUtil,
|
|
89
|
-
storagePrefix: string,
|
|
90
|
-
context: DCContext
|
|
91
|
-
) {
|
|
92
|
-
|
|
93
|
-
this.store = store;
|
|
94
|
-
this.network = network;
|
|
95
|
-
this.dc = dc;
|
|
96
|
-
this.storagePrefix = storagePrefix;
|
|
97
|
-
this.opts = opts;
|
|
98
|
-
this.dbs = new Map();
|
|
99
|
-
this.lock = new AsyncLock();
|
|
100
|
-
this.chainUtil = chainUtil;
|
|
101
|
-
this.connectedDc = connectedDc;
|
|
102
|
-
this.context = context
|
|
103
|
-
}
|
|
104
|
-
async loadDbs():Promise<void>{
|
|
105
|
-
// Query for existing databases
|
|
106
|
-
console.log("manager: loading dbs");
|
|
107
|
-
try {
|
|
108
|
-
const q = { prefix: dsManagerBaseKey.toString(), keysOnly: true };
|
|
109
|
-
const results = this.store.query(q);
|
|
110
|
-
// Create a map to track loaded databases and prevent duplicates
|
|
111
|
-
const loaded = new Map<string, boolean>();
|
|
112
|
-
// Process each result
|
|
113
|
-
for await (const result of results) {
|
|
114
|
-
try {
|
|
115
|
-
// Parse the key to extract thread ID
|
|
116
|
-
const parts = result.key.toString().split('/');
|
|
117
|
-
if (parts.length < 3) {
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
const id = ThreadID.fromString(parts[2]||"");
|
|
121
|
-
// Check if already loaded
|
|
122
|
-
if (loaded.has(id.toString())) {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
// Mark as loaded
|
|
126
|
-
loaded.set(id.toString(), true)
|
|
127
|
-
// Wrap and create database
|
|
128
|
-
const [store, opts, err] = await this.wrapDB(
|
|
129
|
-
this.store,
|
|
130
|
-
id,
|
|
131
|
-
this.opts,
|
|
132
|
-
"",
|
|
133
|
-
[]
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (err) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
const db = await ThreadDb.newDB(store, this.network, id, opts);
|
|
140
|
-
// Add to map of databases
|
|
141
|
-
this.dbs.set(id.toString(), db);
|
|
142
|
-
} catch (err) {
|
|
143
|
-
console.error(`Error loading database: ${err instanceof Error ? err.message : String(err)}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}catch (err) {
|
|
147
|
-
console.error(`Failed to load databases: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
-
throw err;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Gets the log key for a thread. If it doesn't exist, creates a new one.
|
|
155
|
-
* @param tid Thread ID
|
|
156
|
-
* @returns Promise resolving to the private key
|
|
157
|
-
* @throws Error if key operations fail
|
|
158
|
-
*/
|
|
159
|
-
async getLogKey(tid: ThreadID): Promise<Ed25519PrivKey> {
|
|
160
|
-
const storageKey = `${this.storagePrefix}_${tid.toString()}_logkey`;
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
// Try to get existing key from localStorage
|
|
164
|
-
const storedBytes = await this.store.get(new Key(storageKey));
|
|
165
|
-
if (storedBytes) {
|
|
166
|
-
const key = Ed25519PrivKey.fromString(Buffer.from(storedBytes).toString('hex'));
|
|
167
|
-
return key;
|
|
168
|
-
} else {
|
|
169
|
-
// Create new key if none exists
|
|
170
|
-
const key = await this.newLogKey();
|
|
171
|
-
this.store.put(new Key(storageKey), Buffer.from(key.raw));
|
|
172
|
-
return key;
|
|
173
|
-
}
|
|
174
|
-
} catch (err : any) {
|
|
175
|
-
if (err.code === 'ERR_NOT_FOUND') {
|
|
176
|
-
// Create new key if none exists
|
|
177
|
-
const key = await this.newLogKey();
|
|
178
|
-
this.store.put(new Key(storageKey), Buffer.from(key.raw));
|
|
179
|
-
return key;
|
|
180
|
-
}
|
|
181
|
-
throw new Error(`Failed to get/create log key: ${err}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Creates a new log key
|
|
189
|
-
* @private
|
|
190
|
-
*/
|
|
191
|
-
private async newLogKey(): Promise<Ed25519PrivKey> {
|
|
192
|
-
try {
|
|
193
|
-
//生成临时ed25519公私钥对
|
|
194
|
-
const keyPair = await keys.generateKeyPair("Ed25519");
|
|
195
|
-
// 获取私钥
|
|
196
|
-
const privateKey = new Ed25519PrivKey(keyPair.raw)
|
|
197
|
-
return privateKey;
|
|
198
|
-
} catch (err) {
|
|
199
|
-
throw new Error(`Failed to generate log key: ${err}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 新增静态方法
|
|
204
|
-
/**
|
|
205
|
-
* FromAddr returns ID from a multiaddress if present.
|
|
206
|
-
* @param addr Multiaddr instance
|
|
207
|
-
* @returns ID instance
|
|
208
|
-
*/
|
|
209
|
-
static fromAddr(addr: TMultiaddr): ThreadID {
|
|
210
|
-
try {
|
|
211
|
-
// 获取协议值
|
|
212
|
-
const parts = addr.toString().split('/')
|
|
213
|
-
const index = parts.indexOf(Protocol.name)
|
|
214
|
-
if (index === -1 || index === parts.length - 1) {
|
|
215
|
-
throw new Error('thread protocol not found in multiaddr')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const idstr = parts[index + 1] || ""
|
|
219
|
-
return ThreadID.fromString(idstr)
|
|
220
|
-
} catch (err:any) {
|
|
221
|
-
|
|
222
|
-
throw new Error(`Failed to extract ID from multiaddr: ${err.message}`)
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* ToAddr returns ID wrapped as a multiaddress.
|
|
228
|
-
* @returns Multiaddr instance
|
|
229
|
-
*/
|
|
230
|
-
toAddr(): TMultiaddr {
|
|
231
|
-
try {
|
|
232
|
-
const addr = multiaddr(`/${Protocol.name}/${this.toString()}`)
|
|
233
|
-
return addr
|
|
234
|
-
} catch (err:any) {
|
|
235
|
-
// This should not happen with valid IDs
|
|
236
|
-
throw new Error(`Failed to create multiaddr: ${err.message}`)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async newDBFromAddr(
|
|
241
|
-
addr: ThreadMuliaddr,
|
|
242
|
-
key: ThreadKey,
|
|
243
|
-
opts: NewOptions = {}
|
|
244
|
-
): Promise<ThreadDb> {
|
|
245
|
-
const id = addr.id;
|
|
246
|
-
|
|
247
|
-
// return await this.lock.acquire('dbs', async () => {
|
|
248
|
-
|
|
249
|
-
if (this.dbs.has(id.toString())) {
|
|
250
|
-
|
|
251
|
-
throw Errors.ErrDBExists;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (opts.name && !isValidName(opts.name)) {
|
|
255
|
-
throw Errors.ErrInvalidName;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (key.defined() && !key.canRead()) {
|
|
259
|
-
throw Errors.ErrThreadReadKeyRequired;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
await this.network.addThread(addr, {
|
|
263
|
-
token: opts.token ,
|
|
264
|
-
logKey: opts.logKey,
|
|
265
|
-
threadKey:key,
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const collections = opts.collections || [];
|
|
269
|
-
const name = opts.name || '';
|
|
270
|
-
|
|
271
|
-
const [store, dbOpts, err] = await this.wrapDB(this.store,id, this.opts,name, collections);
|
|
272
|
-
if (err) {
|
|
273
|
-
throw new Error(`wrapping db: ${err.message}`);
|
|
274
|
-
}
|
|
275
|
-
const db = await ThreadDb.newDB(store, this.network, id, dbOpts);
|
|
276
|
-
|
|
277
|
-
this.dbs.set(id.toString(), db);
|
|
278
|
-
|
|
279
|
-
if (opts.block) {
|
|
280
|
-
await this.network.pullThread(id,pullThreadBackgroundTimeout, { token: opts.token });
|
|
281
|
-
} else {
|
|
282
|
-
// Background pull
|
|
283
|
-
this.pullThreadBackground(id, opts.token);
|
|
284
|
-
}
|
|
285
|
-
return db;
|
|
286
|
-
// });
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private async pullThreadBackground(id: ThreadID, token?: ThreadToken) {
|
|
290
|
-
try {
|
|
291
|
-
await this.network.pullThread( id,pullThreadBackgroundTimeout, { token: token });
|
|
292
|
-
} catch (err) {
|
|
293
|
-
console.error(`Error pulling thread ${id}:`, err);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Preloads a database from DC network to local
|
|
301
|
-
* Generally happens when a new device first logs in to sync previously created databases
|
|
302
|
-
*
|
|
303
|
-
* @param threadid Thread ID string
|
|
304
|
-
* @param fid Content ID string of the file to preload
|
|
305
|
-
* @param dbname Database name
|
|
306
|
-
* @param dbAddr Database address string
|
|
307
|
-
* @param b32Rk Base32-encoded read key
|
|
308
|
-
* @param b32Sk Base32-encoded secret key
|
|
309
|
-
* @param block Whether to block until syncing is complete
|
|
310
|
-
* @param jsonCollections JSON string of collection configurations
|
|
311
|
-
* @returns Promise that resolves when preloading is complete
|
|
312
|
-
*/
|
|
313
|
-
async preloadDBFromDC(
|
|
314
|
-
threadid: string,
|
|
315
|
-
fid: string,
|
|
316
|
-
dbname: string,
|
|
317
|
-
dbAddr: string,
|
|
318
|
-
b32Rk: string,
|
|
319
|
-
b32Sk: string,
|
|
320
|
-
block: boolean,
|
|
321
|
-
jsonCollections: string
|
|
322
|
-
): Promise<void> {
|
|
323
|
-
console.debug(`manager: preloading DB from DC ${threadid}`);
|
|
324
|
-
|
|
325
|
-
// Check if DBManager exists
|
|
326
|
-
if (!this) {
|
|
327
|
-
throw Errors.ErrNoDbManager;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Decode thread ID
|
|
331
|
-
let tID: ThreadID;
|
|
332
|
-
try {
|
|
333
|
-
tID = await this.decodeThreadId(threadid);
|
|
334
|
-
} catch (err) {
|
|
335
|
-
throw err;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Get log key
|
|
339
|
-
let logKey: Ed25519PrivKey;
|
|
340
|
-
try {
|
|
341
|
-
logKey = await this.getLogKey(tID);
|
|
342
|
-
} catch (err) {
|
|
343
|
-
throw err;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Create peer ID from log key
|
|
347
|
-
let lid: PeerId;
|
|
348
|
-
try {
|
|
349
|
-
lid = peerIdFromPrivateKey(logKey);
|
|
350
|
-
} catch (err) {
|
|
351
|
-
throw err;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Begin connection in background
|
|
355
|
-
await this.dc._connectToObjNodes(tID.toString()).catch(err =>
|
|
356
|
-
console.error(`Error connecting to object nodes: ${err.message}`)
|
|
357
|
-
);
|
|
358
|
-
const ctx = createContext(30000);
|
|
359
|
-
try {
|
|
360
|
-
await this.addLogToThreadStart(ctx, tID, lid);
|
|
361
|
-
} catch (err:any) {
|
|
362
|
-
console.warn(`Warning: could not add log to thread: ${err.message}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Generate thread key
|
|
366
|
-
let threadKey: ThreadKey;
|
|
367
|
-
try {
|
|
368
|
-
const sk = SymmetricKey.fromString(b32Sk);
|
|
369
|
-
const rk = SymmetricKey.fromString(b32Rk);
|
|
370
|
-
threadKey = new ThreadKey(sk, rk);
|
|
371
|
-
} catch (err) {
|
|
372
|
-
throw err;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
let connectedPeerId: PeerId | undefined;
|
|
376
|
-
let multiAddr: TMultiaddr | undefined;
|
|
377
|
-
|
|
378
|
-
// Try to connect using provided dbAddr
|
|
379
|
-
let connectedFlag = false;
|
|
380
|
-
if (dbAddr.length > 10) {
|
|
381
|
-
try {
|
|
382
|
-
// Try to parse address info and connect
|
|
383
|
-
const peerAddrInfo = multiaddr(dbAddr);
|
|
384
|
-
|
|
385
|
-
try {
|
|
386
|
-
const conn = await this.dc.dcNodeClient?.libp2p.dial(peerAddrInfo, {
|
|
387
|
-
signal: AbortSignal.timeout(3000)
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
if (conn) {
|
|
391
|
-
connectedFlag = true;
|
|
392
|
-
multiAddr = multiaddr(dbAddr);
|
|
393
|
-
}
|
|
394
|
-
} catch (err) {
|
|
395
|
-
// Connection failed
|
|
396
|
-
}
|
|
397
|
-
} catch (err) {
|
|
398
|
-
// Invalid address
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// If direct connection failed, connect through object nodes
|
|
403
|
-
if (!connectedFlag) {
|
|
404
|
-
try {
|
|
405
|
-
const [connectedAddr, peers] = await this.dc?._connectToObjNodes(tID.toString());
|
|
406
|
-
if (!connectedAddr && peers) {
|
|
407
|
-
// 有peers但是没有connectedAddr
|
|
408
|
-
throw Errors.ErrNoThreadOnDc;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const conns = this.dc.dcNodeClient?.libp2p.getConnections(connectedPeerId);
|
|
412
|
-
if (!conns || conns.length == 0) {
|
|
413
|
-
throw Errors.ErrNoThreadOnDc;
|
|
414
|
-
}
|
|
415
|
-
} catch (err) {
|
|
416
|
-
throw err;
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
// Start connection in background
|
|
420
|
-
this.dc._connectToObjNodes(tID.toString()).catch(err =>
|
|
421
|
-
console.error(`Error connecting to object nodes: ${err.message}`)
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Parse collection info
|
|
426
|
-
let collectionInfos: ICollectionConfig[] = [];
|
|
427
|
-
try {
|
|
428
|
-
collectionInfos = JSON.parse(jsonCollections);
|
|
429
|
-
} catch (err) {
|
|
430
|
-
throw err;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Create collection configurations
|
|
434
|
-
const collections = collectionInfos.map(info => ({
|
|
435
|
-
name: info.name,
|
|
436
|
-
schema: info.schema,
|
|
437
|
-
indexes: info.indexes || []
|
|
438
|
-
}));
|
|
439
|
-
|
|
440
|
-
// Create options for new database
|
|
441
|
-
const dbOpts: NewOptions = {
|
|
442
|
-
name: dbname,
|
|
443
|
-
collections: collections,
|
|
444
|
-
key: threadKey,
|
|
445
|
-
logKey: logKey,
|
|
446
|
-
block: block
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
// Delete existing database if it exists
|
|
450
|
-
try {
|
|
451
|
-
await this.deleteDB(tID, true);
|
|
452
|
-
} catch (err) {
|
|
453
|
-
// Ignore specific errors
|
|
454
|
-
if (err !== Errors.ErrDBNotFound && err !== Errors.ErrThreadNotFound) {
|
|
455
|
-
throw err;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
if (this.dc.dcNodeClient == null) {
|
|
459
|
-
throw Errors.ErrNoDcNodeClient;
|
|
460
|
-
}
|
|
461
|
-
// Create context with extended timeout for file download
|
|
462
|
-
const tctx = createContext(PullTimeout * 30);
|
|
463
|
-
const fileManager = new FileManager(
|
|
464
|
-
this.dc,
|
|
465
|
-
this.connectedDc,
|
|
466
|
-
this.chainUtil,
|
|
467
|
-
this.dc.dcNodeClient,
|
|
468
|
-
this.context
|
|
469
|
-
);
|
|
470
|
-
const fileStream = await fileManager.createSeekableFileStream(fid, "");
|
|
471
|
-
|
|
472
|
-
if (fileStream == null) {
|
|
473
|
-
throw Errors.ErrFileNotFound;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Preload database from reader
|
|
477
|
-
const threadMultiaddr = new ThreadMuliaddr(multiAddr!, tID);
|
|
478
|
-
await this.preloadDBFromReader(tctx, fileStream.createReadableStream(), threadMultiaddr, threadKey, dbOpts);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
* 从读取器预加载数据库
|
|
485
|
-
* @param ctx 上下文
|
|
486
|
-
* @param ioReader 数据流读取器
|
|
487
|
-
* @param addr 线程地址
|
|
488
|
-
* @param key 线程密钥
|
|
489
|
-
* @param opts 管理选项
|
|
490
|
-
*/
|
|
491
|
-
async preloadDBFromReader(
|
|
492
|
-
ctx: Context,
|
|
493
|
-
ioReader: ReadableStream<Uint8Array>,
|
|
494
|
-
addr: ThreadMuliaddr,
|
|
495
|
-
key: ThreadKey,
|
|
496
|
-
opts: NewOptions = {}
|
|
497
|
-
): Promise<void> {
|
|
498
|
-
console.debug("manager: preloading db from reader");
|
|
499
|
-
let id: ThreadID;
|
|
500
|
-
try {
|
|
501
|
-
id = DBManager.fromAddr(addr.addr);
|
|
502
|
-
} catch (err) {
|
|
503
|
-
throw err;
|
|
504
|
-
}
|
|
505
|
-
// 检查数据库是否已存在
|
|
506
|
-
// await this.lock.acquire('dbs', async () => {
|
|
507
|
-
if (this.dbs.has(id.toString())) {
|
|
508
|
-
throw Errors.ErrDBExists;
|
|
509
|
-
}
|
|
510
|
-
//});
|
|
511
|
-
if (opts.name && !isValidName(opts.name)) {
|
|
512
|
-
throw Errors.ErrInvalidName;
|
|
513
|
-
}
|
|
514
|
-
// 验证密钥
|
|
515
|
-
if (key.defined() && !key.canRead()) {
|
|
516
|
-
throw Errors.ErrThreadReadKeyRequired;
|
|
517
|
-
}
|
|
518
|
-
// 添加线程到网络
|
|
519
|
-
console.debug(`manager: adding thread to net ${id}`);
|
|
520
|
-
try {
|
|
521
|
-
await this.network.addThread(addr, {
|
|
522
|
-
threadKey: key,
|
|
523
|
-
logKey: opts.logKey,
|
|
524
|
-
token: opts.token
|
|
525
|
-
});
|
|
526
|
-
} catch (err) {
|
|
527
|
-
throw err;
|
|
528
|
-
}
|
|
529
|
-
console.debug(`manager: added thread to net ${id}`);
|
|
530
|
-
|
|
531
|
-
// 包装数据库
|
|
532
|
-
let store: TxnDatastoreExtended;
|
|
533
|
-
let dbOpts: NewOptions;
|
|
534
|
-
try {
|
|
535
|
-
const collections = opts.collections || [];
|
|
536
|
-
const name = opts.name || '';
|
|
537
|
-
[store, dbOpts] = await this.wrapDB(this.store, id, this.opts, name, collections);
|
|
538
|
-
} catch (err) {
|
|
539
|
-
throw err;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// 创建新数据库
|
|
543
|
-
let db: ThreadDb;
|
|
544
|
-
try {
|
|
545
|
-
db = await ThreadDb.newDB(store, this.network, id, dbOpts);
|
|
546
|
-
} catch (err) {
|
|
547
|
-
throw err;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// 添加数据库到管理器
|
|
551
|
-
// await this.lock.acquire('dbs', async () => {
|
|
552
|
-
this.dbs.set(id.toString(), db);
|
|
553
|
-
// });
|
|
554
|
-
|
|
555
|
-
// 导入数据库状态
|
|
556
|
-
const readKey = key.read();
|
|
557
|
-
if (!readKey) {
|
|
558
|
-
throw new Error(`read key not found for thread ${id}`);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// 创建行读取器
|
|
562
|
-
const lineReader = new LineReader(ioReader);
|
|
563
|
-
const textDecoder = new TextDecoder();
|
|
564
|
-
|
|
565
|
-
// 读取第一行并更新线程信息的日志头
|
|
566
|
-
let stateValue = "";
|
|
567
|
-
try {
|
|
568
|
-
const value = await lineReader.readLine();
|
|
569
|
-
if (value) {
|
|
570
|
-
stateValue = value;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
} catch (err) {
|
|
574
|
-
throw err;
|
|
575
|
-
}
|
|
576
|
-
if (stateValue == "") {
|
|
577
|
-
throw new Error(`empty state value for thread ${id}`);
|
|
578
|
-
}
|
|
579
|
-
// 移除头部32位hash
|
|
580
|
-
stateValue = stateValue.slice(32);
|
|
581
|
-
|
|
582
|
-
// 更新线程信息的日志头
|
|
583
|
-
const logs = stateValue.split(';');
|
|
584
|
-
const pbLogs: net_pb.pb.Log[] = [];
|
|
585
|
-
|
|
586
|
-
for (const log of logs) {
|
|
587
|
-
try {
|
|
588
|
-
// 解码 multibase 格式
|
|
589
|
-
const data = multibaseDecode(log);
|
|
590
|
-
// 解析 protobuf
|
|
591
|
-
const pbLog = net_pb.pb.Log.decode(data);
|
|
592
|
-
pbLogs.push(pbLog);
|
|
593
|
-
} catch (err) {
|
|
594
|
-
// 忽略错误,继续处理
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// 预加载日志
|
|
600
|
-
try {
|
|
601
|
-
await this.network.preLoadLogs(id, pbLogs);
|
|
602
|
-
} catch (err) {
|
|
603
|
-
await this.deleteDB(id, false);
|
|
604
|
-
throw err;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// 导入数据库状态
|
|
608
|
-
try {
|
|
609
|
-
await this.importDBStateFromReader(id, lineReader, readKey);
|
|
610
|
-
} catch (err) {
|
|
611
|
-
await this.deleteDB(id, false);
|
|
612
|
-
throw err;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Browser-compatible version to export DB to file
|
|
618
|
-
* @param ctx Context
|
|
619
|
-
* @param id ThreadID
|
|
620
|
-
* @param fileName Suggested file name for download
|
|
621
|
-
* @param readKey Optional encryption key
|
|
622
|
-
* @returns Promise resolving to ThreadInfo
|
|
623
|
-
*/
|
|
624
|
-
async exportDBToFile(
|
|
625
|
-
ctx: Context,
|
|
626
|
-
id: ThreadID,
|
|
627
|
-
fileName: string,
|
|
628
|
-
readKey?: SymmetricKey
|
|
629
|
-
): Promise<ThreadInfo> {
|
|
630
|
-
console.debug(`manager: exporting db ${id.toString()} to file download`);
|
|
631
|
-
|
|
632
|
-
// Get thread logs similar to original function
|
|
633
|
-
let logState = "";
|
|
634
|
-
let logs: net_pb.pb.ILog[];
|
|
635
|
-
let threadInfo: ThreadInfo;
|
|
636
|
-
|
|
637
|
-
[logs, threadInfo] = await this.network.getPbLogs(id);
|
|
638
|
-
|
|
639
|
-
// Build log state string
|
|
640
|
-
for (let i = 0; logs &&i < logs.length; i++) {
|
|
641
|
-
if (!logs[i]) {
|
|
642
|
-
continue; // Skip undefined logs
|
|
643
|
-
}
|
|
644
|
-
const log = logs[i] as net_pb.pb.ILog;
|
|
645
|
-
const logBytes = net_pb.pb.Log.encode(log).finish();
|
|
646
|
-
const mbaseLog = multibase.encode('base64', logBytes);
|
|
647
|
-
|
|
648
|
-
if (i === 0) {
|
|
649
|
-
logState = mbaseLog.toString();
|
|
650
|
-
} else {
|
|
651
|
-
logState = `${logState};${mbaseLog.toString()}`;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Create content in memory instead of writing to file
|
|
656
|
-
let content = logState + "\n";
|
|
657
|
-
|
|
658
|
-
// Get database
|
|
659
|
-
const db = this.dbs.get(id.toString());
|
|
660
|
-
if (!db) {
|
|
661
|
-
throw Errors.ErrDBNotFound;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Create transaction
|
|
665
|
-
const txn = await db.datastore.newTransactionExtended(true);
|
|
666
|
-
|
|
667
|
-
try {
|
|
668
|
-
// Similar query logic, but accumulating in memory
|
|
669
|
-
const q = new Query();
|
|
670
|
-
const baseKey = new Key('/');
|
|
671
|
-
const i = await newIterator(txn, baseKey, q);
|
|
672
|
-
|
|
673
|
-
for await (const res of i.iter.next()) {
|
|
674
|
-
if (res.error) {
|
|
675
|
-
throw res.error;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
let line: string;
|
|
679
|
-
if (readKey) {
|
|
680
|
-
const encBytes = await readKey.encrypt(res.entry.value);
|
|
681
|
-
const mValue = multibase.encode('base64', encBytes);
|
|
682
|
-
line = `${res.entry.key}|${mValue.toString()}`;
|
|
683
|
-
} else {
|
|
684
|
-
const mValue = multibase.encode('base64', res.entry.value);
|
|
685
|
-
line = `${res.entry.key}|${mValue.toString()}`;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
content += line + "\n";
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
i.close();
|
|
692
|
-
txn.discard();
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
// Create blob and trigger download
|
|
696
|
-
const blob = new Blob([content], { type: 'text/plain' });
|
|
697
|
-
const url = URL.createObjectURL(blob);
|
|
698
|
-
|
|
699
|
-
// Create download link
|
|
700
|
-
const a = document.createElement('a');
|
|
701
|
-
a.href = url;
|
|
702
|
-
a.download = fileName || `db-export-${id.toString().substring(0, 8)}.txt`;
|
|
703
|
-
|
|
704
|
-
// Append to body, click and remove
|
|
705
|
-
document.body.appendChild(a);
|
|
706
|
-
a.click();
|
|
707
|
-
|
|
708
|
-
// Cleanup
|
|
709
|
-
setTimeout(() => {
|
|
710
|
-
document.body.removeChild(a);
|
|
711
|
-
URL.revokeObjectURL(url);
|
|
712
|
-
}, 100);
|
|
713
|
-
|
|
714
|
-
return threadInfo;
|
|
715
|
-
} catch (err) {
|
|
716
|
-
txn.discard();
|
|
717
|
-
throw err;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* 从读取器导入数据库状态
|
|
727
|
-
* @param id 线程ID
|
|
728
|
-
* @param reader 可读流读取器
|
|
729
|
-
* @param readKey 用于解密的对称密钥
|
|
730
|
-
*/
|
|
731
|
-
async importDBStateFromReader(
|
|
732
|
-
id: ThreadID,
|
|
733
|
-
lineReader: LineReader,
|
|
734
|
-
readKey: SymmetricKey
|
|
735
|
-
): Promise<void> {
|
|
736
|
-
console.debug("manager: importing db state from reader");
|
|
737
|
-
|
|
738
|
-
// 检查数据库是否存在
|
|
739
|
-
let db: ThreadDb | undefined;
|
|
740
|
-
// await this.lock.acquire('dbs', async () => {
|
|
741
|
-
db = this.dbs.get(id.toString());
|
|
742
|
-
// });
|
|
743
|
-
|
|
744
|
-
if (!db) {
|
|
745
|
-
throw Errors.ErrDBNotFound;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// 获取索引函数
|
|
749
|
-
const indexFunc = db.defaultIndexFunc();
|
|
750
|
-
|
|
751
|
-
// 设置行读取
|
|
752
|
-
const textDecoder = new TextDecoder();
|
|
753
|
-
let done = false;
|
|
754
|
-
let line:string|null = ""
|
|
755
|
-
while (true) {
|
|
756
|
-
line = await lineReader.readLine();
|
|
757
|
-
if (!line) {
|
|
758
|
-
break
|
|
759
|
-
}
|
|
760
|
-
// 创建事务
|
|
761
|
-
let txn:Transaction;
|
|
762
|
-
try {
|
|
763
|
-
txn = await db.datastore.newTransactionExtended(false);
|
|
764
|
-
} catch (err) {
|
|
765
|
-
throw new Error(`创建事务错误: ${err instanceof Error ? err.message : String(err)}`);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
try {
|
|
769
|
-
// 解析键值对
|
|
770
|
-
const kv = line.split('|');
|
|
771
|
-
if (kv.length !== 2) {
|
|
772
|
-
txn.discard();
|
|
773
|
-
throw new Error('无效的记录格式');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const key = kv[0];
|
|
777
|
-
const mValue = kv[1] || '';
|
|
778
|
-
|
|
779
|
-
// 使用multibase解码值
|
|
780
|
-
let encValue: Uint8Array;
|
|
781
|
-
try {
|
|
782
|
-
const decoded = multibaseDecode(mValue);
|
|
783
|
-
encValue = decoded;
|
|
784
|
-
} catch (err) {
|
|
785
|
-
await txn.discard();
|
|
786
|
-
throw new Error(`multibase解码失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// 如有需要解密记录
|
|
790
|
-
let decValue = encValue;
|
|
791
|
-
if (readKey) {
|
|
792
|
-
try {
|
|
793
|
-
decValue = await readKey.decrypt(encValue);
|
|
794
|
-
} catch (err) {
|
|
795
|
-
await txn.discard();
|
|
796
|
-
throw new Error(`解密值失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// 创建数据存储键
|
|
801
|
-
const setKey = new Key(key||"");
|
|
802
|
-
|
|
803
|
-
// 检查键是否已存在
|
|
804
|
-
try {
|
|
805
|
-
const exists = await txn.has(setKey);
|
|
806
|
-
if (exists) {
|
|
807
|
-
txn.discard();
|
|
808
|
-
continue; // 跳过此记录
|
|
809
|
-
}
|
|
810
|
-
} catch (err) {
|
|
811
|
-
txn.discard();
|
|
812
|
-
throw new Error(`检查键存在性失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// 存储值
|
|
816
|
-
try {
|
|
817
|
-
await txn.put(setKey, decValue);
|
|
818
|
-
} catch (err) {
|
|
819
|
-
txn.discard();
|
|
820
|
-
throw new Error(`存储值失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// 从键中提取集合名称(倒数第二个部分)
|
|
824
|
-
const parts = key?.split('/');
|
|
825
|
-
if (!parts ||parts.length < 2) {
|
|
826
|
-
txn.discard();
|
|
827
|
-
throw new Error('无效的键格式: 未找到集合名称');
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
const collection = parts[parts.length - 2]||"";
|
|
831
|
-
|
|
832
|
-
// 应用索引
|
|
833
|
-
try {
|
|
834
|
-
await indexFunc(collection, setKey, txn, decValue);
|
|
835
|
-
} catch (err) {
|
|
836
|
-
txn.discard();
|
|
837
|
-
throw new Error(`应用索引失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// 提交事务
|
|
841
|
-
try {
|
|
842
|
-
await txn.commit();
|
|
843
|
-
} catch (err) {
|
|
844
|
-
txn.discard();
|
|
845
|
-
throw new Error(`提交事务失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
846
|
-
}
|
|
847
|
-
} catch (err) {
|
|
848
|
-
// 确保在任何失败时丢弃事务
|
|
849
|
-
try {
|
|
850
|
-
txn.discard();
|
|
851
|
-
} catch {
|
|
852
|
-
// 忽略丢弃时的错误
|
|
853
|
-
}
|
|
854
|
-
throw err;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
/**
|
|
862
|
-
* wrapDB 复制管理器的基本配置,
|
|
863
|
-
* 使用 ID 前缀包装数据存储,
|
|
864
|
-
* 并将指定的集合配置与基本配置合并
|
|
865
|
-
*/
|
|
866
|
-
async wrapDB(
|
|
867
|
-
store: TxnDatastoreExtended,
|
|
868
|
-
id: ThreadID,
|
|
869
|
-
base: NewOptions,
|
|
870
|
-
name: string,
|
|
871
|
-
collections: ICollectionConfig[]
|
|
872
|
-
): Promise<[TxnDatastoreExtended, NewOptions, Error | null]> {
|
|
873
|
-
const isValid = await this.validateThreadId(id.toString());
|
|
874
|
-
if (!isValid) {
|
|
875
|
-
return [null as unknown as TxnDatastoreExtended, null as unknown as NewOptions, new Error('Invalid Thread ID')];
|
|
876
|
-
}
|
|
877
|
-
// 创建前缀转换器并包装数据存储
|
|
878
|
-
const prefix = dsManagerBaseKey.child(new Key(id.toString())).toString();
|
|
879
|
-
const transform = new PrefixTransform(prefix);
|
|
880
|
-
//const wrappedStore = new TransformedDatastore(store, transform);
|
|
881
|
-
const wrappedStore = createTransformedDatastore(store, transform);
|
|
882
|
-
// 创建新的选项对象
|
|
883
|
-
const opts: NewOptions = {
|
|
884
|
-
name: name,
|
|
885
|
-
collections: [...(base.collections || []), ...collections],
|
|
886
|
-
eventCodec: base.eventCodec,
|
|
887
|
-
debug: base.debug
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
return [wrappedStore, opts, null];
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
async listDBs(): Promise<Map<ThreadID, ThreadDb>> {
|
|
894
|
-
const dbs = new Map();
|
|
895
|
-
// await this.lock.acquire('dbs', async () => {
|
|
896
|
-
for (const [idStr, db] of this.dbs) {
|
|
897
|
-
const id = ThreadID.fromString(idStr);
|
|
898
|
-
await this.network.getThread(id);
|
|
899
|
-
dbs.set(id, db);
|
|
900
|
-
}
|
|
901
|
-
// });
|
|
902
|
-
return dbs;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
async ifSyncDBToDCSuccess(tId: string): Promise<boolean> {
|
|
907
|
-
try {
|
|
908
|
-
const [storeUnit,err] = await this.chainUtil.objectState(tId);
|
|
909
|
-
if (!storeUnit || err) return false;
|
|
910
|
-
|
|
911
|
-
return new Promise((resolve) => {
|
|
912
|
-
const timeout = setTimeout(() => resolve(false), PullTimeout);
|
|
913
|
-
|
|
914
|
-
const checkPeers = async () => {
|
|
915
|
-
for (const pid of Object.keys(storeUnit.peers)) {
|
|
916
|
-
try {
|
|
917
|
-
const peerId = peerIdFromString(pid);
|
|
918
|
-
const threadId = ThreadID.fromString(tId);
|
|
919
|
-
const remoteInfo = await this.network.getThreadFromPeer(threadId, peerId,{});
|
|
920
|
-
const localInfo = await this.network.getThread( threadId);
|
|
921
|
-
if (this.compareThreadSync(localInfo, remoteInfo, storeUnit)) {
|
|
922
|
-
clearTimeout(timeout);
|
|
923
|
-
resolve(true);
|
|
924
|
-
return;
|
|
925
|
-
}
|
|
926
|
-
} catch {
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
resolve(false);
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
checkPeers();
|
|
934
|
-
});
|
|
935
|
-
} catch {
|
|
936
|
-
return false;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
private compareThreadSync(local: ThreadInfo, remote: ThreadInfo, storeUnit: StoreunitInfo): boolean {
|
|
941
|
-
for (const logInfo of local.logs) {
|
|
942
|
-
if (!storeUnit.logs.has(logInfo.id.toString())) {
|
|
943
|
-
continue;
|
|
944
|
-
}
|
|
945
|
-
if (!logInfo.head){
|
|
946
|
-
continue;
|
|
947
|
-
}
|
|
948
|
-
const remoteLog = remote.logs.find(l => l.id === logInfo.id);
|
|
949
|
-
if (!remoteLog?.head) {
|
|
950
|
-
return false;
|
|
951
|
-
}
|
|
952
|
-
if (!remoteLog || logInfo.head.counter > remoteLog.head.counter) {
|
|
953
|
-
return false;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return true;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
async ifDbInitSuccess(tid: ThreadID): Promise<boolean> {
|
|
960
|
-
try {
|
|
961
|
-
const logKey = await this.getLogKey(tid);
|
|
962
|
-
const lid = peerIdFromPrivateKey(logKey);
|
|
963
|
-
const [threadInfo,err] = await this.chainUtil.objectState(tid.toString());
|
|
964
|
-
if (!threadInfo || err) {
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
const exist = threadInfo?threadInfo?.logs.has(lid.toString()):false;
|
|
968
|
-
return exist;
|
|
969
|
-
} catch {
|
|
970
|
-
return false;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
async syncDBFromDC(
|
|
975
|
-
ctx: Context,
|
|
976
|
-
threadid: string,
|
|
977
|
-
dbname: string,
|
|
978
|
-
dbAddr: string,
|
|
979
|
-
b32Rk: string,
|
|
980
|
-
b32Sk: string,
|
|
981
|
-
block: boolean,
|
|
982
|
-
collectionInfos: ICollectionConfig[]
|
|
983
|
-
): Promise<Error | null> {
|
|
984
|
-
try {
|
|
985
|
-
const tID = await this.decodeThreadId(threadid);
|
|
986
|
-
const logKey = await this.getLogKey(tID);
|
|
987
|
-
const lid = peerIdFromPrivateKey(logKey);
|
|
988
|
-
// await this.dc._connectToObjNodes(threadid);
|
|
989
|
-
//await this.addLogToThreadStart(ctx,tID, lid);
|
|
990
|
-
const sk = SymmetricKey.fromString(b32Sk);
|
|
991
|
-
const rk = SymmetricKey.fromString(b32Rk);
|
|
992
|
-
const threadKey = new ThreadKey(sk, rk);
|
|
993
|
-
let connectedFlag = false ;
|
|
994
|
-
let connectedConn :Connection | undefined;
|
|
995
|
-
let fullMultiAddr :TMultiaddr | undefined;
|
|
996
|
-
let threadAddr :TMultiaddr;
|
|
997
|
-
let connectedPeerId :PeerId;
|
|
998
|
-
let dbMultiAddr :TMultiaddr;
|
|
999
|
-
if (dbAddr.length > 0) {
|
|
1000
|
-
try {
|
|
1001
|
-
//
|
|
1002
|
-
connectedConn = await this.dc.dcNodeClient?.libp2p.dial(multiaddr(dbAddr), {
|
|
1003
|
-
signal: AbortSignal.timeout(dial_timeout)
|
|
1004
|
-
});
|
|
1005
|
-
} catch (error) {
|
|
1006
|
-
const errMsg = (error as any).message;
|
|
1007
|
-
console.log("connect to %s catch return, error:%s",dbAddr, errMsg);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
if (connectedConn) {//连接成功
|
|
1012
|
-
connectedPeerId = connectedConn?.remotePeer;
|
|
1013
|
-
dbMultiAddr = connectedConn.remoteAddr;
|
|
1014
|
-
|
|
1015
|
-
}else{//从区块链中获取节点信息,再连接
|
|
1016
|
-
const [connectedAddr, peers] = await this.dc._connectToObjNodes(threadid);
|
|
1017
|
-
if (!connectedAddr) {
|
|
1018
|
-
throw new Error("connect to obj nodes failed");
|
|
1019
|
-
}
|
|
1020
|
-
dbMultiAddr = connectedAddr;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
const collections = collectionInfos.map(info => ({
|
|
1028
|
-
name: info.name,
|
|
1029
|
-
schema: info.schema,
|
|
1030
|
-
indexes: info.indexes || []
|
|
1031
|
-
}));
|
|
1032
|
-
|
|
1033
|
-
const dbOpts: NewOptions = {
|
|
1034
|
-
name: dbname,
|
|
1035
|
-
collections: collections,
|
|
1036
|
-
key: threadKey,
|
|
1037
|
-
logKey: logKey,
|
|
1038
|
-
block: block,
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
// Delete existing database if present
|
|
1044
|
-
try {
|
|
1045
|
-
await this.deleteDB(tID, false);
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
const errMsg = (error as any).message;
|
|
1048
|
-
if ( errMsg != Errors.ErrDBNotFound.message && errMsg != Errors.ErrThreadNotFound.message) {
|
|
1049
|
-
throw error;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
const threadMultiaddr = new ThreadMuliaddr(dbMultiAddr, tID);
|
|
1053
|
-
await this.newDBFromAddr(threadMultiaddr, threadKey, dbOpts);
|
|
1054
|
-
return null;
|
|
1055
|
-
} catch (error) {
|
|
1056
|
-
const errMsg = (error as any).message;
|
|
1057
|
-
if (errMsg == Errors.ErrorThreadIDValidation.message) {
|
|
1058
|
-
return errMsg;
|
|
1059
|
-
}
|
|
1060
|
-
return error as Error;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
async getDBRecordsCount(threadid: string): Promise<number> {
|
|
1065
|
-
let count = 0;
|
|
1066
|
-
try {
|
|
1067
|
-
const tid = await this.decodeThreadId(threadid);
|
|
1068
|
-
const threadInfo = await this.network.getThread( tid);
|
|
1069
|
-
if (!threadInfo) {
|
|
1070
|
-
return count;
|
|
1071
|
-
}
|
|
1072
|
-
for (const logInfo of threadInfo.logs ) {
|
|
1073
|
-
if (!logInfo.head) {
|
|
1074
|
-
continue;
|
|
1075
|
-
}
|
|
1076
|
-
if (count < logInfo.head.counter) {
|
|
1077
|
-
count = logInfo.head.counter;
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
console.error(`Error getting records count for thread ${threadid}:`, error);
|
|
1082
|
-
}
|
|
1083
|
-
return count;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
async addLogToThread(ctx: Context, id: ThreadID, lid: PeerId): Promise<void> {
|
|
1088
|
-
let blockHeight: number;
|
|
1089
|
-
try {
|
|
1090
|
-
blockHeight = (await this.chainUtil.getBlockHeight())||0;
|
|
1091
|
-
} catch (err) {
|
|
1092
|
-
throw err;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// 生成用户签名
|
|
1096
|
-
const hValue: Uint8Array = uint32ToLittleEndianBytes(
|
|
1097
|
-
blockHeight ? blockHeight : 0
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
const peerIdValue: Uint8Array = new TextEncoder().encode(
|
|
1101
|
-
this.connectedDc.nodeAddr?.getPeerId() || ""
|
|
1102
|
-
);
|
|
1103
|
-
|
|
1104
|
-
const preSign = new Uint8Array([
|
|
1105
|
-
...new TextEncoder().encode(id.toString()),
|
|
1106
|
-
...new TextEncoder().encode(lid.toString()),
|
|
1107
|
-
...hValue,
|
|
1108
|
-
...peerIdValue,
|
|
1109
|
-
]);
|
|
1110
|
-
|
|
1111
|
-
let signature: Uint8Array;
|
|
1112
|
-
try {
|
|
1113
|
-
signature = await this.context.sign(preSign);
|
|
1114
|
-
} catch (err) {
|
|
1115
|
-
throw err;
|
|
1116
|
-
}
|
|
1117
|
-
if (!this.connectedDc?.client) {
|
|
1118
|
-
throw Errors.ErrNoDcPeerConnected
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
const opts: NewThreadOptions = {
|
|
1122
|
-
token: new ThreadToken(this.connectedDc.client.token),
|
|
1123
|
-
blockHeight: blockHeight,
|
|
1124
|
-
signature: signature,
|
|
1125
|
-
};
|
|
1126
|
-
const dbClient = newGrpcClient(this.connectedDc.client,this.network);
|
|
1127
|
-
await dbClient.addLogToThread(id.toString(),lid.toString(),opts);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
async addLogToThreadStart(
|
|
1133
|
-
ctx: Context,
|
|
1134
|
-
id: ThreadID,
|
|
1135
|
-
lid: PeerId
|
|
1136
|
-
) : Promise<void> {
|
|
1137
|
-
|
|
1138
|
-
if (!ctx){
|
|
1139
|
-
ctx = createContext(30000);
|
|
1140
|
-
}
|
|
1141
|
-
const abortController = new AbortController();
|
|
1142
|
-
const signal = ctx?.signal || abortController.signal;
|
|
1143
|
-
const [storeUnit,err] = await this.chainUtil.objectState(id.toString());
|
|
1144
|
-
if (storeUnit && !err) {
|
|
1145
|
-
const userPubkey = this.context.getPublicKey();
|
|
1146
|
-
let findFlag = false;
|
|
1147
|
-
for (const user of storeUnit.users) {
|
|
1148
|
-
//移除0x前缀
|
|
1149
|
-
const noPrefixUser = user.replace("0x", "");
|
|
1150
|
-
if (noPrefixUser === userPubkey.toString()) {
|
|
1151
|
-
findFlag = true;
|
|
1152
|
-
break;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
if (!findFlag) {
|
|
1156
|
-
throw new Error('user not in the thread');
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// 处理超时
|
|
1161
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
1162
|
-
if (ctx.deadline) {
|
|
1163
|
-
const timeout = ctx.deadline.getTime() - Date.now();
|
|
1164
|
-
if (timeout > 0) {
|
|
1165
|
-
timeoutId = setTimeout(() => {
|
|
1166
|
-
abortController.abort();
|
|
1167
|
-
}, timeout);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
let count = 0;
|
|
1171
|
-
const maxCount = 10;
|
|
1172
|
-
let endFlag = false;
|
|
1173
|
-
try{
|
|
1174
|
-
await this.addLogToThread(ctx, id, lid);
|
|
1175
|
-
}catch (error) {//允许报错
|
|
1176
|
-
}
|
|
1177
|
-
let stopped = false;
|
|
1178
|
-
const tick = async () => {
|
|
1179
|
-
if (signal.aborted || stopped) {
|
|
1180
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
if (await this.ifDbInitSuccess(id)) {
|
|
1185
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1186
|
-
endFlag = true;
|
|
1187
|
-
stopped = true;
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
if (count >= maxCount) {
|
|
1192
|
-
try {
|
|
1193
|
-
await this.addLogToThread(ctx, id, lid);
|
|
1194
|
-
} catch (error) {
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1197
|
-
count = 0;
|
|
1198
|
-
} else {
|
|
1199
|
-
count++;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
setTimeout(tick, 1000); // 递归调度
|
|
1203
|
-
};
|
|
1204
|
-
tick();
|
|
1205
|
-
await new Promise<void>((resolve) => {
|
|
1206
|
-
// Add a resolving condition to the interval
|
|
1207
|
-
const checkFlag = setInterval(() => {
|
|
1208
|
-
if (endFlag) {
|
|
1209
|
-
clearInterval(checkFlag);
|
|
1210
|
-
resolve();
|
|
1211
|
-
}
|
|
1212
|
-
}, 1000);
|
|
1213
|
-
|
|
1214
|
-
// Also make sure the abort signal resolves the promise
|
|
1215
|
-
signal.addEventListener('abort', () => {
|
|
1216
|
-
clearInterval(checkFlag);
|
|
1217
|
-
resolve();
|
|
1218
|
-
});
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
async newDB(
|
|
1225
|
-
dbname: string,
|
|
1226
|
-
b32Rk: string,
|
|
1227
|
-
b32Sk: string,
|
|
1228
|
-
collectionInfos: ICollectionConfig[]
|
|
1229
|
-
): Promise<[string, Error | null]> {
|
|
1230
|
-
if (!this.connectedDc?.client) {
|
|
1231
|
-
return ['', Errors.ErrNoDcPeerConnected];
|
|
1232
|
-
}
|
|
1233
|
-
try {
|
|
1234
|
-
const dbClient = newGrpcClient(this.connectedDc.client,this.network);
|
|
1235
|
-
const tidStr = await dbClient.requestThreadID();
|
|
1236
|
-
const threadID = await this.decodeThreadId(tidStr);
|
|
1237
|
-
const logKey = await this.getLogKey(threadID);
|
|
1238
|
-
const lpk = logKey.publicKey;
|
|
1239
|
-
const lid = peerIdFromPrivateKey(logKey)
|
|
1240
|
-
const sk = SymmetricKey.fromString(b32Sk);
|
|
1241
|
-
const rk = SymmetricKey.fromString(b32Rk);
|
|
1242
|
-
const threadKey = new ThreadKey(sk, rk);
|
|
1243
|
-
const blockHeight = (await this.chainUtil.getBlockHeight())||0;
|
|
1244
|
-
|
|
1245
|
-
const hValue: Uint8Array = uint32ToLittleEndianBytes(
|
|
1246
|
-
blockHeight ? blockHeight : 0
|
|
1247
|
-
);
|
|
1248
|
-
if (!this.connectedDc?.nodeAddr) {
|
|
1249
|
-
return ['', Errors.ErrNodeAddrIsNull];
|
|
1250
|
-
}
|
|
1251
|
-
const rPeerId = await extractPeerIdFromMultiaddr(this.connectedDc.nodeAddr);
|
|
1252
|
-
const peerIdValue: Uint8Array = new TextEncoder().encode(rPeerId.toString());
|
|
1253
|
-
const sizeValue: Uint8Array = uint64ToLittleEndianBytes(50<<20); //数据库固定大小50M
|
|
1254
|
-
const tidUnit8Array = new TextEncoder().encode(tidStr);
|
|
1255
|
-
|
|
1256
|
-
const typeValue: Uint8Array = uint32ToLittleEndianBytes(Type.Threaddbtype);
|
|
1257
|
-
const preSign = new Uint8Array([
|
|
1258
|
-
...tidUnit8Array,
|
|
1259
|
-
...sizeValue,
|
|
1260
|
-
...hValue,
|
|
1261
|
-
...typeValue,
|
|
1262
|
-
...peerIdValue
|
|
1263
|
-
]);
|
|
1264
|
-
const signature = await this.context.sign(preSign);
|
|
1265
|
-
|
|
1266
|
-
// Create thread options
|
|
1267
|
-
const opts: NewThreadOptions = {
|
|
1268
|
-
threadKey: threadKey,
|
|
1269
|
-
logKey: logKey,
|
|
1270
|
-
token: new ThreadToken(this.connectedDc.client.token),
|
|
1271
|
-
blockHeight: blockHeight,
|
|
1272
|
-
signature: signature,
|
|
1273
|
-
};
|
|
1274
|
-
const threadInfo = await dbClient.createThread(threadID.toString(), opts);
|
|
1275
|
-
const collections = collectionInfos.map(info => ({
|
|
1276
|
-
name: info.name,
|
|
1277
|
-
schema: info.schema,
|
|
1278
|
-
indexes: info.indexes || []
|
|
1279
|
-
}));
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
const dbOpts: NewOptions = {
|
|
1284
|
-
name: dbname,
|
|
1285
|
-
collections: collections,
|
|
1286
|
-
key: threadKey,
|
|
1287
|
-
logKey: logKey,
|
|
1288
|
-
block: true,
|
|
1289
|
-
};
|
|
1290
|
-
|
|
1291
|
-
// Try creating database
|
|
1292
|
-
const errors: string[] = [];
|
|
1293
|
-
for (const multiAddr of threadInfo.addrs) {
|
|
1294
|
-
try {
|
|
1295
|
-
|
|
1296
|
-
await this.newDBFromAddr(multiAddr, threadKey, dbOpts);
|
|
1297
|
-
break;
|
|
1298
|
-
} catch (error:any) {
|
|
1299
|
-
errors.push(error.message);
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (errors.length === threadInfo.addrs.length) {
|
|
1304
|
-
throw new Error(`create db failed:${errors.join(',')}`);
|
|
1305
|
-
}
|
|
1306
|
-
const ctx = createContext(30000);
|
|
1307
|
-
await this.addLogToThreadStart(ctx,threadID, lid);
|
|
1308
|
-
return [threadID.toString(), null];
|
|
1309
|
-
} catch (error) {
|
|
1310
|
-
return ['', error as Error];
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
async refreshDBFromDC(threadId:string): Promise<Error | null> {
|
|
1316
|
-
try {
|
|
1317
|
-
const tId = await this.decodeThreadId(threadId);
|
|
1318
|
-
await this.network.pullThread( tId,600, { multiPeersFlag: true });
|
|
1319
|
-
return null;
|
|
1320
|
-
} catch (error) {
|
|
1321
|
-
return error as Error;
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
async syncDBToDC(threadId:string): Promise<Error | null> {
|
|
1326
|
-
if (!this.network) {
|
|
1327
|
-
return Errors.ErrP2pNetworkNotInit;
|
|
1328
|
-
}
|
|
1329
|
-
try {
|
|
1330
|
-
const tId = await this.decodeThreadId(threadId);
|
|
1331
|
-
await this.network.exchange( tId);
|
|
1332
|
-
return null;
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
return error as Error;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
async upgradeCollections(threadId: string, configs: ICollectionConfig[]): Promise<Error | null> {
|
|
1340
|
-
try {
|
|
1341
|
-
const tId = await this.decodeThreadId(threadId);
|
|
1342
|
-
const db = this.dbs.get(tId.toString());
|
|
1343
|
-
if (!db) {
|
|
1344
|
-
return Errors.ErrDBNotFound;
|
|
1345
|
-
}
|
|
1346
|
-
await db.upgradeCollections(configs);
|
|
1347
|
-
return null;
|
|
1348
|
-
} catch (error) {
|
|
1349
|
-
return error as Error;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
private async decodeThreadId(threadid: string): Promise<ThreadID> {
|
|
1357
|
-
if (!threadid) {
|
|
1358
|
-
throw new Error('Thread ID is empty');
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
try {
|
|
1362
|
-
// 基本格式验证
|
|
1363
|
-
if (!/^[a-zA-Z0-9]+$/.test(threadid)) {
|
|
1364
|
-
throw Errors.ErrorThreadIDValidation;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
// 尝试解码
|
|
1368
|
-
const threadID = ThreadID.fromString(threadid);
|
|
1369
|
-
|
|
1370
|
-
// 验证长度
|
|
1371
|
-
const bytes = threadID.toBytes();
|
|
1372
|
-
if (bytes.length < 32) {
|
|
1373
|
-
throw new Error('Thread ID too short');
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
return threadID;
|
|
1377
|
-
} catch (error) {
|
|
1378
|
-
const errMsg = (error as any).message;
|
|
1379
|
-
if (errMsg === Errors.ErrorThreadIDValidation.message) {
|
|
1380
|
-
throw error;
|
|
1381
|
-
}
|
|
1382
|
-
throw new Error(`Failed to decode thread ID: $errMsg}`);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
// 为了方便使用,可以添加一个验证方法
|
|
1387
|
-
async validateThreadId(threadid: string): Promise<boolean> {
|
|
1388
|
-
try {
|
|
1389
|
-
await this.decodeThreadId(threadid);
|
|
1390
|
-
return true;
|
|
1391
|
-
} catch {
|
|
1392
|
-
return false;
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
async close(): Promise<void> {
|
|
1397
|
-
// await this.lock.acquire('dbs', async () => {
|
|
1398
|
-
for (const db of this.dbs.values()) {
|
|
1399
|
-
await db.close();
|
|
1400
|
-
}
|
|
1401
|
-
this.dbs.clear();
|
|
1402
|
-
// });
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
/**
|
|
1406
|
-
* Gets a database by ID
|
|
1407
|
-
* @param ctx The context for the operation
|
|
1408
|
-
* @param id The thread ID of the database
|
|
1409
|
-
* @param opts Optional managed options
|
|
1410
|
-
* @returns Promise resolving to the database instance
|
|
1411
|
-
* @throws Error if the database cannot be found
|
|
1412
|
-
*/
|
|
1413
|
-
async getDB(id: ThreadID, opts?: ManagedOptions): Promise<ThreadDb> {
|
|
1414
|
-
console.debug(`manager: getting db ${id}`);
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
console.debug(`manager: getting thread ${id} from net`);
|
|
1419
|
-
try {
|
|
1420
|
-
// Get thread from the network
|
|
1421
|
-
await this.network.getThread(id, { token: opts?.token });
|
|
1422
|
-
console.debug(`manager: got thread ${id} from net`);
|
|
1423
|
-
} catch (err) {
|
|
1424
|
-
throw err;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const db = this.dbs.get(id.toString());
|
|
1428
|
-
if (!db) {
|
|
1429
|
-
throw Errors.ErrDBNotFound;
|
|
1430
|
-
}
|
|
1431
|
-
return db;
|
|
1432
|
-
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
async getDBInfo(id: ThreadID, opts?: ManagedOptions): Promise<[IDBInfo|null, Error|null]> {
|
|
1437
|
-
let dbInfo: IDBInfo|null = null;
|
|
1438
|
-
try {
|
|
1439
|
-
const db = this.dbs.get(id.toString());
|
|
1440
|
-
if (!db) {
|
|
1441
|
-
throw Errors.ErrDBNotFound;
|
|
1442
|
-
}
|
|
1443
|
-
dbInfo = await db.getDBInfo(opts);
|
|
1444
|
-
if (!dbInfo || dbInfo === null) {
|
|
1445
|
-
throw new Error(`No info available for db ${id}`);
|
|
1446
|
-
}
|
|
1447
|
-
}catch (err) {
|
|
1448
|
-
console.error(`Error getting DB info for ${id}:`, err);
|
|
1449
|
-
return [null, err as Error];
|
|
1450
|
-
}
|
|
1451
|
-
return [dbInfo,null];
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
// DeleteDB deletes a db by id.
|
|
1459
|
-
async deleteDB( id: ThreadID, deleteThreadFlag: boolean, opts?: ManagedOptions): Promise<void> {
|
|
1460
|
-
console.debug(`manager: deleting db ${id}`);
|
|
1461
|
-
|
|
1462
|
-
console.debug(`manager: getting thread ${id} from net`);
|
|
1463
|
-
try {
|
|
1464
|
-
await this.network.getThread(id, { token: opts?.token });
|
|
1465
|
-
console.debug(`manager: got thread ${id} from net`);
|
|
1466
|
-
} catch (err) {
|
|
1467
|
-
throw err;
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
const db = this.dbs.get(id.toString());
|
|
1471
|
-
if (!db) {
|
|
1472
|
-
throw Errors.ErrDBNotFound;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
try {
|
|
1476
|
-
await db.close();
|
|
1477
|
-
} catch (err) {
|
|
1478
|
-
throw err;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
if (deleteThreadFlag) {
|
|
1482
|
-
console.debug(`manager: deleting thread ${id} from net`);
|
|
1483
|
-
try {
|
|
1484
|
-
await this.network.deleteThread(id, { token: opts?.token, apiToken: db.connector?.token });
|
|
1485
|
-
console.debug(`manager: deleted thread ${id} from net`);
|
|
1486
|
-
} catch (err) {
|
|
1487
|
-
throw err;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
try {
|
|
1492
|
-
await this.deleteThreadNamespace(id);
|
|
1493
|
-
} catch (err) {
|
|
1494
|
-
throw err;
|
|
1495
|
-
}
|
|
1496
|
-
// this.lock.acquire('dbs', async () => {
|
|
1497
|
-
this.dbs.delete(id.toString());
|
|
1498
|
-
console.debug(`manager: deleted db ${id}`);
|
|
1499
|
-
// });
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
private async deleteThreadNamespace(id: ThreadID): Promise<void> {
|
|
1503
|
-
const pre = dsManagerBaseKey.child(new Key(id.toString())).toString();
|
|
1504
|
-
const q = { prefix: pre, keysOnly: true };
|
|
1505
|
-
const results = this.store.query(q);
|
|
1506
|
-
for await (const result of results) {
|
|
1507
|
-
await this.store.delete(result.key);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
/**********************数据库数据操作相关**********************/
|
|
1513
|
-
/**
|
|
1514
|
-
* Create creates new instances of objects in a collection
|
|
1515
|
-
* @param threadId Thread ID string
|
|
1516
|
-
* @param collectionName Collection name
|
|
1517
|
-
* @param jsonInstance JSON string representing the instance
|
|
1518
|
-
* @returns Promise resolving to the created instance ID
|
|
1519
|
-
* @throws Error if creation fails
|
|
1520
|
-
*/
|
|
1521
|
-
async create(threadId: string, collectionName: string, jsonInstance: string): Promise<string> {
|
|
1522
|
-
// // 检查实例大小
|
|
1523
|
-
// if (jsonInstance.length > 100 * 1024) { // 100 KB
|
|
1524
|
-
// throw new Error("instance too big");
|
|
1525
|
-
// }
|
|
1526
|
-
|
|
1527
|
-
// 判断instance里面是否有_mod字段,存在则删除
|
|
1528
|
-
try {
|
|
1529
|
-
const instanceObj = JSON.parse(jsonInstance);
|
|
1530
|
-
if (instanceObj && typeof instanceObj === 'object' && '_mod' in instanceObj) {
|
|
1531
|
-
delete instanceObj._mod;
|
|
1532
|
-
jsonInstance = JSON.stringify(instanceObj);
|
|
1533
|
-
}
|
|
1534
|
-
} catch (err) {
|
|
1535
|
-
// JSON解析失败,保持原字符串不变
|
|
1536
|
-
console.warn('Failed to parse instance JSON, keeping original:', err);
|
|
1537
|
-
throw new Error("Invalid instance JSON format");
|
|
1538
|
-
}
|
|
1539
|
-
try {
|
|
1540
|
-
|
|
1541
|
-
// 解码threaddbID
|
|
1542
|
-
const tID = ThreadID.fromString(threadId);
|
|
1543
|
-
|
|
1544
|
-
// 获取threaddb数据库
|
|
1545
|
-
const threadDB = await this.getDB(tID);
|
|
1546
|
-
|
|
1547
|
-
// 获取集合
|
|
1548
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1549
|
-
if (!collection) {
|
|
1550
|
-
throw new Error("Collection does not exist");
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
// 创建实例
|
|
1554
|
-
const instanceID = await collection.create(Buffer.from(jsonInstance));
|
|
1555
|
-
// 返回实例ID
|
|
1556
|
-
return instanceID ?instanceID.toString():"";
|
|
1557
|
-
} catch (err) {
|
|
1558
|
-
console.error(`Failed to create instance: ${err instanceof Error ? err.message : String(err)}`);
|
|
1559
|
-
throw err;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
/**
|
|
1565
|
-
* Delete deletes an instance by ID
|
|
1566
|
-
* @param threadId Thread ID string
|
|
1567
|
-
* @param collectionName Collection name
|
|
1568
|
-
* @param instanceID Instance ID to delete
|
|
1569
|
-
* @throws Error if deletion fails
|
|
1570
|
-
*/
|
|
1571
|
-
async delete(threadId: string, collectionName: string, instanceID: string): Promise<void> {
|
|
1572
|
-
try {
|
|
1573
|
-
// 解码线程ID
|
|
1574
|
-
const tID = ThreadID.fromString(threadId);
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
// 获取线程数据库
|
|
1579
|
-
const threadDB = await this.getDB(tID);
|
|
1580
|
-
|
|
1581
|
-
// 获取集合
|
|
1582
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1583
|
-
if (!collection) {
|
|
1584
|
-
throw new Error("Collection does not exist");
|
|
1585
|
-
}
|
|
1586
|
-
// 删除实例
|
|
1587
|
-
await collection.delete(instanceID);
|
|
1588
|
-
} catch (err) {
|
|
1589
|
-
console.error(`Failed to delete instance: ${err instanceof Error ? err.message : String(err)}`);
|
|
1590
|
-
throw err;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
/**
|
|
1595
|
-
* Save updates an existing instance
|
|
1596
|
-
* @param threadId Thread ID string
|
|
1597
|
-
* @param collectionName Collection name
|
|
1598
|
-
* @param instance JSON string representing the instance
|
|
1599
|
-
* @throws Error if update fails
|
|
1600
|
-
*/
|
|
1601
|
-
async save(threadId: string, collectionName: string, instance: string): Promise<void> {
|
|
1602
|
-
// // 检查实例大小
|
|
1603
|
-
// if (instance.length > 100 * 1024) { // 100 KB
|
|
1604
|
-
// throw new Error("instance too big");
|
|
1605
|
-
// }
|
|
1606
|
-
|
|
1607
|
-
try {
|
|
1608
|
-
// 解码线程ID
|
|
1609
|
-
const tID = ThreadID.fromString(threadId);
|
|
1610
|
-
|
|
1611
|
-
// 判断instance里面是否有_mod字段,存在则删除
|
|
1612
|
-
try {
|
|
1613
|
-
const instanceObj = JSON.parse(instance);
|
|
1614
|
-
if (instanceObj && typeof instanceObj === 'object' && '_mod' in instanceObj) {
|
|
1615
|
-
delete instanceObj._mod;
|
|
1616
|
-
instance = JSON.stringify(instanceObj);
|
|
1617
|
-
}
|
|
1618
|
-
} catch (err) {
|
|
1619
|
-
// JSON解析失败,保持原字符串不变
|
|
1620
|
-
console.warn('Failed to parse instance JSON, keeping original:', err);
|
|
1621
|
-
throw new Error("Invalid instance JSON format");
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
// 获取线程数据库
|
|
1627
|
-
const threadDB = await this.getDB(tID);
|
|
1628
|
-
|
|
1629
|
-
// 获取集合
|
|
1630
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1631
|
-
if (!collection) {
|
|
1632
|
-
throw new Error("Collection does not exist");
|
|
1633
|
-
}
|
|
1634
|
-
// 保存实例
|
|
1635
|
-
await collection.save(Buffer.from(instance));
|
|
1636
|
-
} catch (err) {
|
|
1637
|
-
console.error(`Failed to save instance: ${err instanceof Error ? err.message : String(err)}`);
|
|
1638
|
-
throw err;
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* DeleteMany deletes multiple instances by their IDs
|
|
1644
|
-
* @param threadId Thread ID string
|
|
1645
|
-
* @param collectionName Collection name
|
|
1646
|
-
* @param instanceIDs Comma-separated or JSON array of instance IDs
|
|
1647
|
-
* @throws Error if deletion fails
|
|
1648
|
-
*/
|
|
1649
|
-
async deleteMany(threadId: string, collectionName: string, instanceIDs: string): Promise<void> {
|
|
1650
|
-
try {
|
|
1651
|
-
// 解码线程ID
|
|
1652
|
-
const tID = ThreadID.fromString(threadId);
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
// 获取线程数据库
|
|
1656
|
-
const threadDB = await this.getDB(tID);
|
|
1657
|
-
|
|
1658
|
-
// 获取集合
|
|
1659
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1660
|
-
if (!collection) {
|
|
1661
|
-
throw new Error("Collection does not exist");
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
// 解析实例ID列表
|
|
1665
|
-
let IDs: string[] = [];
|
|
1666
|
-
instanceIDs = instanceIDs.trim();
|
|
1667
|
-
if (instanceIDs === "") {
|
|
1668
|
-
return;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
if (instanceIDs[0] !== '[') {
|
|
1672
|
-
// 逗号分隔的ID列表
|
|
1673
|
-
IDs = instanceIDs.split(',');
|
|
1674
|
-
} else {
|
|
1675
|
-
// JSON数组
|
|
1676
|
-
try {
|
|
1677
|
-
IDs = JSON.parse(instanceIDs);
|
|
1678
|
-
} catch (err) {
|
|
1679
|
-
// 解析失败时,将整个字符串作为一个ID
|
|
1680
|
-
IDs = [instanceIDs];
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// 批量处理,每次最多100个(避免事务过大)
|
|
1685
|
-
const idsLen = IDs.length;
|
|
1686
|
-
for (let i = 0; i < idsLen; i += 100) {
|
|
1687
|
-
const batchIds = IDs.slice(i, Math.min(i + 100, idsLen));
|
|
1688
|
-
await collection.deleteMany(batchIds);
|
|
1689
|
-
}
|
|
1690
|
-
} catch (err) {
|
|
1691
|
-
console.error(`Failed to delete instances: ${err instanceof Error ? err.message : String(err)}`);
|
|
1692
|
-
throw err;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
/**
|
|
1697
|
-
* Has checks if the specified instance exists
|
|
1698
|
-
* @param threadId Thread ID string
|
|
1699
|
-
* @param collectionName Collection name
|
|
1700
|
-
* @param instanceID Instance ID to check
|
|
1701
|
-
* @returns Promise resolving to a boolean indicating if instance exists
|
|
1702
|
-
*/
|
|
1703
|
-
async has(threadId: string, collectionName: string, instanceID: string): Promise<boolean> {
|
|
1704
|
-
try {
|
|
1705
|
-
// 解码线程ID
|
|
1706
|
-
const tID = ThreadID.fromString(threadId);
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
// 获取线程数据库
|
|
1711
|
-
const threadDB = await this.getDB(tID);
|
|
1712
|
-
|
|
1713
|
-
// 获取集合
|
|
1714
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1715
|
-
if (!collection) {
|
|
1716
|
-
return false;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
// 检查实例是否存在
|
|
1720
|
-
return await collection.has(instanceID);
|
|
1721
|
-
} catch (err) {
|
|
1722
|
-
console.error(`Failed to check instance existence: ${err instanceof Error ? err.message : String(err)}`);
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
/**
|
|
1728
|
-
* Find finds instances by query
|
|
1729
|
-
* @param threadId Thread ID string
|
|
1730
|
-
* @param collectionName Collection name
|
|
1731
|
-
* @param queryString JSON string representing the query
|
|
1732
|
-
* @returns Promise resolving to a JSON string with found instances
|
|
1733
|
-
* @throws Error if query fails
|
|
1734
|
-
*/
|
|
1735
|
-
async find(threadId: string, collectionName: string, queryString?: string): Promise<string> {
|
|
1736
|
-
try {
|
|
1737
|
-
if (!queryString) {
|
|
1738
|
-
queryString = "{}";
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
// 解析查询字符串
|
|
1742
|
-
const query = parseJsonToQuery(queryString);
|
|
1743
|
-
// 解码线程ID
|
|
1744
|
-
const tID = ThreadID.fromString(threadId);
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
// 获取线程数据库
|
|
1749
|
-
const threadDB = await this.getDB(tID);
|
|
1750
|
-
|
|
1751
|
-
// 获取集合
|
|
1752
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1753
|
-
if (!collection) {
|
|
1754
|
-
throw new Error("Collection does not exist");
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// 执行查询
|
|
1758
|
-
const results = await collection.find(query);
|
|
1759
|
-
|
|
1760
|
-
// 合并结果并返回JSON字符串
|
|
1761
|
-
if (Array.isArray(results)) {
|
|
1762
|
-
return jsonStringify(results);
|
|
1763
|
-
} else {
|
|
1764
|
-
// 如果结果是字节数组,则需要连接它们
|
|
1765
|
-
const resultArray = results as Buffer[];
|
|
1766
|
-
const joinedResult = Buffer.concat([
|
|
1767
|
-
Buffer.from('['),
|
|
1768
|
-
Buffer.concat(
|
|
1769
|
-
resultArray.map((buf, idx) =>
|
|
1770
|
-
Buffer.concat([
|
|
1771
|
-
buf,
|
|
1772
|
-
idx < resultArray.length - 1 ? Buffer.from(',') : Buffer.from('')
|
|
1773
|
-
])
|
|
1774
|
-
)
|
|
1775
|
-
),
|
|
1776
|
-
Buffer.from(']')
|
|
1777
|
-
]);
|
|
1778
|
-
return joinedResult.toString();
|
|
1779
|
-
}
|
|
1780
|
-
} catch (err) {
|
|
1781
|
-
console.error(`Failed to find instances: ${err instanceof Error ? err.message : String(err)}`);
|
|
1782
|
-
throw err;
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
/**
|
|
1787
|
-
* FindByID finds an instance by ID
|
|
1788
|
-
* @param threadId Thread ID string
|
|
1789
|
-
* @param collectionName Collection name
|
|
1790
|
-
* @param instanceID Instance ID to find
|
|
1791
|
-
* @returns Promise resolving to a JSON string with found instance
|
|
1792
|
-
* @throws Error if query fails
|
|
1793
|
-
*/
|
|
1794
|
-
async findByID(threadId: string, collectionName: string, instanceID: string): Promise<string> {
|
|
1795
|
-
try {
|
|
1796
|
-
// 解码线程ID
|
|
1797
|
-
const tID = ThreadID.fromString(threadId);
|
|
1798
|
-
|
|
1799
|
-
// 获取线程数据库
|
|
1800
|
-
const threadDB = await this.getDB(tID);
|
|
1801
|
-
|
|
1802
|
-
// 获取集合
|
|
1803
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1804
|
-
if (!collection) {
|
|
1805
|
-
throw new Error("Collection does not exist");
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
// 根据ID查找实例
|
|
1809
|
-
const result = await collection.findByID(instanceID);
|
|
1810
|
-
|
|
1811
|
-
// 返回实例
|
|
1812
|
-
return result instanceof Buffer ? result.toString() : jsonStringify(result);
|
|
1813
|
-
} catch (err) {
|
|
1814
|
-
console.error(`Failed to find instance by ID: ${err instanceof Error ? err.message : String(err)}`);
|
|
1815
|
-
throw err;
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
/**
|
|
1820
|
-
* ModifiedSince returns instance IDs modified since the given time
|
|
1821
|
-
* @param threadId Thread ID string
|
|
1822
|
-
* @param collectionName Collection name
|
|
1823
|
-
* @param time Unix timestamp in milliseconds
|
|
1824
|
-
* @returns Promise resolving to a JSON string with instance IDs
|
|
1825
|
-
* @throws Error if query fails
|
|
1826
|
-
*/
|
|
1827
|
-
async modifiedSince(threadId: string, collectionName: string, time: number): Promise<string> {
|
|
1828
|
-
try {
|
|
1829
|
-
// 解码线程ID
|
|
1830
|
-
const tID = ThreadID.fromString(threadId);
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
// 获取线程数据库
|
|
1835
|
-
const threadDB = await this.getDB(tID);
|
|
1836
|
-
|
|
1837
|
-
// 获取集合
|
|
1838
|
-
const collection = threadDB.getCollection(collectionName);
|
|
1839
|
-
if (!collection) {
|
|
1840
|
-
throw new Error("Collection does not exist");
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
// 获取指定时间后修改的实例ID列表
|
|
1844
|
-
const ids = await collection.modifiedSince(time);
|
|
1845
|
-
|
|
1846
|
-
// 序列化并返回ID列表
|
|
1847
|
-
return JSON.stringify(ids);
|
|
1848
|
-
} catch (err) {
|
|
1849
|
-
console.error(`Failed to get modified instances: ${err instanceof Error ? err.message : String(err)}`);
|
|
1850
|
-
throw err;
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
class AsyncLock {
|
|
1859
|
-
private locks: Map<string, Promise<void>>;
|
|
1860
|
-
constructor() {
|
|
1861
|
-
this.locks = new Map();
|
|
1862
|
-
}
|
|
1863
|
-
async acquire<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
1864
|
-
while (this.locks.has(key)) {
|
|
1865
|
-
await this.locks.get(key);
|
|
1866
|
-
}
|
|
1867
|
-
let resolve: () => void;
|
|
1868
|
-
const promise = new Promise<void>((r) => (resolve = r));
|
|
1869
|
-
this.locks.set(key, promise);
|
|
1870
|
-
|
|
1871
|
-
try {
|
|
1872
|
-
const result = await fn();
|
|
1873
|
-
return result;
|
|
1874
|
-
} catch (err: any) {
|
|
1875
|
-
// 重新抛出错误以保持类型一致性
|
|
1876
|
-
throw err;
|
|
1877
|
-
} finally {
|
|
1878
|
-
this.locks.delete(key);
|
|
1879
|
-
resolve!();
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
function isValidName(name: string): boolean {
|
|
1885
|
-
return /^[a-zA-Z0-9_-]+$/.test(name);
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
export function createContext(timeout: number): Context {
|
|
1889
|
-
const ctx : Context = {
|
|
1890
|
-
deadline: new Date(Date.now() + timeout)
|
|
1891
|
-
};
|
|
1892
|
-
if (timeout === 0) {
|
|
1893
|
-
ctx.deadline = undefined;
|
|
1894
|
-
}
|
|
1895
|
-
if (typeof AbortController !== 'undefined') {
|
|
1896
|
-
ctx.signal = new AbortController().signal;
|
|
1897
|
-
}
|
|
1898
|
-
return ctx;
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
|