@peerbit/document 11.1.1 → 11.2.1

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,93 +1,93 @@
1
1
  {
2
- "name": "@peerbit/document",
3
- "version": "11.1.1",
4
- "description": "Document store implementation",
5
- "type": "module",
6
- "sideEffects": false,
7
- "types": "./dist/src/index.d.ts",
8
- "typesVersions": {
9
- "*": {
10
- "*": [
11
- "*",
12
- "dist/*",
13
- "dist/src/*",
14
- "dist/src/*/index"
15
- ],
16
- "src/*": [
17
- "*",
18
- "dist/*",
19
- "dist/src/*",
20
- "dist/src/*/index"
21
- ]
22
- }
23
- },
24
- "files": [
25
- "src",
26
- "dist",
27
- "!dist/e2e",
28
- "!dist/test",
29
- "!**/*.tsbuildinfo"
30
- ],
31
- "exports": {
32
- ".": {
33
- "types": "./dist/src/index.d.ts",
34
- "import": "./dist/src/index.js"
35
- }
36
- },
37
- "eslintConfig": {
38
- "extends": "peerbit",
39
- "parserOptions": {
40
- "project": true,
41
- "sourceType": "module"
42
- },
43
- "ignorePatterns": [
44
- "!.aegir.js",
45
- "test/ts-use",
46
- "*.d.ts"
47
- ]
48
- },
49
- "publishConfig": {
50
- "access": "public"
51
- },
52
- "author": "dao.xyz",
53
- "license": "MIT",
54
- "dependencies": {
55
- "@dao-xyz/borsh": "^6.0.0",
56
- "@chainsafe/libp2p-yamux": "^8.0.0",
57
- "@libp2p/interface": "^3.1.0",
58
- "@libp2p/tcp": "^11.0.2",
59
- "@multiformats/multiaddr": "^13.0.1",
60
- "p-defer": "^4.0.0",
61
- "uint8arrays": "^5.1.0",
62
- "@peerbit/cache": "2.2.0",
63
- "@peerbit/log": "4.2.5",
64
- "@peerbit/logger": "2.0.0",
65
- "@peerbit/pubsub": "4.1.1",
66
- "@peerbit/crypto": "2.4.0",
67
- "@peerbit/program": "5.4.3",
68
- "@peerbit/shared-log": "11.6.5",
69
- "@peerbit/rpc": "5.4.5",
70
- "@peerbit/indexer-interface": "2.1.0",
71
- "@peerbit/indexer-simple": "1.2.0",
72
- "@peerbit/indexer-sqlite3": "1.3.0",
73
- "@peerbit/document-interface": "3.1.5",
74
- "@peerbit/indexer-cache": "0.2.0",
75
- "@peerbit/stream-interface": "5.3.0"
76
- },
77
- "devDependencies": {
78
- "@types/pidusage": "^2.0.5",
79
- "pidusage": "^4.0.1",
80
- "uuid": "^10.0.0",
81
- "@libp2p/websockets": "^10.1.0",
82
- "@chainsafe/libp2p-noise": "^17.0.0",
83
- "@peerbit/test-utils": "2.3.5",
84
- "peerbit": "4.4.5",
85
- "@peerbit/time": "2.3.0"
86
- },
87
- "scripts": {
88
- "clean": "aegir clean",
89
- "build": "aegir build --no-bundle",
90
- "test": "aegir test --target node",
91
- "lint": "aegir lint"
92
- }
93
- }
2
+ "name": "@peerbit/document",
3
+ "version": "11.2.1",
4
+ "description": "Document store implementation",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "types": "./dist/src/index.d.ts",
8
+ "typesVersions": {
9
+ "*": {
10
+ "*": [
11
+ "*",
12
+ "dist/*",
13
+ "dist/src/*",
14
+ "dist/src/*/index"
15
+ ],
16
+ "src/*": [
17
+ "*",
18
+ "dist/*",
19
+ "dist/src/*",
20
+ "dist/src/*/index"
21
+ ]
22
+ }
23
+ },
24
+ "files": [
25
+ "src",
26
+ "dist",
27
+ "!dist/e2e",
28
+ "!dist/test",
29
+ "!**/*.tsbuildinfo"
30
+ ],
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/src/index.d.ts",
34
+ "import": "./dist/src/index.js"
35
+ }
36
+ },
37
+ "eslintConfig": {
38
+ "extends": "peerbit",
39
+ "parserOptions": {
40
+ "project": true,
41
+ "sourceType": "module"
42
+ },
43
+ "ignorePatterns": [
44
+ "!.aegir.js",
45
+ "test/ts-use",
46
+ "*.d.ts"
47
+ ]
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "clean": "aegir clean",
54
+ "build": "aegir build --no-bundle",
55
+ "test": "aegir test --target node",
56
+ "lint": "aegir lint"
57
+ },
58
+ "author": "dao.xyz",
59
+ "license": "MIT",
60
+ "dependencies": {
61
+ "@dao-xyz/borsh": "^6.0.0",
62
+ "@chainsafe/libp2p-yamux": "^8.0.0",
63
+ "@libp2p/interface": "^3.1.0",
64
+ "@libp2p/tcp": "^11.0.2",
65
+ "@multiformats/multiaddr": "^13.0.1",
66
+ "@peerbit/cache": "workspace:*",
67
+ "@peerbit/crypto": "workspace:*",
68
+ "@peerbit/logger": "workspace:*",
69
+ "@peerbit/log": "workspace:*",
70
+ "@peerbit/pubsub": "workspace:*",
71
+ "@peerbit/program": "workspace:*",
72
+ "@peerbit/rpc": "workspace:*",
73
+ "@peerbit/shared-log": "workspace:*",
74
+ "@peerbit/indexer-interface": "workspace:*",
75
+ "@peerbit/indexer-simple": "workspace:*",
76
+ "@peerbit/indexer-sqlite3": "workspace:*",
77
+ "@peerbit/document-interface": "workspace:*",
78
+ "@peerbit/indexer-cache": "workspace:*",
79
+ "@peerbit/stream-interface": "workspace:*",
80
+ "p-defer": "^4.0.0",
81
+ "uint8arrays": "^5.1.0"
82
+ },
83
+ "devDependencies": {
84
+ "@peerbit/test-utils": "workspace:*",
85
+ "@peerbit/time": "workspace:*",
86
+ "peerbit": "workspace:*",
87
+ "@types/pidusage": "^2.0.5",
88
+ "pidusage": "^4.0.1",
89
+ "uuid": "^10.0.0",
90
+ "@libp2p/websockets": "^10.1.0",
91
+ "@chainsafe/libp2p-noise": "^17.0.0"
92
+ }
93
+ }
package/src/search.ts CHANGED
@@ -142,12 +142,32 @@ export type JoiningOnMissedResults = (evt: {
142
142
  peer: PublicSignKey;
143
143
  }) => void | Promise<void>;
