web-dc-api 0.1.5 → 0.1.6

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 (148) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/dc.min.js +1 -1
  3. package/dist/esm/index.js +1 -1
  4. package/dist/index.d.ts +934 -878
  5. package/package.json +4 -8
  6. package/dist/cjs/helia-core-B1Xqha7a.js +0 -1
  7. package/dist/cjs/helia-core-D8Uv1KjQ.js +0 -1
  8. package/dist/cjs/polkadot-api-7PhQf3ws.js +0 -1
  9. package/dist/cjs/polkadot-api-CtrJVWuZ.js +0 -1
  10. package/dist/esm/chunks/helia-core-BxMqyK2Y.js +0 -1
  11. package/dist/esm/chunks/helia-core-DMXRpcO-.js +0 -1
  12. package/dist/esm/chunks/polkadot-api-5Y9Bw8VT.js +0 -1
  13. package/dist/esm/chunks/polkadot-api-D69Ioun_.js +0 -1
  14. package/lib/common/blowfish/block.ts +0 -259
  15. package/lib/common/blowfish/cipher.ts +0 -144
  16. package/lib/common/blowfish/const.ts +0 -195
  17. package/lib/common/chain.ts +0 -469
  18. package/lib/common/commonclient.ts +0 -202
  19. package/lib/common/constants.ts +0 -55
  20. package/lib/common/dc-key/ed25519.ts +0 -343
  21. package/lib/common/dc-key/keyManager.ts +0 -424
  22. package/lib/common/dcapi.ts +0 -98
  23. package/lib/common/dcutil.ts +0 -627
  24. package/lib/common/define.ts +0 -70
  25. package/lib/common/error.ts +0 -67
  26. package/lib/common/grpc-dc.ts +0 -104
  27. package/lib/common/module-system.ts +0 -184
  28. package/lib/common/service-worker.ts +0 -234
  29. package/lib/common/types/types.ts +0 -344
  30. package/lib/dc.ts +0 -701
  31. package/lib/implements/account/client.ts +0 -185
  32. package/lib/implements/account/manager.ts +0 -683
  33. package/lib/implements/aiproxy/client.ts +0 -357
  34. package/lib/implements/aiproxy/manager.ts +0 -670
  35. package/lib/implements/cache/client.ts +0 -105
  36. package/lib/implements/cache/manager.ts +0 -127
  37. package/lib/implements/comment/client.ts +0 -982
  38. package/lib/implements/comment/manager.ts +0 -1151
  39. package/lib/implements/dc/client.ts +0 -51
  40. package/lib/implements/dc/manager.ts +0 -33
  41. package/lib/implements/file/client.ts +0 -253
  42. package/lib/implements/file/file-cache-manager.ts +0 -142
  43. package/lib/implements/file/manager.ts +0 -1240
  44. package/lib/implements/file/seekableFileStream.ts +0 -344
  45. package/lib/implements/file/streamwriter.ts +0 -322
  46. package/lib/implements/keyvalue/client.ts +0 -376
  47. package/lib/implements/keyvalue/manager.ts +0 -759
  48. package/lib/implements/message/client.ts +0 -250
  49. package/lib/implements/message/manager.ts +0 -215
  50. package/lib/implements/threaddb/cbor/coding.ts +0 -62
  51. package/lib/implements/threaddb/cbor/event.ts +0 -336
  52. package/lib/implements/threaddb/cbor/node.ts +0 -542
  53. package/lib/implements/threaddb/cbor/record.ts +0 -398
  54. package/lib/implements/threaddb/common/AsyncMutex.ts +0 -24
  55. package/lib/implements/threaddb/common/addrinfo.ts +0 -135
  56. package/lib/implements/threaddb/common/dispatcher.ts +0 -81
  57. package/lib/implements/threaddb/common/idbstore-adapter.ts +0 -260
  58. package/lib/implements/threaddb/common/json-patcher.ts +0 -204
  59. package/lib/implements/threaddb/common/key.ts +0 -290
  60. package/lib/implements/threaddb/common/level-adapter.ts +0 -235
  61. package/lib/implements/threaddb/common/lineReader.ts +0 -79
  62. package/lib/implements/threaddb/common/logstore.ts +0 -215
  63. package/lib/implements/threaddb/common/transformed-datastore.ts +0 -308
  64. package/lib/implements/threaddb/core/app.ts +0 -206
  65. package/lib/implements/threaddb/core/core.ts +0 -230
  66. package/lib/implements/threaddb/core/db.ts +0 -249
  67. package/lib/implements/threaddb/core/event.ts +0 -54
  68. package/lib/implements/threaddb/core/head.ts +0 -89
  69. package/lib/implements/threaddb/core/identity.ts +0 -171
  70. package/lib/implements/threaddb/core/logstore.ts +0 -137
  71. package/lib/implements/threaddb/core/options.ts +0 -14
  72. package/lib/implements/threaddb/core/record.ts +0 -54
  73. package/lib/implements/threaddb/db/collection.ts +0 -1910
  74. package/lib/implements/threaddb/db/db.ts +0 -698
  75. package/lib/implements/threaddb/db/json2Query.ts +0 -192
  76. package/lib/implements/threaddb/db/query.ts +0 -524
  77. package/lib/implements/threaddb/dbclient.ts +0 -543
  78. package/lib/implements/threaddb/dbmanager.ts +0 -1906
  79. package/lib/implements/threaddb/lsstoreds/addr_book.ts +0 -549
  80. package/lib/implements/threaddb/lsstoreds/cache.ts +0 -36
  81. package/lib/implements/threaddb/lsstoreds/cyclic_batch.ts +0 -87
  82. package/lib/implements/threaddb/lsstoreds/global.ts +0 -151
  83. package/lib/implements/threaddb/lsstoreds/headbook.ts +0 -373
  84. package/lib/implements/threaddb/lsstoreds/keybook.ts +0 -297
  85. package/lib/implements/threaddb/lsstoreds/logstore.ts +0 -29
  86. package/lib/implements/threaddb/lsstoreds/metadata.ts +0 -223
  87. package/lib/implements/threaddb/net/define.ts +0 -149
  88. package/lib/implements/threaddb/net/grpcClient.ts +0 -589
  89. package/lib/implements/threaddb/net/grpcserver.ts +0 -146
  90. package/lib/implements/threaddb/net/net.ts +0 -2047
  91. package/lib/implements/threaddb/pb/lstore.proto +0 -38
  92. package/lib/implements/threaddb/pb/lstore.ts +0 -393
  93. package/lib/implements/threaddb/pb/lstore_pb.d.ts +0 -433
  94. package/lib/implements/threaddb/pb/lstore_pb.js +0 -1085
  95. package/lib/implements/threaddb/pb/net.proto +0 -194
  96. package/lib/implements/threaddb/pb/net_pb.d.ts +0 -2349
  97. package/lib/implements/threaddb/pb/net_pb.js +0 -5525
  98. package/lib/implements/threaddb/pb/proto-custom-types.ts +0 -212
  99. package/lib/implements/util/client.ts +0 -72
  100. package/lib/implements/util/manager.ts +0 -146
  101. package/lib/implements/wallet/manager.ts +0 -671
  102. package/lib/index.ts +0 -57
  103. package/lib/interfaces/DCContext.ts +0 -51
  104. package/lib/interfaces/aiproxy-interface.ts +0 -145
  105. package/lib/interfaces/auth-interface.ts +0 -118
  106. package/lib/interfaces/cache-interface.ts +0 -22
  107. package/lib/interfaces/client-interface.ts +0 -11
  108. package/lib/interfaces/comment-interface.ts +0 -167
  109. package/lib/interfaces/components/news-component.ts +0 -0
  110. package/lib/interfaces/database-interface.ts +0 -169
  111. package/lib/interfaces/file-interface.ts +0 -120
  112. package/lib/interfaces/index.ts +0 -10
  113. package/lib/interfaces/keyvalue-interface.ts +0 -156
  114. package/lib/interfaces/message-interface.ts +0 -22
  115. package/lib/interfaces/util-interface.ts +0 -31
  116. package/lib/modules/aiproxy-module.ts +0 -246
  117. package/lib/modules/auth-module.ts +0 -753
  118. package/lib/modules/cache-module.ts +0 -99
  119. package/lib/modules/client-module.ts +0 -71
  120. package/lib/modules/comment-module.ts +0 -429
  121. package/lib/modules/components/news-components.ts +0 -390
  122. package/lib/modules/database-module.ts +0 -598
  123. package/lib/modules/file-module.ts +0 -291
  124. package/lib/modules/index.ts +0 -13
  125. package/lib/modules/keyvalue-module.ts +0 -379
  126. package/lib/modules/message-module.ts +0 -107
  127. package/lib/modules/util-module.ts +0 -148
  128. package/lib/polyfills/process-env-browser.ts +0 -1
  129. package/lib/proto/datasource.ts +0 -93
  130. package/lib/proto/dcnet.proto +0 -1601
  131. package/lib/proto/dcnet_proto.d.ts +0 -22857
  132. package/lib/proto/dcnet_proto.js +0 -55204
  133. package/lib/proto/dcnet_proto_sparse.js +0 -55166
  134. package/lib/proto/oidfetch.proto +0 -25
  135. package/lib/proto/oidfetch_proto.d.ts +0 -585
  136. package/lib/proto/oidfetch_proto.js +0 -1247
  137. package/lib/serverless/babel-browser.ts +0 -39
  138. package/lib/serverless/base_entity.ts +0 -78
  139. package/lib/serverless/base_repository.ts +0 -414
  140. package/lib/serverless/browser_schema_extractor.ts +0 -283
  141. package/lib/serverless/decorator_factory.ts +0 -322
  142. package/lib/util/BrowserLineReader.ts +0 -73
  143. package/lib/util/base64.ts +0 -105
  144. package/lib/util/bcrypt.ts +0 -206
  145. package/lib/util/curve25519Encryption.ts +0 -418
  146. package/lib/util/dccrypt.ts +0 -73
  147. package/lib/util/logger.ts +0 -104
  148. package/lib/util/utils.ts +0 -289
