@peerbit/document 15.1.6 → 15.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/document",
3
- "version": "15.1.6",
3
+ "version": "15.1.7",
4
4
  "description": "Document store implementation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -61,20 +61,21 @@
61
61
  "p-defer": "^4.0.0",
62
62
  "uint8arrays": "^5.1.0",
63
63
  "@peerbit/cache": "3.1.1",
64
- "@peerbit/document-interface": "3.2.79",
65
64
  "@peerbit/crypto": "3.1.6",
65
+ "@peerbit/document-interface": "3.2.79",
66
66
  "@peerbit/indexer-cache": "0.3.0",
67
- "@peerbit/indexer-interface": "3.1.0",
67
+ "@peerbit/diagnostics": "0.1.0",
68
68
  "@peerbit/indexer-simple": "1.3.0",
69
+ "@peerbit/log": "6.2.35",
69
70
  "@peerbit/indexer-sqlite3": "3.0.21",
70
71
  "@peerbit/logger": "2.0.2",
71
- "@peerbit/log": "6.2.35",
72
- "@peerbit/program": "6.0.63",
73
- "@peerbit/rpc": "6.1.31",
74
- "@peerbit/pubsub": "5.4.8",
75
- "@peerbit/shared-log": "16.0.35",
72
+ "@peerbit/program": "6.0.64",
73
+ "@peerbit/pubsub": "5.4.9",
74
+ "@peerbit/indexer-interface": "3.1.0",
76
75
  "@peerbit/stream-interface": "6.0.16",
77
- "@peerbit/time": "3.0.1"
76
+ "@peerbit/time": "3.0.1",
77
+ "@peerbit/shared-log": "16.0.36",
78
+ "@peerbit/rpc": "6.2.0"
78
79
  },
79
80
  "optionalDependencies": {
80
81
  "@peerbit/document-rust": "0.1.1"
@@ -85,10 +86,10 @@
85
86
  "@types/pidusage": "^2.0.5",
86
87
  "pidusage": "^4.0.1",
87
88
  "uuid": "^11.1.1",
88
- "@peerbit/log-rust": "1.1.5",
89
89
  "@peerbit/native-backbone": "0.2.17",
90
- "@peerbit/test-utils": "3.1.44",
91
- "peerbit": "5.4.5"
90
+ "@peerbit/test-utils": "3.1.45",
91
+ "@peerbit/log-rust": "1.1.5",
92
+ "peerbit": "5.4.6"
92
93
  },
93
94
  "repository": {
94
95
  "type": "git",
package/src/program.ts CHANGED
@@ -2622,6 +2622,7 @@ export class Documents<
2622
2622
  prefetch: options.index?.prefetch,
2623
2623
  includeIndexed: options.index?.includeIndexed,
2624
2624
  immutable: this.immutable,
2625
+ profile: options.sync?.profile,
2625
2626
  });
2626
2627
  this._documentInternalChangeListenerCount = Math.max(
2627
2628
  0,
package/src/search.ts CHANGED
@@ -14,6 +14,10 @@ import {
14
14
  PublicSignKey,
15
15
  sha256Base64Sync,
16
16
  } from "@peerbit/crypto";
17
+ import {
18
+ type DiagnosticSink,
19
+ createDiagnosticTrace,
20
+ } from "@peerbit/diagnostics";
17
21
  import * as types from "@peerbit/document-interface";
18
22
  import { CachedIndex, type QueryCacheOptions } from "@peerbit/indexer-cache";
19
23
  import * as indexerTypes from "@peerbit/indexer-interface";
@@ -79,6 +83,11 @@ import {
79
83
  } from "./transform.js";
80
84
 
81
85
  const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
86
+ const QUERY_PROFILE_TARGET_LIMIT = 16;
87
+ type QueryProfile = {
88
+ trace: NonNullable<ReturnType<typeof createDiagnosticTrace>>;
89
+ missingGroups: number;
90
+ };
82
91
 
83
92
  const logger = loggerFn("peerbit:program:document:search");
84
93
  const warn = logger.newScope("warn");
@@ -838,6 +847,8 @@ export type OpenOptions<
838
847
  prefetch?: boolean | Partial<PrefetchOptions>;
839
848
  includeIndexed?: boolean; // if true, indexed representations will always be included in the search results
840
849
  immutable?: boolean; // conflict rule of the owning store: oldest version wins when true (mirrors Documents "immutable")
850
+ /** Advisory query timing; no effect on coverage, authorization or deadlines. */
851
+ profile?: DiagnosticSink;
841
852
  };