144
144
 
145
- export type LateResultsEvent = {
145
+ export type OutOfOrderMode = "drop" | "queue";
146
+
147
+ export type LateResultsItem = {
148
+ indexed: WithContext<any>;
149
+ context: types.Context;
150
+ from: PublicSignKey;
151
+ value?: any;
152
+ };
153
+
154
+ export type LateResultsEvent<
155
+ M extends OutOfOrderMode = "drop",
156
+ Item = LateResultsItem,
157
+ > = {
146
158
  /** Count of items that should have appeared earlier than the current frontier */
147
159
  amount: number;
148
160
 
149
161
  /** If attributable, the peer that produced the late items */
150
162
  peer?: PublicSignKey;
163
+ } & (M extends "queue" ? { items: Item[] } : { items?: undefined });
164
+
165
+ export type LateResultsHelpers<
166
+ M extends OutOfOrderMode = "drop",
167
+ Item = LateResultsItem,
168
+ > = {
169
+ /** Collect concrete late items if available for the chosen mode */
170
+ collect: () => Promise<M extends "queue" ? Item[] : Item[] | undefined>;
151
171
  };
152
172
 
153
173
  export type WaitBehavior =
@@ -197,7 +217,21 @@ export type QueryOptions<T, I, D, Resolve extends boolean | undefined> = {
197
217
  resolve?: Resolve;
198
218
  signal?: AbortSignal;
199
219
  updates?: UpdateOptions<T, I, Resolve>;
200
- onLateResults?: (evt: LateResultsEvent) => void | Promise<void>;
220
+ outOfOrder?:
221
+ | {
222
+ mode?: "drop";
223
+ handle?: (
224
+ evt: LateResultsEvent<"drop">,
225
+ helpers: LateResultsHelpers<"drop">,
226
+ ) => void | Promise<void>;
227
+ }
228
+ | {
229
+ mode: "queue";
230
+ handle?: (
231
+ evt: LateResultsEvent<"queue">,
232
+ helpers: LateResultsHelpers<"queue">,
233
+ ) => void | Promise<void>;
234
+ };
201
235
  /**
202
236
  * Controls iterator liveness after batches are consumed.
203
237
  * - 'onEmpty' (default): close when no more results
@@ -2649,15 +2683,40 @@ export class DocumentIndex<
2649
2683
  if (remoteOptions !== originalRemote) {
2650
2684
  options = Object.assign({}, options, { remote: remoteOptions });
2651
2685
  }
2686
+ const outOfOrderMode: OutOfOrderMode = options?.outOfOrder?.mode ?? "drop";
2652
2687
 
2653
2688
  let resolve = options?.resolve !== false;
2654
- if (
2655
- !(queryRequestCoerced instanceof types.IterationRequest) &&
2689
+ const wantsReplication =
2656
2690
  options?.remote &&
2657
2691
  typeof options.remote !== "boolean" &&
2658
- (options.remote.replicate || pushUpdates) &&
2692
+ options.remote.replicate;
2693
+
2694
+ if (
2695
+ !(queryRequestCoerced instanceof types.IterationRequest) &&
2696
+ pushUpdates
2697
+ ) {
2698
+ // Push streaming only works on IterationRequest; reject legacy compat and upgrade other callers.
2699
+ if (this.compatibility !== undefined) {
2700
+ throw new Error(
2701
+ "updates.push requires IterationRequest support; not available when compatibility is set",
2702
+ );
2703
+ }
2704
+ queryRequestCoerced = new types.IterationRequest({
2705
+ query: queryRequestCoerced.query,
2706
+ sort: queryRequestCoerced.sort,
2707
+ fetch: queryRequestCoerced.fetch,
2708
+ resolve,
2709
+ pushUpdates,
2710
+ mergeUpdates: mergePolicy?.merge ? true : undefined,
2711
+ });
2712
+ resolve =
2713
+ (queryRequestCoerced as types.IterationRequest).resolve !== false;
2714
+ } else if (
2715
+ !(queryRequestCoerced instanceof types.IterationRequest) &&
2716
+ wantsReplication &&
2659
2717
  options?.resolve !== false
2660
2718
  ) {
2719
+ // Legacy requests can't carry replicate=true; swap to indexed search so replication intent is preserved.
2661
2720
  if (
2662
2721
  (queryRequest instanceof types.SearchRequestIndexed === false &&
2663
2722
  this.compatibility == null) ||
@@ -3496,9 +3555,107 @@ export class DocumentIndex<
3496
3555
  let fetchedFirstForRemote: Set<string> | undefined = undefined;
3497
3556
 
3498
3557
  let updateDeferred: ReturnType<typeof pDefer> | undefined;
3499
- const onLateResults =
3500
- typeof options?.onLateResults === "function"
3501
- ? options.onLateResults
3558
+ const onLateResultsQueue =
3559
+ options?.outOfOrder?.mode === "queue" &&
3560
+ typeof options?.outOfOrder?.handle === "function"
3561
+ ? (options.outOfOrder.handle as (
3562
+ evt: LateResultsEvent<"queue">,
3563
+ helpers: LateResultsHelpers<"queue">,
3564
+ ) => void | Promise<void>)
3565
+ : undefined;
3566
+ const onLateResultsDrop =
3567
+ options?.outOfOrder?.mode === "queue"
3568
+ ? undefined
3569
+ : typeof options?.outOfOrder?.handle === "function"
3570
+ ? (options.outOfOrder.handle as (
3571
+ evt: LateResultsEvent<"drop">,
3572
+ helpers: LateResultsHelpers<"drop">,
3573
+ ) => void | Promise<void>)
3574
+ : undefined;
3575
+ const normalizeLateItems = (
3576
+ items?:
3577
+ | {
3578
+ indexed: WithContext<I>;
3579
+ context: types.Context;
3580
+ from: PublicSignKey;
3581
+ value?: types.ResultTypeFromRequest<R, T, I> | I;
3582
+ }[]
3583
+ | undefined,
3584
+ ): LateResultsItem[] | undefined => {
3585
+ if (!items) return undefined;
3586
+ return items.map((item) => {
3587
+ const ctx = item.context || item.indexed.__context;
3588
+ let value = (item.value ?? item.indexed) as any;
3589
+ if (value && ctx && value.__context == null) {
3590
+ value.__context = ctx;
3591
+ }
3592
+ if (value && item.indexed && value.__indexed == null) {
3593
+ value.__indexed = item.indexed;
3594
+ }
3595
+ return {
3596
+ indexed: item.indexed,
3597
+ context: ctx,
3598
+ from: item.from,
3599
+ value,
3600
+ };
3601
+ });
3602
+ };
3603
+
3604
+ const notifyLateResults =
3605
+ onLateResultsQueue || onLateResultsDrop
3606
+ ? (
3607
+ amount: number,
3608
+ peer?: PublicSignKey,
3609
+ items?: {
3610
+ indexed: WithContext<I>;
3611
+ context: types.Context;
3612
+ from: PublicSignKey;
3613
+ value?: types.ResultTypeFromRequest<R, T, I> | I;
3614
+ }[],
3615
+ ) => {
3616
+ if (amount <= 0) {
3617
+ return;
3618
+ }
3619
+ unsetDone();
3620
+ const payload = items ? normalizeLateItems(items) : undefined;
3621
+ if (outOfOrderMode === "queue" && onLateResultsQueue) {
3622
+ const normalized =
3623
+ payload ??
3624
+ ([] as LateResultsEvent<"queue", LateResultsItem>["items"]);
3625
+ const collector = () =>
3626
+ Promise.resolve(
3627
+ normalized as LateResultsEvent<
3628
+ "queue",
3629
+ LateResultsItem
3630
+ >["items"],
3631
+ );
3632
+ onLateResultsQueue(
3633
+ {
3634
+ amount,
3635
+ peer,
3636
+ items: normalized as LateResultsEvent<
3637
+ "queue",
3638
+ LateResultsItem
3639
+ >["items"],
3640
+ },
3641
+ { collect: collector },
3642
+ );
3643
+ return;
3644
+ }
3645
+ if (onLateResultsDrop) {
3646
+ const collector = () =>
3647
+ Promise.resolve(
3648
+ payload as LateResultsEvent<"drop", LateResultsItem>["items"],
3649
+ );
3650
+ onLateResultsDrop(
3651
+ {
3652
+ amount,
3653
+ peer,
3654
+ },
3655
+ { collect: collector },
3656
+ );
3657
+ }
3658
+ }
3502
3659
  : undefined;
3503
3660
  const runNotify = (reason: UpdateReason) => {
3504
3661
  if (!updateCallbacks?.notify) {
@@ -3594,6 +3751,18 @@ export class DocumentIndex<
3594
3751
  return;
3595
3752
  }
3596
3753
 
3754
+ const collectLateItems =
3755
+ outOfOrderMode !== "drop" && !!notifyLateResults;
3756
+ const lateResults = collectLateItems
3757
+ ? ([] as {
3758
+ indexed: WithContext<I>;
3759
+ context: types.Context;
3760
+ from: PublicSignKey;
3761
+ value?: types.ResultTypeFromRequest<R, T, I> | I;
3762
+ }[])
3763
+ : undefined;
3764
+ let lateCount = 0;
3765
+
3597
3766
  for (const result of results.results) {
3598
3767
  const indexKey = indexerTypes.toId(
3599
3768
  this.indexByResolver(result.value),
@@ -3612,18 +3781,28 @@ export class DocumentIndex<
3612
3781
  indexedPlaceholders?.delete(indexKey);
3613
3782
  continue;
3614
3783
  }
3615
- if (visited.has(indexKey)) {
3616
- continue;
3617
- }
3618
- visited.add(indexKey);
3619
3784
  const indexed = await this.resolveIndexed<R>(
3620
3785
  result,
3621
3786
  results.results as types.ResultTypeFromRequest<R, T, I>[],
3622
3787
  );
3623
- if (isLateResult(indexed)) {
3624
- onLateResults?.({ amount: 1, peer: from });
3788
+ const late = isLateResult(indexed);
3789
+ if (late) {
3790
+ lateCount++;
3791
+ lateResults?.push({
3792
+ indexed,
3793
+ context: result.context,
3794
+ from: from!,
3795
+ value: result.value as types.ResultTypeFromRequest<R, T, I>,
3796
+ });
3797
+ if (outOfOrderMode === "drop") {
3798
+ visited.add(indexKey);
3799
+ continue; // don't buffer late push results
3800
+ }
3801
+ }
3802
+ if (visited.has(indexKey)) {
3625
3803
  continue;
3626
3804
  }
3805
+ visited.add(indexKey);
3627
3806
  buffer.push({
3628
3807
  value: result.value as types.ResultTypeFromRequest<R, T, I>,
3629
3808
  context: result.context,
@@ -3631,18 +3810,28 @@ export class DocumentIndex<
3631
3810
  indexed,
3632
3811
  });
3633
3812
  } else {
3634
- if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
3635
- continue;
3636
- }
3637
- visited.add(indexKey);
3638
3813
  const indexed = coerceWithContext(
3639
3814
  result.indexed || result.value,
3640
3815
  result.context,
3641
3816
  );
3642
- if (isLateResult(indexed)) {
3643
- onLateResults?.({ amount: 1, peer: from });
3817
+ const late = isLateResult(indexed);
3818
+ if (late) {
3819
+ lateCount++;
3820
+ lateResults?.push({
3821
+ indexed,
3822
+ context: result.context,
3823
+ from: from!,
3824
+ value: result.value,
3825
+ });
3826
+ if (outOfOrderMode === "drop") {
3827
+ visited.add(indexKey);
3828
+ continue; // don't buffer late push results
3829
+ }
3830
+ }
3831
+ if (visited.has(indexKey) && !indexedPlaceholders?.has(indexKey)) {
3644
3832
  continue;
3645
3833
  }
3834
+ visited.add(indexKey);
3646
3835
  const placeholder = {
3647
3836
  value: result.value,
3648
3837
  context: result.context,
@@ -3654,6 +3843,14 @@ export class DocumentIndex<
3654
3843
  }
3655
3844
  }
3656
3845
 
3846
+ if (lateCount > 0) {
3847
+ notifyLateResults?.(
3848
+ lateCount,
3849
+ from,
3850
+ collectLateItems ? lateResults : undefined,
3851
+ );
3852
+ }
3853
+
3657
3854
  peerBufferMap.set(peerHash, {
3658
3855
  buffer,
3659
3856
  // Prefetched batches should not claim remote pending counts;
@@ -3859,10 +4056,6 @@ export class DocumentIndex<
3859
4056
  continue;
3860
4057
  }
3861
4058
  }
3862
- if (isLateResult(indexedCandidate)) {
3863
- onLateResults?.({ amount: 1 });
3864
- continue;
3865
- }
3866
4059
  const id = indexerTypes.toId(
3867
4060
  this.indexByResolver(indexedCandidate),
3868
4061
  ).primitive;
@@ -3878,6 +4071,20 @@ export class DocumentIndex<
3878
4071
  continue;
3879
4072
  }
3880
4073
  if (visited.has(id)) continue; // already presented
4074
+ const wasLate = isLateResult(indexedCandidate);
4075
+ if (wasLate) {
4076
+ notifyLateResults?.(1, this.node.identity.publicKey, [
4077
+ {
4078
+ indexed: indexedCandidate,
4079
+ context: added.__context,
4080
+ from: this.node.identity.publicKey,
4081
+ value: resolve ? (added as any) : indexedCandidate,
4082
+ },
4083
+ ]);
4084
+ if (outOfOrderMode === "drop") {
4085
+ continue;
4086
+ }
4087
+ }
3881
4088
  visited.add(id);
3882
4089
  const valueForBuffer = resolve
3883
4090
  ? (added as any)
@@ -3954,7 +4161,7 @@ export class DocumentIndex<
3954
4161
  fetchedFirstForRemote,
3955
4162
  });
3956
4163
  await fetchPromise;
3957
- if (onLateResults) {
4164
+ if (onLateResultsQueue || onLateResultsDrop) {
3958
4165
  const pending = peerBufferMap.get(hash)?.buffer;
3959
4166
  if (pending && pending.length > 0) {
3960
4167
  if (lastValueInOrder) {
@@ -3971,10 +4178,21 @@ export class DocumentIndex<
3971
4178
  (x) => x === lastValueInOrder,
3972
4179
  );
3973
4180
  if (lateResults > 0) {
3974
- onLateResults({ amount: lateResults });
4181
+ const lateItems =
4182
+ outOfOrderMode === "queue"
4183
+ ? results.slice(
4184
+ 0,
4185
+ Math.min(lateResults, results.length),
4186
+ )
4187
+ : undefined;
4188
+ notifyLateResults?.(lateResults, pk, lateItems);
3975
4189
  }
3976
4190
  } else {
3977
- onLateResults({ amount: pending.length });
4191
+ notifyLateResults?.(
4192
+ pending.length,
4193
+ pk,
4194
+ outOfOrderMode === "queue" ? pending : undefined,
4195
+ );
3978
4196
  }
3979
4197
  }
3980
4198
  }
@@ -4032,11 +4250,11 @@ export class DocumentIndex<
4032
4250
  warn("Failed to refresh iterator pending state", error);
4033
4251
  }
4034
4252
 
4035
- let pendingCount = 0;
4253
+ let total = 0;
4036
4254
  for (const buffer of peerBufferMap.values()) {
4037
- pendingCount += buffer.kept + buffer.buffer.length;
4255
+ total += buffer.kept + buffer.buffer.length;
4038
4256
  }
4039
- return pendingCount;
4257
+ return total;
4040
4258
  },
4041
4259
  all: async () => {
4042
4260
  drain = true;