@@ -1,1910 +0,0 @@
1
- import { Key,QueryFilter } from 'interface-datastore';
2
- import {AnySchema} from 'ajv';
3
- import Ajv from 'ajv';
4
- import { nanoid } from 'nanoid';
5
- import EventEmitter from 'eventemitter3';
6
-
7
- import { Ed25519PrivKey as PrivKey,Ed25519PubKey as PubKey} from "../../../common/dc-key/ed25519";
8
- import { Action, CoreActionType, Event,ITxn ,idFieldName} from '../core/db';
9
- import { ThreadID } from '@textile/threads-id';
10
- import { IPLDNode } from '../core/core';
11
- import { ICollectionConfig } from '../core/core';
12
- import {dsPrefix,IDB,ICollection,DBPrefix} from '../core/db';
13
- import {ThreadToken} from '../core/identity';
14
- import {dsDispatcherPrefix} from '../common/dispatcher';
15
- import {Query,compare,traverseFieldPathMap} from './query';
16
- import * as cbornode from '../cbor/node';
17
- import * as dagCBOR from '@ipld/dag-cbor';
18
- import { dagCbor } from '@helia/dag-cbor';
19
- import { jsonStringify } from '../../../util/utils';
20
-
21
- // iteratorKeyMinCacheSize is the size of iterator keys stored in memory before more are fetched.
22
- const iteratorKeyMinCacheSize = 100
23
-
24
-
25
- // Error classes for index operations
26
- class IndexError extends Error {
27
- constructor(message: string) {
28
- super(message);
29
- this.name = 'IndexError';
30
- }
31
- }
32
-
33
- // Error constants
34
- const ErrUniqueExists = new IndexError("unique constraint violation");
35
- const ErrNotIndexable = new IndexError("value not indexable");
36
- const ErrCantCreateUniqueIndex = new IndexError("can't create unique index (duplicate instances exist)");
37
- const ErrIndexNotFound = new IndexError("index not found");
38
-
39
- // Index key prefix
40
- const indexPrefix = new Key("_index");
41
-
42
- // Valid index types
43
- const indexTypes: string[] = ["string", "number", "integer", "boolean"];
44
-
45
- /**
46
- * Index defines an index configuration
47
- */
48
- export interface Index {
49
- /**
50
- * Path to the field to index in dot syntax, e.g., "name.last" or "age".
51
- */
52
- path: string;
53
-
54
- /**
55
- * Unique indicates that only one instance should exist per field value.
56
- */
57
- unique?: boolean;
58
- }
59
-
60
- /**
61
- * MarshaledResult 表示查询结果
62
- */
63
- interface MarshaledResult {
64
- instanceID: InstanceID;
65
- key: string;
66
- value: Uint8Array;
67
- marshaledValue?: Record<string, any>;
68
- }
69
-
70
-
71
- /**
72
- * Error types
73
- */
74
- export class InvalidSortingFieldError extends Error {
75
- constructor() {
76
- super("sorting field doesn't correspond to instance type");
77
- this.name = "InvalidSortingFieldError";
78
- }
79
- }
80
-
81
- // 常量定义
82
-
83
- const ErrInvalidSortingField = new InvalidSortingFieldError();
84
- // Error definitions
85
- class ThreadDBError extends Error {
86
- constructor(message: string) {
87
- super(message);
88
- this.name = 'ThreadDBError';
89
- }
90
- }
91
-
92
- // Define specific error types
93
- const ErrInvalidCollectionSchemaPath = new ThreadDBError('collection schema does not contain path');
94
- const ErrCollectionNotFound = new ThreadDBError('collection not found');
95
- const ErrCollectionAlreadyRegistered = new ThreadDBError('collection already registered');
96
- const ErrInstanceNotFound = new ThreadDBError('instance not found');
97
- const ErrReadonlyTx = new ThreadDBError('read only transaction');
98
- const ErrInvalidSchemaInstance = new ThreadDBError('instance doesn\'t correspond to schema');
99
- const ErrInvalidCollectionSchema = new ThreadDBError('invalid collection schema');
100
- const ErrInvalidName = new ThreadDBError('invalid collection name');
101
-
102
- const errMissingInstanceID = new ThreadDBError('invalid instance: missing _id attribute');
103
- const errAlreadyDiscardedCommitedTxn = new ThreadDBError('can\'t commit discarded/committed txn');
104
- const errCantCreateExistingInstance = new ThreadDBError('can\'t create already existing instance');
105
-
106
- // Constants
107
- const baseKey = dsPrefix.child(new Key('collection'));
108
- const vmTimeout = 200; // milliseconds
109
- const writeValidatorFn = "_validate";
110
- const readFilterFn = "_filter";
111
-
112
- const createNetRecordTimeout = 30000; // 30 seconds
113
-
114
- // RegExp for validating collection names
115
- const nameRx = /^[a-zA-Z0-9_]+$/;
116
-
117
- /**
118
- * Instance ID type
119
- */
120
- export type InstanceID = string;
121
-
122
- // Empty instance ID constant
123
- const EmptyInstanceID = '';
124
-
125
- // Generate a new instance ID
126
- function NewInstanceID(): InstanceID {
127
- return nanoid();
128
- }
129
-
130
- /**
131
- * Collection configuration interface
132
- */
133
-
134
-
135
- /**
136
- * Index definition for collections
137
- */
138
- export interface Index {
139
- path: string;
140
- unique?: boolean;
141
- }
142
-
143
-
144
-
145
-
146
-
147
- /**
148
- * JavaScript runtime interface for validators
149
- */
150
- interface JSRuntime {
151
- runScript(script: string): any;
152
- getGlobal(name: string): any;
153
- call(fn: string, ...args: any[]): any;
154
- }
155
-
156
- /**
157
- * Browser-compatible JS runtime
158
- */
159
- class BrowserJSRuntime implements JSRuntime {
160
- private context: Record<string, any> = {};
161
- private functions: Record<string, Function> = {};
162
- private timeoutId: number | null = null;
163
-
164
- constructor(private timeout: number = vmTimeout) {}
165
-
166
- runScript(script: string): any {
167
- try {
168
- // Create a function that executes in our context
169
- const fn = new Function('context', `
170
- with(context) {
171
- ${script};
172
- return context;
173
- }
174
- `);
175
-
176
- // Set timeout for long-running scripts
177
- let completed = false;
178
- this.timeoutId = window.setTimeout(() => {
179
- if (!completed) {
180
- console.warn("JavaScript execution timed out");
181
- }
182
- }, this.timeout);
183
-
184
- // Execute the script
185
- this.context = fn(this.context) || this.context;
186
-
187
- // Clear timeout
188
- completed = true;
189
- if (this.timeoutId) {
190
- clearTimeout(this.timeoutId);
191
- this.timeoutId = null;
192
- }
193
-
194
- return true;
195
- } catch (err) {
196
- if (this.timeoutId) {
197
- clearTimeout(this.timeoutId);
198
- this.timeoutId = null;
199
- }
200
- throw new Error(`Script execution error: ${err instanceof Error ? err.message : String(err)}`);
201
- }
202
- }
203
-
204
- getGlobal(name: string): any {
205
- return this.context[name];
206
- }
207
-
208
- call(fn: string, ...args: any[]): any {
209
- const func = this.context[fn];
210
- if (typeof func !== 'function') {
211
- throw new Error(`${fn} is not a function`);
212
- }
213
-
214
- try {
215
- // Set timeout for long-running functions
216
- let completed = false;
217
- this.timeoutId = window.setTimeout(() => {
218
- if (!completed) {
219
- console.warn("JavaScript function call timed out");
220
- }
221
- }, this.timeout);
222
-
223
- // Execute the function
224
- const result = func.apply(this.context, args);
225
-
226
- // Clear timeout
227
- completed = true;
228
- if (this.timeoutId) {
229
- clearTimeout(this.timeoutId);
230
- this.timeoutId = null;
231
- }
232
-
233
- return result;
234
- } catch (err) {
235
- if (this.timeoutId) {
236
- clearTimeout(this.timeoutId);
237
- this.timeoutId = null;
238
- }
239
- throw new Error(`Function execution error: ${err instanceof Error ? err.message : String(err)}`);
240
- }
241
- }
242
- }
243
-
244
- /**
245
- * Connector interface for DB
246
- */
247
- interface Connector {
248
- validate(token: ThreadToken, readOnly: boolean): Promise<Error | null>;
249
- createNetRecord(node: IPLDNode, token: ThreadToken): Promise<any>;
250
- }
251
-
252
-
253
-
254
- /**
255
- * Collection class - a group of instances sharing a schema
256
- */
257
- export class Collection implements ICollection {
258
- private schemaValidator: Ajv;
259
- private vm: JSRuntime;
260
- private writeValidator: ((writer: any, event: any, instance: any) => boolean) | null = null;
261
- public readFilter: ((reader: any, instance: any) => any) | null = null;
262
- public indexes: Map<string, Index> = new Map();
263
-
264
- constructor(
265
- public readonly name: string,
266
- public schema: AnySchema,
267
- public db: IDB,
268
- public rawWriteValidator?: string,
269
- public rawReadFilter?: string
270
- ) {
271
- // Initialize schema validator
272
- this.schemaValidator = new Ajv({ allErrors: true });
273
- this.vm = new BrowserJSRuntime(vmTimeout);
274
-
275
- // Compile and load validators if provided
276
- if (rawWriteValidator) {
277
- this.compileAndLoadValidator(rawWriteValidator, writeValidatorFn, ['writer', 'event', 'instance']);
278
- }
279
-
280
- if (rawReadFilter) {
281
- this.compileAndLoadValidator(rawReadFilter, readFilterFn, ['reader', 'instance']);
282
- }
283
- }
284
-
285
- private compileAndLoadValidator(source: string, name: string, args: string[]): void {
286
- try {
287
- const script = `function ${name}(${args.join(",")}) {${source}}`;
288
- this.vm.runScript(script);
289
-
290
- // Test the function exists
291
- const fn = this.vm.getGlobal(name);
292
- if (typeof fn !== 'function') {
293
- throw new Error(`${name} is not a function`);
294
- }
295
-
296
- // Set the appropriate validator
297
- if (name === writeValidatorFn) {
298
- this.writeValidator = (writer: any, event: any, instance: any) =>
299
- this.vm.call(writeValidatorFn, writer, event, instance);
300
- } else if (name === readFilterFn) {
301
- this.readFilter = (reader: any, instance: any) =>
302
- this.vm.call(readFilterFn, reader, instance);
303
- }
304
- } catch (err) {
305
- throw new Error(`Failed to compile ${name}: ${err instanceof Error ? err.message : String(err)}`);
306
- }
307
- }
308
-
309
- /**
310
- * Get base key for this collection
311
- */
312
- baseKey(): Key {
313
- return baseKey.child(new Key(this.name));
314
- }
315
-
316
- /**
317
- * Get collection name
318
- */
319
- getName(): string {
320
- return this.name;
321
- }
322
-
323
- /**
324
- * Get collection schema
325
- */
326
- getSchema(): Uint8Array {
327
- return new TextEncoder().encode(JSON.stringify(this.schema));
328
- }
329
-
330
- /**
331
- * Get write validator
332
- */
333
- getWriteValidator(): Uint8Array {
334
- return new TextEncoder().encode(this.rawWriteValidator || '');
335
- }
336
-
337
- /**
338
- * Get read filter
339
- */
340
- getReadFilter(): Uint8Array {
341
- return new TextEncoder().encode(this.rawReadFilter || '');
342
- }
343
-
344
- /**
345
- * Create a read transaction
346
- */
347
- async readTxn(fn: (txn: ITxn) => Promise<void> , token?: ThreadToken): Promise<void> {
348
- return this.db.readTxn(this, fn, token);
349
- }
350
-
351
- /**
352
- * Create a write transaction
353
- */
354
- async writeTxn(fn: (txn: ITxn) => Promise<void> , token?: ThreadToken): Promise<void> {
355
- return this.db.writeTxn(this, fn, token);
356
- }
357
-
358
- /**
359
- * Find instance by ID
360
- */
361
- async findByID(id: InstanceID, token?: ThreadToken): Promise<Object> {
362
- let instance: Object | null = null;
363
-
364
- await this.readTxn(async (txn) => {
365
- instance = await txn.findByID(id);
366
- }, token);
367
-
368
- return instance!;
369
- }
370
-
371
- /**
372
- * Create a new instance
373
- */
374
- async create(v: Uint8Array, token?: ThreadToken): Promise<InstanceID> {
375
- let id: InstanceID = '';
376
-
377
- await this.writeTxn(async (txn) => {
378
- const ids = await txn.create(v);
379
- if (ids.length > 0) {
380
- id = ids[0] as InstanceID;
381
- }
382
- }, token);
383
-
384
- return id;
385
- }
386
-
387
- /**
388
- * Create multiple instances
389
- */
390
- async createMany(vs: Uint8Array[], token?: ThreadToken): Promise<InstanceID[]> {
391
- let ids: InstanceID[] = [];
392
-
393
- await this.writeTxn(async (txn) => {
394
- ids = await txn.create(...vs);
395
- }, token);
396
-
397
- return ids;
398
- }
399
-
400
- /**
401
- * Delete an instance by ID
402
- */
403
- async delete(id: InstanceID, token?: ThreadToken): Promise<void> {
404
- await this.writeTxn(async (txn) => {
405
- await txn.delete(id);
406
- }, token);
407
- }
408
-
409
- /**
410
- * Delete multiple instances by ID
411
- */
412
- async deleteMany(ids: InstanceID[], token?: ThreadToken): Promise<void> {
413
- await this.writeTxn(async (txn) => {
414
- await txn.delete(...ids);
415
- }, token);
416
- }
417
-
418
- /**
419
- * Save changes to an instance
420
- */
421
- async save(v: Uint8Array, token?: ThreadToken): Promise<void> {
422
- await this.writeTxn(async (txn) => {
423
- await txn.save(v);
424
- }, token);
425
- }
426
-
427
- /**
428
- * Save changes to multiple instances
429
- */
430
- async saveMany(vs: Uint8Array[], token?: ThreadToken): Promise<void> {
431
- await this.writeTxn(async (txn) => {
432
- await txn.save(...vs);
433
- }, token);
434
- }
435
-
436
- /**
437
- * Verify changes to an instance
438
- */
439
- async verify(v: Uint8Array, token?: ThreadToken): Promise<void> {
440
- await this.writeTxn(async (txn) => {
441
- await txn.verify(v);
442
- }, token);
443
- }
444
-
445
- /**
446
- * Verify changes to multiple instances
447
- */
448
- async verifyMany(vs: Uint8Array[], token?: ThreadToken): Promise<void> {
449
- await this.writeTxn(async (txn) => {
450
- await txn.verify(...vs);
451
- }, token);
452
- }
453
-
454
- /**
455
- * Check if an instance exists
456
- */
457
- async has(id: InstanceID, token?: ThreadToken): Promise<boolean> {
458
- let exists = false;
459
-
460
- await this.readTxn(async (txn) => {
461
- exists = await txn.has(id);
462
- }, token);
463
-
464
- return exists;
465
- }
466
-
467
- /**
468
- * Check if multiple instances exist
469
- */
470
- async hasMany(ids: InstanceID[], token?: ThreadToken): Promise<boolean> {
471
- let exists = false;
472
-
473
- await this.readTxn(async (txn) => {
474
- exists = await txn.has(...ids);
475
- }, token);
476
-
477
- return exists;
478
- }
479
-
480
- /**
481
- * Find instances matching a query
482
- */
483
- async find(q: Query, token?: ThreadToken): Promise<Object[]> {
484
- let instances: Object[] = [];
485
-
486
- await this.readTxn(async (txn) => {
487
- instances = await txn.find(q);
488
- }, token);
489
-
490
- return instances;
491
- }
492
-
493
- /**
494
- * Get instances modified since a specific time
495
- */
496
- async modifiedSince(time: number, token?: ThreadToken): Promise<InstanceID[]> {
497
- let ids: InstanceID[] = [];
498
-
499
- await this.readTxn(async (txn) => {
500
- ids = await txn.modifiedSince(time);
501
- }, token);
502
-
503
- return ids;
504
- }
505
-
506
- /**
507
- * Validate an instance against the collection schema
508
- */
509
- validInstance(v: Uint8Array): void {
510
- try {
511
- const instance = JSON.parse(new TextDecoder().decode(v));
512
- const validate = this.schemaValidator.compile(this.schema);
513
- const valid = validate(instance);
514
-
515
- if (!valid && validate.errors && validate.errors.length > 0) {
516
- let msg = '';
517
- for (let i = 0; i < validate.errors.length; i++) {
518
- const e = validate.errors[i];
519
- msg += `${e?.schemaPath}: ${e?.message}`;
520
- if (i !== validate.errors.length - 1) {
521
- msg += '; ';
522
- }
523
- }
524
- throw new Error(`${ErrInvalidSchemaInstance.message}: ${msg}`);
525
- }
526
- } catch (err) {
527
- if (err instanceof Error && err.message.includes(ErrInvalidSchemaInstance.message)) {
528
- throw err;
529
- }
530
- throw new Error(`Error validating instance: ${err instanceof Error ? err.message : String(err)}`);
531
- }
532
- }
533
-
534
- /**
535
- * Validate write operations against the write validator
536
- */
537
- async validWrite(identity: PubKey, e: Event): Promise<void> {
538
- if (!this.writeValidator) {
539
- return;
540
- }
541
-
542
- try {
543
- // Load identity
544
- const writer = identity ? identity.toString() : null;
545
-
546
- // Marshal event
547
- const data = await e.marshal();
548
- const eventData = JSON.parse(new TextDecoder().decode(data));
549
-
550
- // Get instance
551
- let instanceData = null;
552
- try {
553
- const key = this.baseKey().child(new Key(e.instanceID));
554
- const val = await this.db.datastore.get(key);
555
- if (val) {
556
- instanceData = JSON.parse(new TextDecoder().decode(val));
557
- }
558
- } catch (err: any) {
559
- // Only ignore "not found" errors
560
- if (err.code !== 'ERR_NOT_FOUND') {
561
- throw err;
562
- }
563
- }
564
-
565
- // Run validator
566
- const result = this.writeValidator(writer, eventData, instanceData);
567
-
568
- if (result !== true) {
569
- throw new Error('Write validation failed');
570
- }
571
- } catch (err) {
572
- throw new Error(`Write validation error: ${err instanceof Error ? err.message : String(err)}`);
573
- }
574
- }
575
-
576
- /**
577
- * Filter read operations based on the read filter
578
- */
579
- async filterRead(identity: PubKey|undefined, instance: Uint8Array): Promise<Uint8Array | null> {
580
- if (!this.readFilter) {
581
- return instance;
582
- }
583
-
584
- try {
585
- // Load identity
586
- const reader = identity ? identity.toString() : null;
587
-
588
- // Parse instance
589
- const instanceData = JSON.parse(new TextDecoder().decode(instance));
590
-
591
- // Run filter
592
- const result = this.readFilter(reader, instanceData);
593
-
594
- if (result === null) {
595
- return null;
596
- }
597
-
598
- return new TextEncoder().encode(jsonStringify(result));
599
- } catch (err) {
600
- throw new Error(`Read filter error: ${err instanceof Error ? err.message : String(err)}`);
601
- }
602
- }
603
-
604
-
605
- /**
606
- * 返回当前索引列表
607
- */
608
- getIndexes(): Index[] {
609
- if (this.indexes.size === 0) {
610
- return [];
611
- }
612
-
613
- const indexes: Index[] = [];
614
- this.indexes.forEach((index, path) => {
615
- if (path !== idFieldName) {
616
- indexes.push(index);
617
- }
618
- });
619
-
620
- return indexes;
621
- }
622
-
623
- /**
624
- * 创建基于路径的新索引
625
- * 使用点语法访问嵌套字段,例如 "name.last"
626
- * 路径上的字段必须是支持的JSON Schema类型之一: string, number, integer, 或 boolean
627
- * 设置unique为true可以在该路径上创建唯一性约束
628
- * 添加索引将覆盖任何已存在的重叠索引值
629
- * 注意: 这目前不会构建索引。如果在添加新索引之前已添加了项目,它们不会被事后索引。
630
- */
631
- async addIndex( index: Index, token?: ThreadToken): Promise<void> {
632
-
633
-
634
- // 不允许覆盖默认索引
635
- if (index.path === idFieldName) {
636
- if (this.indexes.has(idFieldName)) {
637
- return;
638
- }
639
- }
640
-
641
- // 验证路径和类型
642
- try {
643
- const jt = getSchemaTypeAtPath(this.schema, index.path);
644
- let valid = false;
645
- for (const t of indexTypes) {
646
- if (jt?.type === t) {
647
- valid = true;
648
- break;
649
- }
650
- }
651
- if (!valid) {
652
- throw ErrNotIndexable;
653
- }
654
-
655
- // 如果没有变化则跳过
656
- const existingIndex = this.indexes.get(index.path);
657
- if (existingIndex && existingIndex.unique === index.unique) {
658
- return;
659
- }
660
-
661
- // 确保集合在该路径上不包含具有相同值的多个实例
662
- if (index.unique && index.path !== idFieldName) {
663
- const vals = new Map<any, boolean>();
664
- const all = await this.find(new Query(), token);
665
-
666
- for (const item of all) {
667
- const value = traverseFieldPathMap(item, index.path);
668
-
669
- if (value === undefined) {
670
- continue;
671
- }
672
-
673
- if (vals.has(value)) {
674
- throw ErrCantCreateUniqueIndex;
675
- } else {
676
- vals.set(value, true);
677
- }
678
- }
679
- }
680
-
681
- // 保存索引
682
- this.indexes.set(index.path, index);
683
- await this.saveIndexes();
684
- } catch (err) {
685
- throw err;
686
- }
687
- }
688
-
689
- /**
690
- * 删除指定路径的索引
691
- */
692
- async dropIndex(path: string): Promise<void> {
693
- // 不允许删除默认索引
694
- if (path === idFieldName) {
695
- throw new Error(`${idFieldName} index cannot be dropped`);
696
- }
697
-
698
- this.indexes.delete(path);
699
- await this.saveIndexes();
700
- }
701
-
702
- /**
703
- * 持久化当前索引
704
- */
705
- async saveIndexes(): Promise<void> {
706
- try {
707
- const indexesObj: Record<string, Index> = {};
708
- this.indexes.forEach((index, path) => {
709
- indexesObj[path] = index;
710
- });
711
-
712
- const indexBytes = new TextEncoder().encode(JSON.stringify(indexesObj));
713
- await this.db.datastore.put(DBPrefix.dsIndexes.child(new Key(this.name)), indexBytes);
714
- } catch (err) {
715
- throw new Error(`Failed to save indexes: ${err instanceof Error ? err.message : String(err)}`);
716
- }
717
- }
718
-
719
- /**
720
- * 向索引添加条目
721
- */
722
- async indexAdd(txn: any, key: Key, data: Uint8Array): Promise<void> {
723
- for (const [path, index] of this.indexes.entries()) {
724
- try {
725
-
726
- const decoded = dagCBOR.decode<Uint8Array>(data);
727
- await this.indexUpdate(path, index, txn, key, decoded, false);
728
- } catch (err) {
729
- throw err;
730
- }
731
- }
732
- }
733
-
734
- /**
735
- * 从索引中移除条目
736
- * 确保传入的是旧记录的数据,而非新记录
737
- */
738
- async indexDelete(txn: any, key: Key, originalData: Uint8Array): Promise<void> {
739
- for (const [path, index] of this.indexes.entries()) {
740
- try {
741
- await this.indexUpdate(path, index, txn, key, originalData, true);
742
- } catch (err) {
743
- throw err;
744
- }
745
- }
746
- }
747
-
748
- /**
749
- * 在项目上添加或移除特定索引
750
- */
751
- async indexUpdate(field: string, index: Index, txn: any, key: Key, input: Uint8Array, deleteOp: boolean): Promise<void> {
752
- try {
753
- const valueKey = getIndexValue(field, input);
754
-
755
- const indexKey = indexPrefix.child(this.baseKey()).child(new Key(field)).child(new Key(valueKey.toString().substring(1)));
756
- let data: Uint8Array | null = null;
757
-
758
- try {
759
- data = await txn.get(indexKey);
760
- } catch (err: any) {
761
- if (err.code !== 'ERR_NOT_FOUND') {
762
- throw err;
763
- }
764
- }
765
-
766
- const indexValue = new KeyList();
767
- if (data) {
768
- const decoded = JSON.parse(new TextDecoder().decode(data));
769
- indexValue.fromArray(decoded);
770
- }
771
-
772
- if (deleteOp) {
773
- indexValue.remove(key);
774
- } else {
775
- if (index.unique) {
776
- indexValue.remove(key);
777
- }
778
- indexValue.add(key);
779
- }
780
-
781
- if (indexValue.size() === 0) {
782
- await txn.delete(indexKey);
783
- } else {
784
- const encodedValue = new TextEncoder().encode(JSON.stringify(indexValue.toArray()));
785
- await txn.put(indexKey, encodedValue);
786
- }
787
- } catch (err) {
788
- if (err === ErrNotIndexable) {
789
- return;
790
- }
791
- throw err;
792
- }
793
- }
794
- }
795
-
796
- /**
797
- * 返回对输入的字段搜索结果
798
- */
799
- function getIndexValue(field: string, input: Uint8Array): Key {
800
- const jsonObj = JSON.parse(new TextDecoder().decode(input));
801
- const value = traverseFieldPathMap(jsonObj, field);
802
-
803
- if (value === undefined) {
804
- throw ErrNotIndexable;
805
- }
806
-
807
- return new Key(String(value));
808
- }
809
-
810
- /**
811
- * KeyList是索引指向的唯一、排序的键切片
812
- */
813
- class KeyList {
814
- private keys: Uint8Array[] = [];
815
-
816
- /**
817
- * 添加键到列表(如果不存在)
818
- */
819
- add(key: Key): void {
820
- const bytes = key.uint8Array();
821
-
822
- let i = this.binarySearch(bytes);
823
-
824
- if (i < this.keys.length && this.bytesEqual(this.keys[i]!, bytes)) {
825
- return; // 已添加
826
- }
827
-
828
- this.keys.splice(i, 0, bytes);
829
- }
830
-
831
- /**
832
- * 从列表中移除键
833
- */
834
- remove(key: Key): void {
835
- const bytes = key.uint8Array();
836
-
837
- let i = this.binarySearch(bytes);
838
-
839
- if (i < this.keys.length && this.keys[i] && this.bytesEqual(this.keys[i], bytes)) {
840
- this.keys.splice(i, 1);
841
- }
842
- }
843
-
844
- /**
845
- * 检查键是否在列表中
846
- */
847
- in(key: Key): boolean {
848
- const bytes = key.uint8Array();
849
- const i = this.binarySearch(bytes);
850
- return i < this.keys.length && this.keys[i] !== undefined && this.bytesEqual(this.keys[i], bytes);
851
- }
852
-
853
- /**
854
- * 获取列表大小
855
- */
856
- size(): number {
857
- return this.keys.length;
858
- }
859
-
860
- /**
861
- * 转换为数组
862
- */
863
- toArray(): Uint8Array[] {
864
- return [...this.keys];
865
- }
866
-
867
- /**
868
- * 从数组填充
869
- */
870
- fromArray(arr: Uint8Array[]): void {
871
- this.keys = [...arr];
872
- }
873
-
874
- /**
875
- * 二分查找键位置
876
- */
877
- private binarySearch(bytes: Uint8Array): number {
878
- let low = 0;
879
- let high = this.keys.length - 1;
880
-
881
- while (low <= high) {
882
- const mid = Math.floor((low + high) / 2);
883
- // Add non-null assertion operator (!) to tell TypeScript this is always defined
884
- const cmp = this.bytesCompare(this.keys[mid]!, bytes);
885
-
886
- if (cmp < 0) {
887
- low = mid + 1;
888
- } else if (cmp > 0) {
889
- high = mid - 1;
890
- } else {
891
- return mid; // 找到
892
- }
893
- }
894
-
895
- return low; // 未找到,返回插入位置
896
- }
897
-
898
- /**
899
- * 比较两个字节数组
900
- */
901
- private bytesCompare(a: Uint8Array, b: Uint8Array): number {
902
- const len = Math.min(a.length, b.length);
903
-
904
- for (let i = 0; i < len; i++) {
905
- if (a[i] !== b[i]) {
906
- return a[i]! < b[i]! ? -1 : 1;
907
- }
908
- }
909
-
910
- if (a.length < b.length) return -1;
911
- if (a.length > b.length) return 1;
912
- return 0;
913
- }
914
-
915
- /**
916
- * 检查两个字节数组是否相等
917
- */
918
- private bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
919
- if (a.length !== b.length) return false;
920
-
921
- for (let i = 0; i < a.length; i++) {
922
- if (a[i] !== b[i]) return false;
923
- }
924
-
925
- return true;
926
- }
927
- }
928
-
929
-
930
-
931
-
932
- /**
933
- * Transaction class for collections
934
- */
935
- export class Txn implements ITxn{
936
- private actions: Action[] = [];
937
- private discarded = false;
938
- private committed = false;
939
-
940
- constructor(
941
- private collection: Collection,
942
- private token?: ThreadToken,
943
- private readonly readonly: boolean = false
944
- ) {}
945
-
946
- /**
947
- * Create new instances
948
- */
949
- async create(...newInstances: Uint8Array[]): Promise<InstanceID[]> {
950
- const results: InstanceID[] = new Array(newInstances.length);
951
-
952
- for (let i = 0; i < newInstances.length; i++) {
953
- if (this.readonly) {
954
- throw ErrReadonlyTx;
955
- }
956
-
957
- // Copy the data to avoid modifying the input
958
- const updated = newInstances[i]!.slice(0);
959
-
960
- // Try to get instance ID, set one if missing
961
- let id = await getInstanceID(updated);
962
- if (id === EmptyInstanceID) {
963
- const result = setNewInstanceID(updated);
964
- id = result.id;
965
- const updatedWithId = result.data;
966
-
967
- // Validate schema
968
- this.collection.validInstance(updatedWithId);
969
-
970
- results[i] = id;
971
- const key = this.collection.baseKey().child(new Key(id));
972
-
973
- try {
974
- const exists = await this.collection.db.datastore.has(key);
975
- if (exists) {
976
- throw errCantCreateExistingInstance;
977
- }
978
- } catch (err) {
979
- if (err !== errCantCreateExistingInstance) {
980
- throw new Error(`Error checking if instance exists: ${err instanceof Error ? err.message : String(err)}`);
981
- }
982
- throw err;
983
- }
984
-
985
- // Set modified timestamp
986
- const { data: updatedWithTimestamp } = setModifiedTag(updatedWithId);
987
-
988
- // Add action
989
- this.actions.push({
990
- type: CoreActionType.Create,
991
- instanceID: id,
992
- collectionName: this.collection.name,
993
- current: updatedWithTimestamp
994
- });
995
- }
996
- }
997
-
998
- return results;
999
- }
1000
-
1001
- /**
1002
- * Verify instance changes without saving them
1003
- */
1004
- async verify(...updated: Uint8Array[]): Promise<void> {
1005
- try {
1006
- const identity = await this.token?.pubKey();
1007
- if (!identity) {
1008
- throw new Error('Identity not found');
1009
- }
1010
- const actions = await this.createSaveActions(identity, ...updated);
1011
-
1012
- const { events } = await this.createEvents(actions);
1013
- if (events.length === 0) {
1014
- return;
1015
- }
1016
-
1017
- for (const e of events) {
1018
- await this.collection.validWrite(identity, e);
1019
- }
1020
- } catch (err) {
1021
- throw new Error(`Verification failed: ${err instanceof Error ? err.message : String(err)}`);
1022
- }
1023
- }
1024
-
1025
- /**
1026
- * Save instance changes
1027
- */
1028
- async save(...updated: Uint8Array[]): Promise<void> {
1029
- try {
1030
- const identity = await this.token?.pubKey() || null;
1031
-
1032
- const actions = await this.createSaveActions(identity, ...updated);
1033
-
1034
- this.actions.push(...actions);
1035
- } catch (err) {
1036
- throw new Error(`Save failed: ${err instanceof Error ? err.message : String(err)}`);
1037
- }
1038
- }
1039
-
1040
- /**
1041
- * Helper to create save actions
1042
- */
1043
- private async createSaveActions(identity: PubKey|null, ...updated: Uint8Array[]): Promise<Action[]> {
1044
- const actions: Action[] = [];
1045
-
1046
- for (let i = 0; i < updated.length; i++) {
1047
- if (this.readonly) {
1048
- throw ErrReadonlyTx;
1049
- }
1050
-
1051
- // Copy the data to avoid modifying the input
1052
- const next = updated[i]!.slice(0);
1053
-
1054
- // Validate schema
1055
- this.collection.validInstance(next);
1056
-
1057
- // Set modified timestamp
1058
- const { data: nextWithTimestamp } = setModifiedTag(next);
1059
-
1060
- // Get instance ID
1061
- const id = await getInstanceID(nextWithTimestamp);
1062
- if (id === EmptyInstanceID) {
1063
- throw errMissingInstanceID;
1064
- }
1065
-
1066
- // Get previous version
1067
- const key = this.collection.baseKey().child(new Key(id));
1068
- let previous: Uint8Array;
1069
-
1070
- try {
1071
- previous = await this.collection.db.datastore.get(key);
1072
- } catch (err: any) {
1073
- // If not found, use empty object
1074
- if (err.code === 'ERR_NOT_FOUND') {
1075
- previous = new TextEncoder().encode('{}');
1076
- } else {
1077
- throw err;
1078
- }
1079
- }
1080
-
1081
- // Apply read filter if needed
1082
- if (previous.length > 0 && identity !== null) {
1083
- const filtered = await this.collection.filterRead(identity, previous);
1084
- previous = filtered || previous;
1085
- }
1086
-
1087
- // Add action
1088
- actions.push({
1089
- type: CoreActionType.Save,
1090
- instanceID: id,
1091
- collectionName: this.collection.name,
1092
- previous,
1093
- current: nextWithTimestamp
1094
- });
1095
- }
1096
-
1097
- return actions;
1098
- }
1099
-
1100
- /**
1101
- * Delete instances
1102
- */
1103
- async delete(...ids: InstanceID[]): Promise<void> {
1104
- for (let i = 0; i < ids.length; i++) {
1105
- if (this.readonly) {
1106
- throw ErrReadonlyTx;
1107
- }
1108
-
1109
- const key = this.collection.baseKey().child(new Key(ids[i]!));
1110
- const exists = await this.collection.db.datastore.has(key);
1111
-
1112
- if (!exists) {
1113
- // Nothing to do
1114
- continue;
1115
- }
1116
-
1117
- // Add action
1118
- this.actions.push({
1119
- type: CoreActionType.Delete,
1120
- instanceID: ids[i]!,
1121
- collectionName: this.collection.name
1122
- });
1123
- }
1124
- }
1125
-
1126
- /**
1127
- * Check if instances exist
1128
- */
1129
- async has(...ids: InstanceID[]): Promise<boolean> {
1130
- const validationResult = await this.collection.db.connector?.validate(this.token);
1131
- if (validationResult) {
1132
- throw validationResult;
1133
- }
1134
-
1135
- const pk = await this.token?.pubKey();
1136
-
1137
-
1138
- for (let i = 0; i < ids.length; i++) {
1139
- const key = this.collection.baseKey().child(new Key(ids[i]!));
1140
- const exists = await this.collection.db.datastore.has(key);
1141
-
1142
- if (exists) {
1143
- // If no read filter, instance is accessible
1144
- if (!this.collection.readFilter) {
1145
- continue;
1146
- }
1147
-
1148
- // Otherwise, check read filter
1149
- const bytes = await this.collection.db.datastore.get(key);
1150
- const filtered = await this.collection.filterRead(pk, bytes);
1151
-
1152
- // If filtered to null, user doesn't have access
1153
- if (!filtered) {
1154
- return false;
1155
- }
1156
- } else {
1157
- // Instance doesn't exist
1158
- return false;
1159
- }
1160
- }
1161
-
1162
- // All instances exist and are accessible
1163
- return true;
1164
- }
1165
-
1166
- /**
1167
- * Find instance by ID
1168
- */
1169
- async findByID(id: InstanceID): Promise<Object> {
1170
- const validationResult = await this.collection.db.connector?.validate(this.token);
1171
- if (validationResult) {
1172
- throw validationResult;
1173
- }
1174
-
1175
- const key = this.collection.baseKey().child(new Key(id));
1176
- let bytes: Uint8Array;
1177
-
1178
- try {
1179
- bytes = await this.collection.db.datastore.get(key);
1180
- } catch (err: any) {
1181
- if (err.code === 'ERR_NOT_FOUND') {
1182
- throw ErrInstanceNotFound;
1183
- }
1184
- throw err;
1185
- }
1186
-
1187
- const pk = await this.token?.pubKey();
1188
-
1189
- const filtered = await this.collection.filterRead(pk, bytes);
1190
-
1191
- if (!filtered) {
1192
- throw ErrInstanceNotFound;
1193
- }
1194
- let parsedValue;
1195
- try {
1196
- parsedValue = JSON.parse(new TextDecoder().decode(filtered));
1197
- } catch (err) {
1198
- console.warn("Failed to parse JSON for sorting:", err);
1199
- parsedValue = {};
1200
- }
1201
-
1202
- return parsedValue;
1203
- }
1204
-
1205
- /**
1206
- * Find instances matching a query
1207
- */
1208
- async find(q?: Query): Promise<Object[]> {
1209
- try {
1210
- // 验证令牌
1211
- const validationError = await this.collection.db.connector?.validate(this.token);
1212
- if (validationError) {
1213
- throw validationError;
1214
- }
1215
- if (!q) {
1216
- q = new Query();
1217
- }
1218
- const validationResult = q.validate();
1219
- if (validationResult) {
1220
- throw new Error(`Invalid query: ${validationResult.message}`);
1221
- }
1222
-
1223
- const txn = await this.collection.db.datastore.newTransactionExtended(true);
1224
-
1225
- try {
1226
- // 创建迭代器
1227
- const iter = await this.newIterator(txn, this.collection.baseKey(), q);
1228
-
1229
- try {
1230
- // 获取公钥
1231
- const pk = await this.token?.pubKey();
1232
-
1233
-
1234
- // 存储结果
1235
- const values: MarshaledResult[] = [];
1236
-
1237
- // 跟踪返回的实际值数量(考虑到读取过滤器和查询中的任何索引)
1238
- let count = 0;
1239
-
1240
- // 迭代所有结果
1241
- while (true) {
1242
- const result = await iter.next();
1243
- if (!result || !result.done === false) {
1244
- break;
1245
- }
1246
-
1247
- // 应用读取过滤器
1248
- let filteredValue: Uint8Array | null = null;
1249
- try {
1250
- filteredValue = await this.collection.filterRead(pk, result.value);
1251
- } catch (err) {
1252
- throw new Error(`Filter read error: ${err instanceof Error ? err.message : String(err)}`);
1253
- }
1254
-
1255
- if (filteredValue) {
1256
- // 只计算未被读取过滤器过滤掉的有效值
1257
- count++;
1258
-
1259
- if (count > q.skip) {
1260
- // 解析JSON以便之后排序
1261
- let parsedValue;
1262
- try {
1263
- parsedValue = JSON.parse(new TextDecoder().decode(filteredValue));
1264
- } catch (err) {
1265
- console.warn("Failed to parse JSON for sorting:", err);
1266
- parsedValue = {};
1267
- }
1268
-
1269
- values.push({
1270
- instanceID: result.instanceID,
1271
- key: result.key,
1272
- value: filteredValue,
1273
- marshaledValue: parsedValue
1274
- });
1275
- }
1276
- }
1277
-
1278
- // 如果达到限制,则停止
1279
- if (q.limit > 0 && values.length === q.limit) {
1280
- break;
1281
- }
1282
- }
1283
-
1284
- // 如果需要排序且不是按ID排序
1285
- if (q.sort.fieldPath && q.sort.fieldPath !== idFieldName) {
1286
- // let wrongField = false;
1287
- // let cantCompare = false;
1288
-
1289
- // 对结果进行排序
1290
- values.sort((a, b) => {
1291
- // 获取排序字段的值
1292
- const fieldA = traverseFieldPathMap(a.marshaledValue || {}, q!.sort.fieldPath);
1293
- const fieldB = traverseFieldPathMap(b.marshaledValue || {}, q!.sort.fieldPath);
1294
-
1295
- // 处理缺少值的情况
1296
- if (fieldA === undefined || fieldB === undefined) {
1297
- // wrongField = true;
1298
-
1299
- // 使null值排在最前或最后
1300
- if (fieldA === undefined && fieldB === undefined) return 0;
1301
- return fieldA === undefined ? -1 : 1;
1302
- }
1303
-
1304
- try {
1305
- // 比较两个字段值
1306
- let result = compare(fieldA, fieldB);
1307
-
1308
- // 如果是降序,则反转比较结果
1309
- if (q!.sort.desc) {
1310
- result *= -1;
1311
- }
1312
-
1313
- return result;
1314
- } catch (err) {
1315
- // cantCompare = true;
1316
- return 0;
1317
- }
1318
- });
1319
-
1320
- // // 处理排序错误
1321
- // if (wrongField) {
1322
- // throw ErrInvalidSortingField;
1323
- // }
1324
-
1325
- // if (cantCompare) {
1326
- // throw new Error("Can't compare while sorting");
1327
- // }
1328
- }
1329
-
1330
- // 提取最终结果
1331
- const results = values.map(v => v.marshaledValue);
1332
- return results as Object[];
1333
-
1334
- } finally {
1335
- // 清理迭代器资源
1336
- if (iter && typeof iter.close === 'function') {
1337
- await iter.close();
1338
- }
1339
- }
1340
- } finally {
1341
- // 确保丢弃事务
1342
- txn.discard();
1343
- }
1344
- } catch (err) {
1345
- throw new Error(`Find operation failed: ${err instanceof Error ? err.message : String(err)}`);
1346
- }
1347
- }
1348
-
1349
- /**
1350
- * 创建一个适合查询的迭代器
1351
- */
1352
- private async newIterator(txn: any, baseKey: Key, q: Query): Promise<any> {
1353
- // 这里实现迭代器创建逻辑
1354
- // 注意: 这是一个占位符,具体实现取决于你的数据存储实现
1355
-
1356
- // 示例实现:
1357
- const queryOptions: any = {
1358
- prefix: baseKey.toString()
1359
- };
1360
-
1361
- // 如果有ID索引,使用索引查询
1362
- if (q.index) {
1363
- queryOptions.index = q.index;
1364
- }
1365
-
1366
- // 如果需要查找特定ID
1367
- if (q.seek) {
1368
- queryOptions.start = baseKey.child(new Key(q.seek)).toString();
1369
- }
1370
-
1371
- // 创建底层查询
1372
- const results = await txn.query(queryOptions);
1373
-
1374
- // 返回一个支持 next() 和 close() 的迭代器
1375
- return {
1376
- async next() {
1377
- const result = await results.next();
1378
- if (result.done) {
1379
- return { done: true };
1380
- }
1381
-
1382
- // 检查是否匹配查询条件
1383
- if (result.value && q && result.value.value) {
1384
- try {
1385
- const decodedValue = JSON.parse(new TextDecoder().decode(result.value.value));
1386
- if (!q.match(decodedValue)) {
1387
- // 不匹配,跳过这个结果
1388
- return this.next();
1389
- }
1390
- } catch (err) {
1391
- console.warn("Error parsing JSON in query:", err);
1392
- }
1393
- }
1394
-
1395
- return {
1396
- done: false,
1397
- key: result.value.key,
1398
- instanceID: new Key(result.value.key).name(),
1399
- value: result.value.value
1400
- };
1401
- },
1402
-
1403
- async close() {
1404
- // 关闭底层资源
1405
- if (results && typeof results.return === 'function') {
1406
- await results.return();
1407
- }
1408
- }
1409
- };
1410
- }
1411
-
1412
- /**
1413
- * Get instances modified since a specific time
1414
- *
1415
- * The _mod field tracks modified instances, but not those that have been deleted, so we need
1416
- * to query the dispatcher for all (unique) instances in this collection that have been modified
1417
- * at all since `time`.
1418
- */
1419
- async modifiedSince(time: number): Promise<InstanceID[]> {
1420
- // Create a read-only transaction
1421
- const txn = await this.collection.db.datastore.newTransactionExtended(true);
1422
- try {
1423
- const collectionFilter: QueryFilter = (item: any) => {
1424
- const keyString = item.key.toString();
1425
- const key = new Key(keyString);
1426
- return key.type() === this.collection.name;
1427
- };
1428
-
1429
- // Convert time to string for key operations
1430
- const timestr = time.toString();
1431
- // Create the query
1432
- const res = txn.queryExtended({
1433
- prefix: dsDispatcherPrefix.toString(),
1434
- filters: [collectionFilter],
1435
- seekPrefix: dsDispatcherPrefix.child(new Key(timestr)).toString()
1436
- });
1437
-
1438
- // Create a set to store unique instance IDs
1439
- const instanceIdSet = new Set<InstanceID>();
1440
-
1441
- // Process query results
1442
- for await (const entry of res) {
1443
- const id = new Key(entry.key).name();
1444
- instanceIdSet.add(id as InstanceID);
1445
- }
1446
-
1447
- // Convert set to array
1448
- return Array.from(instanceIdSet);
1449
- } finally {
1450
- // Make sure to discard the transaction
1451
- txn.discard();
1452
- }
1453
- }
1454
- /**
1455
- * Commit the transaction
1456
- */
1457
- async commit(): Promise<void> {
1458
-
1459
-
1460
- try {
1461
- const { events, node } = await this.createEvents(this.actions);
1462
- if (!node) {
1463
- return;
1464
- }
1465
- await this.collection.db.connector?.createNetRecord(node, this.token);
1466
- await this.collection.db.dispatcher?.dispatch(events);
1467
- this.committed = true;
1468
- } catch (err) {
1469
- throw new Error(`Commit failed: ${err instanceof Error ? err.message : String(err)}`);
1470
- }
1471
- }
1472
-
1473
- /**
1474
- * Discard the transaction
1475
- */
1476
- discard(): void {
1477
- this.discarded = true;
1478
- }
1479
-
1480
- /**
1481
- * Refresh collection reference
1482
- */
1483
- refreshCollection(): void {
1484
- const c = this.collection.db.collections.get(this.collection.name);
1485
- if (!c) {
1486
- throw ErrCollectionNotFound;
1487
- }
1488
- this.collection = c as Collection;
1489
- }
1490
-
1491
- /**
1492
- * Create events from actions
1493
- */
1494
- private async createEvents(actions: Action[]): Promise<{ events: Event[], node: IPLDNode | null }> {
1495
- if (this.discarded || this.committed) {
1496
- throw errAlreadyDiscardedCommitedTxn;
1497
- }
1498
-
1499
- try {
1500
- const [events, node] = await this.collection.db.eventcodec.create(actions);
1501
-
1502
- if (events.length === 0 && node) {
1503
- return { events: [], node: null };
1504
- }
1505
- // const node = await cbornode.wrapObject(nodeData);
1506
- // if (!node) {
1507
- // throw new Error('Failed to wrap node data');
1508
- // }
1509
-
1510
- return { events, node };
1511
- } catch (err) {
1512
- throw new Error(`Error creating events: ${err instanceof Error ? err.message : String(err)}`);
1513
- }
1514
- }
1515
- }
1516
-
1517
- /**
1518
- * Create a new collection
1519
- */
1520
- export async function newCollection(db: IDB, config: ICollectionConfig): Promise<Collection> {
1521
- // Validate name
1522
- if (config.name && !nameRx.test(config.name)) {
1523
- throw ErrInvalidName;
1524
- }
1525
-
1526
- // Validate schema
1527
- const idType = getSchemaTypeAtPath(config.schema, idFieldName);
1528
- if (!idType || idType.type !== 'string') {
1529
- throw ErrInvalidCollectionSchema;
1530
- }
1531
-
1532
- // Create collection
1533
- return new Collection(
1534
- config.name,
1535
- config.schema,
1536
- db,
1537
- config.writeValidator,
1538
- config.readFilter
1539
- );
1540
- }
1541
-
1542
- // Helper functions
1543
-
1544
- /**
1545
- * Get schema type at a path
1546
- */
1547
- function getSchemaTypeAtPath(schema: any, path: string): { type: string } | null {
1548
- const parts = path.split('.');
1549
- let current = schema;
1550
-
1551
- for (const part of parts) {
1552
- const props = getSchemaTypeProperties(current, schema.definitions);
1553
- current = props[part];
1554
-
1555
- if (!current) {
1556
- throw ErrInvalidCollectionSchemaPath;
1557
- }
1558
- }
1559
-
1560
- return current;
1561
- }
1562
-
1563
- /**
1564
- * Get schema type properties
1565
- */
1566
- function getSchemaTypeProperties(schema: any, definitions: any): Record<string, any> {
1567
- if (!schema) {
1568
- return {};
1569
- }
1570
-
1571
- let properties = schema.properties || {};
1572
-
1573
- if (schema.$ref) {
1574
- const parts = schema.$ref.split('/');
1575
- if (parts.length > 0) {
1576
- const defName = parts[parts.length - 1];
1577
- const def = definitions?.[defName];
1578
-
1579
- if (def) {
1580
- properties = def.properties || {};
1581
- }
1582
- }
1583
- }
1584
-
1585
- return properties;
1586
- }
1587
-
1588
- /**
1589
- * Extract instance ID from data
1590
- */
1591
- async function getInstanceID(data: Uint8Array): Promise<InstanceID> {
1592
- try {
1593
- const instance = JSON.parse(new TextDecoder().decode(data));
1594
-
1595
- if (!instance._id) {
1596
- return EmptyInstanceID;
1597
- }
1598
-
1599
- return instance._id;
1600
- } catch (err) {
1601
- throw new Error(`Error getting instance ID: ${err instanceof Error ? err.message : String(err)}`);
1602
- }
1603
- }
1604
-
1605
- /**
1606
- * Set a new instance ID
1607
- */
1608
- function setNewInstanceID(data: Uint8Array): { id: InstanceID, data: Uint8Array } {
1609
- const id = NewInstanceID();
1610
-
1611
- try {
1612
- const instance = JSON.parse(new TextDecoder().decode(data));
1613
- instance._id = id;
1614
-
1615
- return {
1616
- id,
1617
- data: new TextEncoder().encode(JSON.stringify(instance))
1618
- };
1619
- } catch (err) {
1620
- throw new Error(`Error setting instance ID: ${err instanceof Error ? err.message : String(err)}`);
1621
- }
1622
- }
1623
-
1624
- /**
1625
- * 为对象设置修改时间戳标签 (纳秒精度)
1626
- * @param data 包含原始数据的字节数组
1627
- * @returns 包含新时间戳和更新后数据的对象
1628
- */
1629
- function setModifiedTag(data: Uint8Array): { time: bigint, data: Uint8Array } {
1630
- // 生成纳秒级时间戳 (等同于 Go 的 time.Now().UnixNano())
1631
- const timeMs = BigInt(Date.now());
1632
- const randomNs = BigInt(Math.floor(Math.random() * 1000000));
1633
- // 转换为纳秒
1634
- const time = timeMs * 1000000n + randomNs;
1635
-
1636
- try {
1637
- // 解析输入数据为 JSON 对象
1638
- const instance = JSON.parse(new TextDecoder().decode(data));
1639
-
1640
- // 字段名与 Go 代码中相同
1641
- const modFieldName = "_mod";
1642
-
1643
- // 添加或更新 _mod 字段
1644
- // 注意:存储时将 BigInt 转换为数字,这可能会在某些极端情况下丢失精度
1645
- // 但对于一般应用已经足够了
1646
- instance[modFieldName] = time;
1647
-
1648
- // 序列化回字节数组
1649
- const modifiedData = new TextEncoder().encode(jsonStringify(instance));
1650
-
1651
- return {
1652
- time,
1653
- data: modifiedData
1654
- };
1655
- } catch (err) {
1656
- // 处理错误,但避免使用 log.Fatal 这样的致命错误
1657
- console.error(`修改标签设置错误: ${err instanceof Error ? err.message : String(err)}`);
1658
- throw new Error(`设置 _mod 字段失败: ${err instanceof Error ? err.message : String(err)}`);
1659
- }
1660
- }
1661
-
1662
- /**
1663
- * Parse JSON from bytes
1664
- */
1665
- function parseJSON(data: Uint8Array): any {
1666
- return JSON.parse(new TextDecoder().decode(data));
1667
- }
1668
-
1669
- /**
1670
- * 迭代器类用于查询结果的迭代
1671
- */
1672
- class Iterator {
1673
- nextKeys: () => Promise<Key[]>;
1674
- txn: any;
1675
- query: Query;
1676
- keyCache: Key[] = [];
1677
- iter: any;
1678
-
1679
- constructor(txn: any, query: Query) {
1680
- this.txn = txn;
1681
- this.query = query;
1682
- this.nextKeys = async () => { return []; };
1683
- }
1684
-
1685
- /**
1686
- * 返回下一个符合迭代器条件的键值对
1687
- * 如果有错误,ok 为 false,result.error 将返回错误
1688
- */
1689
- async nextSync(): Promise<{ result: any, marshaledValue?: any } | null> {
1690
- if (this.query.index === "") {
1691
- const value: any = {
1692
- result: {
1693
- entry: {},
1694
- error: null
1695
- }
1696
- };
1697
- let ok = false;
1698
-
1699
- for await (const res of this.iter.next()) {
1700
- try {
1701
- const val = JSON.parse(new TextDecoder().decode(res.value));
1702
- ok = this.query.match(val);
1703
-
1704
- if (value.result.error) {
1705
- break;
1706
- }
1707
-
1708
- if (ok) {
1709
- return {
1710
- result: res,
1711
- marshaledValue: val
1712
- };
1713
- }
1714
- } catch (error) {
1715
- value.result.error = error;
1716
- break;
1717
- }
1718
- }
1719
-
1720
- return ok ? value : null;
1721
- }
1722
-
1723
- if (this.keyCache.length === 0) {
1724
- try {
1725
- const newKeys = await this.nextKeys();
1726
-
1727
- if (newKeys.length === 0) {
1728
- return {
1729
- result: {
1730
- entry: {},
1731
- error: null
1732
- }
1733
- };
1734
- }
1735
-
1736
- this.keyCache = [...this.keyCache, ...newKeys];
1737
- } catch (error) {
1738
- return {
1739
- result: {
1740
- entry: {},
1741
- error: error
1742
- }
1743
- };
1744
- }
1745
- }
1746
-
1747
- const key = this.keyCache[0];
1748
- this.keyCache = this.keyCache.slice(1);
1749
-
1750
- try {
1751
- const value = await this.txn.get(key);
1752
-
1753
- return {
1754
- result: {
1755
- entry: {
1756
- key: key!.toString(),
1757
- value: value
1758
- },
1759
- error: null
1760
- }
1761
- };
1762
- } catch (error) {
1763
- return {
1764
- result: {
1765
- entry: {},
1766
- error: error
1767
- }
1768
- };
1769
- }
1770
- }
1771
-
1772
- /**
1773
- * 关闭迭代器并释放资源
1774
- */
1775
- async close(): Promise<void> {
1776
- if (this.iter && typeof this.iter.close === 'function') {
1777
- await this.iter.close();
1778
- }
1779
- }
1780
- }
1781
-
1782
- /**
1783
- * 创建新的迭代器
1784
- */
1785
- export async function newIterator(txn: any, baseKey: Key, q: Query): Promise<Iterator> {
1786
- const i = new Iterator(txn, q);
1787
-
1788
- let prefix: Key;
1789
- if (!q.index) {
1790
- prefix = baseKey;
1791
- } else {
1792
- prefix = indexPrefix.child(baseKey).child(new Key(q.index));
1793
- }
1794
-
1795
- const dsq: any = {
1796
- query: {
1797
- prefix: prefix.toString(),
1798
- // 由于 readFilters 的存在,我们事先不知道需要跳过/限制多少,
1799
- // 所以这里不使用 Skip 和 Limit
1800
- // limit: q.limit,
1801
- // offset: q.skip,
1802
- }
1803
- };
1804
-
1805
- if (q.sort.fieldPath === idFieldName) {
1806
- if (q.sort.desc) {
1807
- dsq.orders = [{ orderByKeyDescending: true }];
1808
- } else {
1809
- dsq.orders = [{ orderByKey: true }];
1810
- }
1811
- }
1812
-
1813
- if (q.seek) {
1814
- dsq.seekPrefix = prefix.child(new Key(q.seek)).toString();
1815
- }
1816
-
1817
- try {
1818
- const iter = await txn.queryExtended(dsq);
1819
- i.iter = iter;
1820
-
1821
- // 未指定键字段或索引,通过基本的"迭代器"传递
1822
- if (!q.index) {
1823
- i.nextKeys = async () => {
1824
- return [];
1825
- };
1826
- return i;
1827
- }
1828
-
1829
- // 索引字段,从索引获取键
1830
- let first = true;
1831
- i.nextKeys = async () => {
1832
- const nKeys: Key[] = [];
1833
-
1834
- while (nKeys.length < iteratorKeyMinCacheSize) {
1835
- const result = await i.iter.nextSync();
1836
- if (!result) {
1837
- if (first) {
1838
- throw ErrIndexNotFound;
1839
- }
1840
- return nKeys;
1841
- }
1842
-
1843
- first = false;
1844
- // result.key 包含索引值,先在这里提取
1845
- const key = new Key(result.result.entry.key);
1846
- const base = prefix.name();
1847
- const name = key.name();
1848
-
1849
- let val: any = name;
1850
- if (isValidJSON(name)) {
1851
- const parsed = JSON.parse(name);
1852
- val = parsed !== null ? parsed : name;
1853
- } else {
1854
- val = name;
1855
- }
1856
-
1857
- try {
1858
- // 使用 JSON 构建文档
1859
- const doc = JSON.stringify({ [base]: val });
1860
- const value = JSON.parse(doc);
1861
-
1862
- // 匹配查询
1863
- const ok = q.match(value);
1864
-
1865
- if (ok) {
1866
- // 解码索引值
1867
- const indexValue = decodeKeyList(result.result.entry.value);
1868
-
1869
- for (const v of indexValue) {
1870
- nKeys.push(new Key(new TextDecoder().decode(v)));
1871
- }
1872
- }
1873
- } catch (err) {
1874
- // 为了让业务逻辑正常进行,这里不报告错误,而是跳过
1875
- continue;
1876
- }
1877
- }
1878
-
1879
- return nKeys;
1880
- };
1881
-
1882
- return i;
1883
- } catch (error) {
1884
- throw error;
1885
- }
1886
- }
1887
-
1888
- /**
1889
- * 辅助函数:检查字符串是否是有效的 JSON
1890
- */
1891
- function isValidJSON(str: string): boolean {
1892
- try {
1893
- JSON.parse(str);
1894
- return true;
1895
- } catch {
1896
- return false;
1897
- }
1898
- }
1899
-
1900
- /**
1901
- * 辅助函数:解码键列表
1902
- */
1903
- function decodeKeyList(data?: Uint8Array): Uint8Array[] {
1904
- if (!data) return [];
1905
- try {
1906
- return JSON.parse(new TextDecoder().decode(data));
1907
- } catch {
1908
- return [];
1909
- }
1910
- }