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.
Files changed (142) hide show
  1. package/dist/cjs/index.js +1 -0
  2. package/dist/cjs/protobuf-BVBdi7Hh.js +1 -0
  3. package/dist/dc.min.js +1 -19
  4. package/dist/esm/chunks/protobuf-CbxDm-Gy.js +1 -0
  5. package/dist/esm/index.js +1 -0
  6. package/dist/index.d.ts +947 -886
  7. package/package.json +14 -17
  8. package/dist/index.cjs.js +0 -19
  9. package/dist/index.esm.js +0 -19
  10. package/lib/common/blowfish/block.ts +0 -259
  11. package/lib/common/blowfish/cipher.ts +0 -144
  12. package/lib/common/blowfish/const.ts +0 -195
  13. package/lib/common/chain.ts +0 -469
  14. package/lib/common/commonclient.ts +0 -202
  15. package/lib/common/constants.ts +0 -55
  16. package/lib/common/dc-key/ed25519.ts +0 -343
  17. package/lib/common/dc-key/keyManager.ts +0 -424
  18. package/lib/common/dcapi.ts +0 -98
  19. package/lib/common/dcutil.ts +0 -595
  20. package/lib/common/define.ts +0 -66
  21. package/lib/common/error.ts +0 -67
  22. package/lib/common/grpc-dc.ts +0 -104
  23. package/lib/common/module-system.ts +0 -184
  24. package/lib/common/service-worker.ts +0 -234
  25. package/lib/common/types/types.ts +0 -344
  26. package/lib/dc.ts +0 -694
  27. package/lib/implements/account/client.ts +0 -185
  28. package/lib/implements/account/manager.ts +0 -683
  29. package/lib/implements/aiproxy/client.ts +0 -357
  30. package/lib/implements/aiproxy/manager.ts +0 -670
  31. package/lib/implements/cache/client.ts +0 -105
  32. package/lib/implements/cache/manager.ts +0 -127
  33. package/lib/implements/comment/client.ts +0 -982
  34. package/lib/implements/comment/manager.ts +0 -1151
  35. package/lib/implements/dc/client.ts +0 -51
  36. package/lib/implements/dc/manager.ts +0 -33
  37. package/lib/implements/file/client.ts +0 -253
  38. package/lib/implements/file/file-cache-manager.ts +0 -142
  39. package/lib/implements/file/manager.ts +0 -1240
  40. package/lib/implements/file/seekableFileStream.ts +0 -344
  41. package/lib/implements/file/streamwriter.ts +0 -322
  42. package/lib/implements/keyvalue/client.ts +0 -376
  43. package/lib/implements/keyvalue/manager.ts +0 -759
  44. package/lib/implements/message/client.ts +0 -250
  45. package/lib/implements/message/manager.ts +0 -215
  46. package/lib/implements/threaddb/cbor/coding.ts +0 -62
  47. package/lib/implements/threaddb/cbor/event.ts +0 -336
  48. package/lib/implements/threaddb/cbor/node.ts +0 -542
  49. package/lib/implements/threaddb/cbor/record.ts +0 -398
  50. package/lib/implements/threaddb/common/AsyncMutex.ts +0 -24
  51. package/lib/implements/threaddb/common/addrinfo.ts +0 -135
  52. package/lib/implements/threaddb/common/dispatcher.ts +0 -81
  53. package/lib/implements/threaddb/common/idbstore-adapter.ts +0 -260
  54. package/lib/implements/threaddb/common/json-patcher.ts +0 -204
  55. package/lib/implements/threaddb/common/key.ts +0 -290
  56. package/lib/implements/threaddb/common/level-adapter.ts +0 -235
  57. package/lib/implements/threaddb/common/lineReader.ts +0 -79
  58. package/lib/implements/threaddb/common/logstore.ts +0 -215
  59. package/lib/implements/threaddb/common/transformed-datastore.ts +0 -308
  60. package/lib/implements/threaddb/core/app.ts +0 -202
  61. package/lib/implements/threaddb/core/core.ts +0 -230
  62. package/lib/implements/threaddb/core/db.ts +0 -249
  63. package/lib/implements/threaddb/core/event.ts +0 -54
  64. package/lib/implements/threaddb/core/head.ts +0 -89
  65. package/lib/implements/threaddb/core/identity.ts +0 -171
  66. package/lib/implements/threaddb/core/logstore.ts +0 -137
  67. package/lib/implements/threaddb/core/options.ts +0 -14
  68. package/lib/implements/threaddb/core/record.ts +0 -54
  69. package/lib/implements/threaddb/db/collection.ts +0 -1910
  70. package/lib/implements/threaddb/db/db.ts +0 -698
  71. package/lib/implements/threaddb/db/json2Query.ts +0 -192
  72. package/lib/implements/threaddb/db/query.ts +0 -524
  73. package/lib/implements/threaddb/dbclient.ts +0 -443
  74. package/lib/implements/threaddb/dbmanager.ts +0 -1901
  75. package/lib/implements/threaddb/lsstoreds/addr_book.ts +0 -452
  76. package/lib/implements/threaddb/lsstoreds/cache.ts +0 -36
  77. package/lib/implements/threaddb/lsstoreds/cyclic_batch.ts +0 -87
  78. package/lib/implements/threaddb/lsstoreds/global.ts +0 -151
  79. package/lib/implements/threaddb/lsstoreds/headbook.ts +0 -280
  80. package/lib/implements/threaddb/lsstoreds/keybook.ts +0 -297
  81. package/lib/implements/threaddb/lsstoreds/logstore.ts +0 -29
  82. package/lib/implements/threaddb/lsstoreds/metadata.ts +0 -223
  83. package/lib/implements/threaddb/net/define.ts +0 -138
  84. package/lib/implements/threaddb/net/grpcClient.ts +0 -582
  85. package/lib/implements/threaddb/net/grpcserver.ts +0 -146
  86. package/lib/implements/threaddb/net/net.ts +0 -2006
  87. package/lib/implements/threaddb/pb/lstore.proto +0 -38
  88. package/lib/implements/threaddb/pb/lstore.ts +0 -393
  89. package/lib/implements/threaddb/pb/lstore_pb.d.ts +0 -433
  90. package/lib/implements/threaddb/pb/lstore_pb.js +0 -1085
  91. package/lib/implements/threaddb/pb/net.proto +0 -194
  92. package/lib/implements/threaddb/pb/net_pb.d.ts +0 -2349
  93. package/lib/implements/threaddb/pb/net_pb.js +0 -5525
  94. package/lib/implements/threaddb/pb/proto-custom-types.ts +0 -212
  95. package/lib/implements/util/client.ts +0 -72
  96. package/lib/implements/util/manager.ts +0 -146
  97. package/lib/implements/wallet/manager.ts +0 -664
  98. package/lib/index.ts +0 -57
  99. package/lib/interfaces/DCContext.ts +0 -51
  100. package/lib/interfaces/aiproxy-interface.ts +0 -145
  101. package/lib/interfaces/auth-interface.ts +0 -118
  102. package/lib/interfaces/cache-interface.ts +0 -22
  103. package/lib/interfaces/client-interface.ts +0 -11
  104. package/lib/interfaces/comment-interface.ts +0 -167
  105. package/lib/interfaces/database-interface.ts +0 -169
  106. package/lib/interfaces/file-interface.ts +0 -120
  107. package/lib/interfaces/index.ts +0 -10
  108. package/lib/interfaces/keyvalue-interface.ts +0 -156
  109. package/lib/interfaces/message-interface.ts +0 -22
  110. package/lib/interfaces/util-interface.ts +0 -31
  111. package/lib/modules/aiproxy-module.ts +0 -246
  112. package/lib/modules/auth-module.ts +0 -753
  113. package/lib/modules/cache-module.ts +0 -99
  114. package/lib/modules/client-module.ts +0 -71
  115. package/lib/modules/comment-module.ts +0 -429
  116. package/lib/modules/database-module.ts +0 -598
  117. package/lib/modules/file-module.ts +0 -291
  118. package/lib/modules/index.ts +0 -13
  119. package/lib/modules/keyvalue-module.ts +0 -379
  120. package/lib/modules/message-module.ts +0 -107
  121. package/lib/modules/util-module.ts +0 -148
  122. package/lib/polyfills/process-env-browser.ts +0 -1
  123. package/lib/proto/datasource.ts +0 -93
  124. package/lib/proto/dcnet.proto +0 -1601
  125. package/lib/proto/dcnet_proto.d.ts +0 -22857
  126. package/lib/proto/dcnet_proto.js +0 -55204
  127. package/lib/proto/dcnet_proto_sparse.js +0 -55166
  128. package/lib/proto/oidfetch.proto +0 -25
  129. package/lib/proto/oidfetch_proto.d.ts +0 -585
  130. package/lib/proto/oidfetch_proto.js +0 -1247
  131. package/lib/serverless/babel-browser.ts +0 -39
  132. package/lib/serverless/base_entity.ts +0 -78
  133. package/lib/serverless/base_repository.ts +0 -414
  134. package/lib/serverless/browser_schema_extractor.ts +0 -283
  135. package/lib/serverless/decorator_factory.ts +0 -322
  136. package/lib/util/BrowserLineReader.ts +0 -73
  137. package/lib/util/base64.ts +0 -105
  138. package/lib/util/bcrypt.ts +0 -206
  139. package/lib/util/curve25519Encryption.ts +0 -418
  140. package/lib/util/dccrypt.ts +0 -73
  141. package/lib/util/logger.ts +0 -104
  142. 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
-