842
853
 
843
854
  type IndexableClass<I> = new (
@@ -1089,6 +1100,7 @@ export class DocumentIndex<
1089
1100
  ) => Promise<void>;
1090
1101
 
1091
1102
  private _log: SharedLog<Operation, D, any>;
1103
+ private _queryProfile?: DiagnosticSink;
1092
1104
 
1093
1105
  private _resolverProgramCache?: Map<string | number | bigint, T>;
1094
1106
  private _resolverCache?: Cache<T>;
@@ -1534,6 +1546,7 @@ export class DocumentIndex<
1534
1546
  }
1535
1547
  async open(properties: OpenOptions<T, I, D>) {
1536
1548
  this._log = properties.log;
1549
+ this._queryProfile = properties.profile;
1537
1550
  // Allow reopening with partial options (tests override the index transform)
1538
1551
  const previousEvents = this.documentEvents;
1539
1552
  this.documentEvents =
@@ -2305,6 +2318,7 @@ export class DocumentIndex<
2305
2318
  async close(from?: Program): Promise<boolean> {
2306
2319
  const closed = await super.close(from);
2307
2320
  if (closed) {
2321
+ this._queryProfile = undefined;
2308
2322
  if (this._joinListener) {
2309
2323
  this._query.events.removeEventListener("join", this._joinListener);
2310
2324
  }
@@ -2339,6 +2353,7 @@ export class DocumentIndex<
2339
2353
  async drop(from?: Program): Promise<boolean> {
2340
2354
  const dropped = await super.drop(from);
2341
2355
  if (dropped) {
2356
+ this._queryProfile = undefined;
2342
2357
  this.documentEvents?.removeEventListener(
2343
2358
  "change",
2344
2359
  this.handleDocumentChange,
@@ -4210,7 +4225,55 @@ export class DocumentIndex<
4210
4225
  * @param options
4211
4226
  * @returns
4212
4227
  */
4213
- private async queryCommence<
4228
+ private queryCommence<
4229
+ R extends
4230
+ | types.SearchRequest
4231
+ | types.SearchRequestIndexed
4232
+ | types.IterationRequest,
4233
+ RT extends types.Result = types.ResultTypeFromRequest<R, T, I>,
4234
+ >(
4235
+ queryRequest: R,
4236
+ options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
4237
+ fetchFirstForRemote?: Set<string>,
4238
+ ): Promise<types.Results<RT>[]> {
4239
+ const trace = createDiagnosticTrace(
4240
+ this._queryProfile,
4241
+ "document",
4242
+ "query",
4243
+ );
4244
+ if (!trace) {
4245
+ return this.queryCommenceWithProfile<R, RT>(
4246
+ queryRequest,
4247
+ options,
4248
+ fetchFirstForRemote,
4249
+ );
4250
+ }
4251
+ const profile: QueryProfile = { trace, missingGroups: 0 };
4252
+ let outcome = "rejected";
4253
+ trace.emit({
4254
+ name: "documents.query.start",
4255
+ details: { immutable: this.immutable },
4256
+ });
4257
+ return (async () => {
4258
+ try {
4259
+ const results = await this.queryCommenceWithProfile<R, RT>(
4260
+ queryRequest,
4261
+ options,
4262
+ fetchFirstForRemote,
4263
+ profile,
4264
+ );
4265
+ outcome = "fulfilled";
4266
+ return results;
4267
+ } finally {
4268
+ trace.finish({
4269
+ name: "documents.query.settle",
4270
+ details: { outcome, missingGroups: profile.missingGroups },
4271
+ });
4272
+ }
4273
+ })();
4274
+ }
4275
+
4276
+ private async queryCommenceWithProfile<
4214
4277
  R extends
4215
4278
  | types.SearchRequest
4216
4279
  | types.SearchRequestIndexed
@@ -4220,7 +4283,9 @@ export class DocumentIndex<
4220
4283
  queryRequest: R,
4221
4284
  options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
4222
4285
  fetchFirstForRemote?: Set<string>,
4286
+ profile?: QueryProfile,
4223
4287
  ): Promise<types.Results<RT>[]> {
4288
+ const trace = profile?.trace;
4224
4289
  const local = typeof options?.local === "boolean" ? options?.local : true;
4225
4290
  let remote:
4226
4291
  | RemoteQueryOptions<
@@ -4259,11 +4324,22 @@ export class DocumentIndex<
4259
4324
  [];
4260
4325
 
4261
4326
  if (local) {
4327
+ const startedAt = trace?.now();
4328
+ trace?.emit({
4329
+ name: "documents.query.local",
4330
+ details: { edge: "start" },
4331
+ });
4262
4332
  const results = await this.processQuery(
4263
4333
  queryRequest,
4264
4334
  this.node.identity.publicKey,
4265
4335
  true,
4266
4336
  );
4337
+ trace?.emit({
4338
+ name: "documents.query.local",
4339
+ durationMs: trace.now() - startedAt!,
4340
+ entries: results.results.length,
4341
+ details: { edge: "end" },
4342
+ });
4267
4343
  if (results.results.length > 0) {
4268
4344
  options?.onResponse &&
4269
4345
  (await options.onResponse(results, this.node.identity.publicKey));
@@ -4284,6 +4360,11 @@ export class DocumentIndex<
4284
4360
  (!("args" in coverProps) || (coverProps as any).args == null);
4285
4361
  const remoteWasExplicit = options?.remote != null;
4286
4362
 
4363
+ const coverStartedAt = trace?.now();
4364
+ trace?.emit({
4365
+ name: "documents.query.cover",
4366
+ details: { edge: "start" },
4367
+ });
4287
4368
  let replicatorGroups = options?.remote?.from
4288
4369
  ? options?.remote?.from
4289
4370
  : await this._log.getCover(coverProps, {
@@ -4292,6 +4373,16 @@ export class DocumentIndex<
4292
4373
  reachableOnly: !!remote.wait, // when we want to merge joining we can ignore pending to be online peers and instead consider them once they become online
4293
4374
  signal: options?.signal,
4294
4375
  });
4376
+ trace?.emit({
4377
+ name: "documents.query.cover",
4378
+ durationMs: trace.now() - coverStartedAt!,
4379
+ targets: replicatorGroups.length,
4380
+ details: {
4381
+ edge: "end",
4382
+ explicit: !!options?.remote?.from,
4383
+ reachableOnly: !!remote.wait,
4384
+ },
4385
+ });
4295
4386
 
4296
4387
  // Cold start: cover can be temporarily self-only or empty while
4297
4388
  // replication metadata converges. For explicit bounded remote searches,
@@ -4365,6 +4456,11 @@ export class DocumentIndex<
4365
4456
  from?: PublicSignKey;
4366
4457
  }[],
4367
4458
  ) => {
4459
+ const startedAt = trace?.now();
4460
+ trace?.emit({
4461
+ name: "documents.query.introduce",
4462
+ details: { edge: "start" },
4463
+ });
4368
4464
  const resultInitialized = await introduceEntries(
4369
4465
  queryRequest,
4370
4466
  results,
@@ -4373,6 +4469,12 @@ export class DocumentIndex<
4373
4469
  this._sync,
4374
4470
  options,
4375
4471
  );
4472
+ trace?.emit({
4473
+ name: "documents.query.introduce",
4474
+ durationMs: trace.now() - startedAt!,
4475
+ count: resultInitialized.length,
4476
+ details: { edge: "end" },
4477
+ });
4376
4478
  for (const r of resultInitialized) {
4377
4479
  resolved.push(r.response);
4378
4480
  }
@@ -4425,9 +4527,54 @@ export class DocumentIndex<
4425
4527
  .map((x) => [x]);
4426
4528
 
4427
4529
  options?.onRemoteTargets?.(selectedRemoteHashes);
4530
+ if (trace) {
4531
+ trace.emit({
4532
+ name: "documents.query.targets",
4533
+ targets: selectedRemoteHashes.length,
4534
+ details: {
4535
+ requestTargets: groupHashes.length,
4536
+ sampledTargets: Math.min(
4537
+ QUERY_PROFILE_TARGET_LIMIT,
4538
+ selectedRemoteHashes.length,
4539
+ ),
4540
+ },
4541
+ });
4542
+ for (
4543
+ let i = 0;
4544
+ i <
4545
+ Math.min(QUERY_PROFILE_TARGET_LIMIT, selectedRemoteHashes.length);
4546
+ i++
4547
+ ) {
4548
+ const hash = selectedRemoteHashes[i];
4549
+ // Presence is only local direct-peer evidence, not reachability,
4550
+ // subscriber readiness, authority or receipt eligibility.
4551
+ const peers = (
4552
+ this.node.services.pubsub as { peers?: Map<string, unknown> }
4553
+ ).peers;
4554
+ trace.emit({
4555
+ name: "documents.query.target",
4556
+ peer: hash,
4557
+ details: {
4558
+ index: i,
4559
+ directPeerPresent:
4560
+ peers instanceof Map ? peers.has(hash) : undefined,
4561
+ },
4562
+ });
4563
+ }
4564
+ }
4428
4565
  extraPromises && (await Promise.all(extraPromises));
4429
4566
  let tearDown: (() => void) | undefined = undefined;
4430
4567
  const search = this;
4568
+ const requestOptions = trace
4569
+ ? {
4570
+ ...remote,
4571
+ profile: (event: Parameters<DiagnosticSink>[0]) =>
4572
+ trace.emit({
4573
+ ...event,
4574
+ details: { ...event.details, requestTraceId: event.traceId },
4575
+ }),
4576
+ }
4577
+ : remote;
4431
4578
 
4432
4579
  try {
4433
4580
  groupHashes.length > 0 &&
@@ -4438,7 +4585,7 @@ export class DocumentIndex<
4438
4585
  responseHandler,
4439
4586
  search._prefetch?.accumulator
4440
4587
  ? {
4441
- ...remote,
4588
+ ...requestOptions,
4442
4589
  responseInterceptor(fn) {
4443
4590
  const listener = (evt: {
4444
4591
  detail: {
@@ -4490,10 +4637,18 @@ export class DocumentIndex<
4490
4637
  };
4491
4638
  },
4492
4639
  }
4493
- : remote,
4640
+ : requestOptions,
4494
4641
  ));
4495
4642
  } catch (error) {
4496
4643
  if (error instanceof MissingResponsesError) {
4644
+ if (profile) {
4645
+ profile.missingGroups = error.missingGroups.length;
4646
+ trace!.emit({
4647
+ name: "documents.query.missing",
4648
+ count: error.missingGroups.length,
4649
+ details: { tolerated: !remote.throwOnMissing },
4650
+ });
4651
+ }
4497
4652
  warn("Did not reciveve responses from all shard");
4498
4653
  if (options?.onMissingResponses) {
4499
4654
  await options.onMissingResponses(error);