@peerbit/document 9.12.17 → 9.13.1-0fddff8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/program.d.ts +2 -2
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +3 -3
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts +1 -0
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +3 -0
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts +94 -21
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +346 -89
- package/dist/src/search.js.map +1 -1
- package/package.json +75 -75
- package/src/program.ts +6 -5
- package/src/resumable-iterator.ts +4 -0
- package/src/search.ts +658 -216
package/src/search.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type AbstractType, field, serialize, variant } from "@dao-xyz/borsh";
|
|
2
2
|
import type { PeerId, TypedEventTarget } from "@libp2p/interface";
|
|
3
|
+
import type { Multiaddr } from "@multiformats/multiaddr";
|
|
3
4
|
import { Cache } from "@peerbit/cache";
|
|
4
5
|
import {
|
|
5
6
|
type MaybePromise,
|
|
6
7
|
PublicSignKey,
|
|
7
|
-
getPublicKeyFromPeerId,
|
|
8
8
|
sha256Base64Sync,
|
|
9
9
|
} from "@peerbit/crypto";
|
|
10
10
|
import * as types from "@peerbit/document-interface";
|
|
@@ -26,8 +26,12 @@ import {
|
|
|
26
26
|
type ReplicationDomain,
|
|
27
27
|
SharedLog,
|
|
28
28
|
} from "@peerbit/shared-log";
|
|
29
|
-
import {
|
|
30
|
-
|
|
29
|
+
import {
|
|
30
|
+
DataMessage,
|
|
31
|
+
type PeerRefs,
|
|
32
|
+
SilentDelivery,
|
|
33
|
+
} from "@peerbit/stream-interface";
|
|
34
|
+
import { AbortError, TimeoutError, waitFor } from "@peerbit/time";
|
|
31
35
|
import pDefer, { type DeferredPromise } from "p-defer";
|
|
32
36
|
import { concat, fromString } from "uint8arrays";
|
|
33
37
|
import { copySerialization } from "./borsh.js";
|
|
@@ -40,6 +44,8 @@ import { Prefetch } from "./prefetch.js";
|
|
|
40
44
|
import type { ExtractArgs } from "./program.js";
|
|
41
45
|
import { ResumableIterators } from "./resumable-iterator.js";
|
|
42
46
|
|
|
47
|
+
const WARNING_WHEN_ITERATING_FOR_MORE_THAN = 1e5;
|
|
48
|
+
|
|
43
49
|
const logger = loggerFn({ module: "document-index" });
|
|
44
50
|
|
|
45
51
|
type BufferedResult<T, I extends Record<string, any>> = {
|
|
@@ -49,6 +55,102 @@ type BufferedResult<T, I extends Record<string, any>> = {
|
|
|
49
55
|
from: PublicSignKey;
|
|
50
56
|
};
|
|
51
57
|
|
|
58
|
+
export type UpdateMergeStrategy<
|
|
59
|
+
T,
|
|
60
|
+
I,
|
|
61
|
+
Resolve extends boolean | undefined,
|
|
62
|
+
RT = ValueTypeFromRequest<Resolve, T, I>,
|
|
63
|
+
> =
|
|
64
|
+
| boolean
|
|
65
|
+
| {
|
|
66
|
+
filter?: (
|
|
67
|
+
evt: DocumentsChange<T, I>,
|
|
68
|
+
) => MaybePromise<DocumentsChange<T, I> | void>;
|
|
69
|
+
};
|
|
70
|
+
export type UpdateCallbacks<
|
|
71
|
+
T,
|
|
72
|
+
I,
|
|
73
|
+
Resolve extends boolean | undefined,
|
|
74
|
+
RT = ValueTypeFromRequest<Resolve, T, I>,
|
|
75
|
+
> = {
|
|
76
|
+
/**
|
|
77
|
+
* Fires on raw DB change events (added/removed/updated).
|
|
78
|
+
* Use if you want low-level inspection of change streams.
|
|
79
|
+
*/
|
|
80
|
+
onChange?: (change: DocumentsChange<T, I>) => void | Promise<void>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fires whenever the iterator yields a batch to the consumer.
|
|
84
|
+
* Good for external sync (e.g. React state).
|
|
85
|
+
*/
|
|
86
|
+
onResults?: (
|
|
87
|
+
batch: RT[],
|
|
88
|
+
meta: { reason: "initial" | "next" | "join" | "change" },
|
|
89
|
+
) => void | Promise<void>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Unified update options for iterate()/search()/get() and hooks.
|
|
94
|
+
* If you pass `true`, defaults to `{ merge: "sorted" }`.
|
|
95
|
+
*/
|
|
96
|
+
export type UpdateOptions<T, I, Resolve extends boolean | undefined> =
|
|
97
|
+
| boolean
|
|
98
|
+
| ({
|
|
99
|
+
/** Live update behavior. Only sorted merging is supported; optional filter can mutate/ignore events. */
|
|
100
|
+
merge?: UpdateMergeStrategy<T, I, Resolve>;
|
|
101
|
+
} & UpdateCallbacks<T, I, Resolve>);
|
|
102
|
+
|
|
103
|
+
export type JoiningTargets = {
|
|
104
|
+
/** Specific peers you care about */
|
|
105
|
+
peers?: Array<PublicSignKey | PeerId | string>; // string = hash or peer id
|
|
106
|
+
|
|
107
|
+
/** Multiaddrs you care about */
|
|
108
|
+
multiaddrs?: (string | Multiaddr)[];
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* From the previous cover set (what you "knew" about earlier).
|
|
112
|
+
* - "any": wait until at least 1 of the known peers is ready
|
|
113
|
+
* - "all": wait until all known peers are ready
|
|
114
|
+
* - number: wait until N known peers are ready
|
|
115
|
+
*/
|
|
116
|
+
known?: "any" | "all" | number;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type JoiningTimeoutPolicy = "proceed" | "error";
|
|
120
|
+
|
|
121
|
+
export type JoiningOnMissedResults = (evt: {
|
|
122
|
+
/** How many items should have preceded the current frontier. */
|
|
123
|
+
amount: number;
|
|
124
|
+
|
|
125
|
+
/** The peer whose arrival triggered the gap calculation. */
|
|
126
|
+
peer: PublicSignKey;
|
|
127
|
+
}) => void | Promise<void>;
|
|
128
|
+
|
|
129
|
+
export type LateResultsEvent = {
|
|
130
|
+
/** Count of items that should have appeared earlier than the current frontier */
|
|
131
|
+
amount: number;
|
|
132
|
+
|
|
133
|
+
/** If attributable, the peer that produced the late items */
|
|
134
|
+
peer?: PublicSignKey;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type WaitBehavior =
|
|
138
|
+
| "block" // hold the *first* fetch until readiness condition is met or timeout
|
|
139
|
+
| "keep-open"; // return immediately; iterator stays open listening for late peers
|
|
140
|
+
|
|
141
|
+
export type WaitPolicy = {
|
|
142
|
+
timeout: number; // max time to wait
|
|
143
|
+
until?: "any"; // readiness condition, TODO more options like "cover" (to wait for this.log.watiForReplicators)
|
|
144
|
+
onTimeout?: "proceed" | "error"; // proceed = continue with whoever's ready
|
|
145
|
+
behavior?: WaitBehavior; // default: "keep-open"
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export type ReachScope = {
|
|
149
|
+
/** who to consider for readiness */
|
|
150
|
+
eager?: boolean; // not yet matured
|
|
151
|
+
discover?: PublicSignKey[]; // wait for these peers to be ready, assumes they are already in the dialqueue or connected, but not actively subscribing yet
|
|
152
|
+
};
|
|
153
|
+
|
|
52
154
|
export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
|
|
53
155
|
replicate?: boolean;
|
|
54
156
|
minAge?: number;
|
|
@@ -61,12 +163,15 @@ export type RemoteQueryOptions<Q, R, D> = RPCRequestAllOptions<Q, R> & {
|
|
|
61
163
|
| {
|
|
62
164
|
range: CoverRange<number | bigint>;
|
|
63
165
|
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
166
|
+
/** WHO can answer? How do we grow the candidate set? */
|
|
167
|
+
reach?: ReachScope;
|
|
168
|
+
/** WHEN are we allowed to proceed? Quorum semantics over a chosen group. */
|
|
169
|
+
wait?: WaitPolicy;
|
|
170
|
+
|
|
171
|
+
onLateResults?: (evt: LateResultsEvent) => void | Promise<void>;
|
|
68
172
|
};
|
|
69
|
-
|
|
173
|
+
|
|
174
|
+
export type QueryOptions<T, I, D, Resolve extends boolean | undefined> = {
|
|
70
175
|
remote?:
|
|
71
176
|
| boolean
|
|
72
177
|
| RemoteQueryOptions<
|
|
@@ -77,9 +182,16 @@ export type QueryOptions<R, D, Resolve extends boolean | undefined> = {
|
|
|
77
182
|
local?: boolean;
|
|
78
183
|
resolve?: Resolve;
|
|
79
184
|
signal?: AbortSignal;
|
|
185
|
+
updates?: UpdateOptions<T, I, Resolve>;
|
|
186
|
+
/**
|
|
187
|
+
* Controls iterator liveness after batches are consumed.
|
|
188
|
+
* - 'onEmpty' (default): close when no more results
|
|
189
|
+
* - 'manual': keep open until iterator.close() or program close; good for live updates
|
|
190
|
+
*/
|
|
191
|
+
closePolicy?: "onEmpty" | "manual";
|
|
80
192
|
};
|
|
81
193
|
|
|
82
|
-
export type GetOptions<
|
|
194
|
+
export type GetOptions<T, I, D, Resolve extends boolean | undefined> = {
|
|
83
195
|
remote?:
|
|
84
196
|
| boolean
|
|
85
197
|
| RemoteQueryOptions<
|
|
@@ -94,10 +206,11 @@ export type GetOptions<R, D, Resolve extends boolean | undefined> = {
|
|
|
94
206
|
};
|
|
95
207
|
|
|
96
208
|
export type SearchOptions<
|
|
97
|
-
|
|
209
|
+
T,
|
|
210
|
+
I,
|
|
98
211
|
D,
|
|
99
212
|
Resolve extends boolean | undefined,
|
|
100
|
-
> = QueryOptions<
|
|
213
|
+
> = QueryOptions<T, I, D, Resolve>;
|
|
101
214
|
|
|
102
215
|
type Transformer<T, I> = (obj: T, context: types.Context) => MaybePromise<I>;
|
|
103
216
|
|
|
@@ -108,13 +221,15 @@ export type ResultsIterator<T> = {
|
|
|
108
221
|
all: () => Promise<T[]>;
|
|
109
222
|
pending: () => number | undefined;
|
|
110
223
|
first: () => Promise<T | undefined>;
|
|
224
|
+
[Symbol.asyncIterator]: () => AsyncIterator<T>;
|
|
111
225
|
};
|
|
112
226
|
|
|
113
227
|
type QueryDetailedOptions<
|
|
114
228
|
T,
|
|
229
|
+
I,
|
|
115
230
|
D,
|
|
116
231
|
Resolve extends boolean | undefined,
|
|
117
|
-
> = QueryOptions<T, D, Resolve> & {
|
|
232
|
+
> = QueryOptions<T, I, D, Resolve> & {
|
|
118
233
|
onResponse?: (
|
|
119
234
|
response: types.AbstractSearchResult,
|
|
120
235
|
from: PublicSignKey,
|
|
@@ -122,6 +237,7 @@ type QueryDetailedOptions<
|
|
|
122
237
|
remote?: {
|
|
123
238
|
from?: string[]; // if specified, only query these peers
|
|
124
239
|
};
|
|
240
|
+
fetchFirstForRemote?: Set<string>;
|
|
125
241
|
};
|
|
126
242
|
|
|
127
243
|
type QueryLike = {
|
|
@@ -130,7 +246,7 @@ type QueryLike = {
|
|
|
130
246
|
};
|
|
131
247
|
|
|
132
248
|
type ExtractResolveFromOptions<O> =
|
|
133
|
-
O extends QueryOptions<any, any, infer X>
|
|
249
|
+
O extends QueryOptions<any, any, any, infer X>
|
|
134
250
|
? X extends boolean // if X is a boolean (true or false)
|
|
135
251
|
? X
|
|
136
252
|
: true // else default to true
|
|
@@ -138,7 +254,7 @@ type ExtractResolveFromOptions<O> =
|
|
|
138
254
|
|
|
139
255
|
const coerceQuery = <Resolve extends boolean | undefined>(
|
|
140
256
|
query: types.SearchRequest | types.SearchRequestIndexed | QueryLike,
|
|
141
|
-
options?: QueryOptions<any, any, Resolve>,
|
|
257
|
+
options?: QueryOptions<any, any, any, Resolve>,
|
|
142
258
|
) => {
|
|
143
259
|
let replicate =
|
|
144
260
|
typeof options?.remote !== "boolean" ? options?.remote?.replicate : false;
|
|
@@ -180,7 +296,7 @@ const introduceEntries = async <
|
|
|
180
296
|
documentType: AbstractType<T>,
|
|
181
297
|
indexedType: AbstractType<I>,
|
|
182
298
|
sync: (request: R, response: types.Results<any>) => Promise<void>,
|
|
183
|
-
options?: QueryDetailedOptions<T, D, any>,
|
|
299
|
+
options?: QueryDetailedOptions<T, I, D, any>,
|
|
184
300
|
): Promise<
|
|
185
301
|
RPCResponse<types.Results<types.ResultTypeFromRequest<R, T, I>>>[]
|
|
186
302
|
> => {
|
|
@@ -815,18 +931,18 @@ export class DocumentIndex<
|
|
|
815
931
|
return dropped;
|
|
816
932
|
}
|
|
817
933
|
|
|
818
|
-
public async get<Options extends GetOptions<T, D, true | undefined>>(
|
|
934
|
+
public async get<Options extends GetOptions<T, I, D, true | undefined>>(
|
|
819
935
|
key: indexerTypes.Ideable | indexerTypes.IdKey,
|
|
820
936
|
options?: Options,
|
|
821
937
|
): Promise<WithIndexedContext<T, I>>;
|
|
822
938
|
|
|
823
|
-
public async get<Options extends GetOptions<T, D, false>>(
|
|
939
|
+
public async get<Options extends GetOptions<T, I, D, false>>(
|
|
824
940
|
key: indexerTypes.Ideable | indexerTypes.IdKey,
|
|
825
941
|
options?: Options,
|
|
826
942
|
): Promise<WithContext<I>>;
|
|
827
943
|
|
|
828
944
|
public async get<
|
|
829
|
-
Options extends GetOptions<T, D, Resolve>,
|
|
945
|
+
Options extends GetOptions<T, I, D, Resolve>,
|
|
830
946
|
Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
|
|
831
947
|
>(key: indexerTypes.Ideable | indexerTypes.IdKey, options?: Options) {
|
|
832
948
|
let deferred:
|
|
@@ -879,16 +995,15 @@ export class DocumentIndex<
|
|
|
879
995
|
? { ...options.remote }
|
|
880
996
|
: {};
|
|
881
997
|
if (baseRemote) {
|
|
882
|
-
const
|
|
998
|
+
const waitPolicy = baseRemote.wait;
|
|
883
999
|
if (
|
|
884
|
-
!
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
(prevJoining.waitFor || 0) < options.waitFor)
|
|
1000
|
+
!waitPolicy ||
|
|
1001
|
+
(typeof waitPolicy === "object" &&
|
|
1002
|
+
(waitPolicy.timeout || 0) < options.waitFor)
|
|
888
1003
|
) {
|
|
889
|
-
baseRemote.
|
|
890
|
-
...(typeof
|
|
891
|
-
|
|
1004
|
+
baseRemote.wait = {
|
|
1005
|
+
...(typeof waitPolicy === "object" ? waitPolicy : {}),
|
|
1006
|
+
timeout: options.waitFor,
|
|
892
1007
|
};
|
|
893
1008
|
}
|
|
894
1009
|
}
|
|
@@ -897,10 +1012,11 @@ export class DocumentIndex<
|
|
|
897
1012
|
let joinListener: (() => void) | undefined;
|
|
898
1013
|
if (baseRemote) {
|
|
899
1014
|
joinListener = this.attachJoinListener({
|
|
1015
|
+
eager: baseRemote.reach?.eager,
|
|
900
1016
|
onPeer: async (pk) => {
|
|
901
1017
|
if (cleanedUp) return;
|
|
902
1018
|
const hash = pk.hashcode();
|
|
903
|
-
const requeryOptions: QueryOptions<T, D, Resolve> = {
|
|
1019
|
+
const requeryOptions: QueryOptions<T, I, D, Resolve> = {
|
|
904
1020
|
...(options as any),
|
|
905
1021
|
remote: {
|
|
906
1022
|
...(baseRemote || {}),
|
|
@@ -1005,14 +1121,14 @@ export class DocumentIndex<
|
|
|
1005
1121
|
}
|
|
1006
1122
|
|
|
1007
1123
|
public async getDetailed<
|
|
1008
|
-
Options extends QueryOptions<T, D, Resolve>,
|
|
1124
|
+
Options extends QueryOptions<T, I, D, Resolve>,
|
|
1009
1125
|
Resolve extends boolean | undefined = ExtractResolveFromOptions<Options>,
|
|
1010
1126
|
RT extends types.Result = Resolve extends true
|
|
1011
1127
|
? types.ResultValue<WithIndexedContext<T, I>>
|
|
1012
1128
|
: types.ResultIndexedValue<WithContext<I>>,
|
|
1013
1129
|
>(
|
|
1014
1130
|
key: indexerTypes.IdKey | indexerTypes.IdPrimitive,
|
|
1015
|
-
options?: QueryOptions<T, D, Resolve>,
|
|
1131
|
+
options?: QueryOptions<T, I, D, Resolve>,
|
|
1016
1132
|
): Promise<types.Results<RT>[] | undefined> {
|
|
1017
1133
|
let coercedOptions = options;
|
|
1018
1134
|
if (options?.remote && typeof options.remote !== "boolean") {
|
|
@@ -1049,7 +1165,7 @@ export class DocumentIndex<
|
|
|
1049
1165
|
new indexerTypes.ByteMatchQuery({ key: this.indexBy, value: key }),
|
|
1050
1166
|
],
|
|
1051
1167
|
}),
|
|
1052
|
-
coercedOptions,
|
|
1168
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1053
1169
|
);
|
|
1054
1170
|
} else {
|
|
1055
1171
|
const indexableKey = indexerTypes.toIdeable(key);
|
|
@@ -1068,7 +1184,7 @@ export class DocumentIndex<
|
|
|
1068
1184
|
}),
|
|
1069
1185
|
],
|
|
1070
1186
|
}),
|
|
1071
|
-
coercedOptions,
|
|
1187
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1072
1188
|
);
|
|
1073
1189
|
} else if (typeof indexableKey === "string") {
|
|
1074
1190
|
results = await this.queryCommence(
|
|
@@ -1080,7 +1196,7 @@ export class DocumentIndex<
|
|
|
1080
1196
|
}),
|
|
1081
1197
|
],
|
|
1082
1198
|
}),
|
|
1083
|
-
coercedOptions,
|
|
1199
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1084
1200
|
);
|
|
1085
1201
|
} else if (indexableKey instanceof Uint8Array) {
|
|
1086
1202
|
results = await this.queryCommence(
|
|
@@ -1092,7 +1208,7 @@ export class DocumentIndex<
|
|
|
1092
1208
|
}),
|
|
1093
1209
|
],
|
|
1094
1210
|
}),
|
|
1095
|
-
coercedOptions,
|
|
1211
|
+
coercedOptions as QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1096
1212
|
);
|
|
1097
1213
|
}
|
|
1098
1214
|
}
|
|
@@ -1364,10 +1480,131 @@ export class DocumentIndex<
|
|
|
1364
1480
|
}
|
|
1365
1481
|
}
|
|
1366
1482
|
|
|
1483
|
+
private async waitForCoverReady(params: {
|
|
1484
|
+
domain?: { args?: ExtractArgs<D> } | { range: CoverRange<number | bigint> };
|
|
1485
|
+
eager?: boolean;
|
|
1486
|
+
settle: "any";
|
|
1487
|
+
timeout: number;
|
|
1488
|
+
signal?: AbortSignal;
|
|
1489
|
+
onTimeout?: "proceed" | "error";
|
|
1490
|
+
}) {
|
|
1491
|
+
const {
|
|
1492
|
+
domain,
|
|
1493
|
+
eager,
|
|
1494
|
+
settle,
|
|
1495
|
+
timeout,
|
|
1496
|
+
signal,
|
|
1497
|
+
onTimeout = "proceed",
|
|
1498
|
+
} = params;
|
|
1499
|
+
|
|
1500
|
+
if (settle !== "any") {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const properties =
|
|
1505
|
+
domain && "range" in domain
|
|
1506
|
+
? { range: domain.range }
|
|
1507
|
+
: { args: domain?.args };
|
|
1508
|
+
const selfHash = this.node.identity.publicKey.hashcode();
|
|
1509
|
+
|
|
1510
|
+
const ready = async () => {
|
|
1511
|
+
const cover = await this._log.getCover(properties, { eager });
|
|
1512
|
+
return cover.some((hash) => hash !== selfHash);
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
if (await ready()) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const deferred = pDefer<void>();
|
|
1520
|
+
let settled = false;
|
|
1521
|
+
let cleaned = false;
|
|
1522
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1523
|
+
let checking = false;
|
|
1524
|
+
|
|
1525
|
+
const cleanup = () => {
|
|
1526
|
+
if (cleaned) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
cleaned = true;
|
|
1530
|
+
this._log.events.removeEventListener("replicator:join", onEvent);
|
|
1531
|
+
this._log.events.removeEventListener("replication:change", onEvent);
|
|
1532
|
+
this._log.events.removeEventListener("replicator:mature", onEvent);
|
|
1533
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1534
|
+
if (timer != null) {
|
|
1535
|
+
clearTimeout(timer);
|
|
1536
|
+
timer = undefined;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
const resolve = () => {
|
|
1541
|
+
if (settled) {
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
settled = true;
|
|
1545
|
+
cleanup();
|
|
1546
|
+
deferred.resolve();
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
const reject = (error: Error) => {
|
|
1550
|
+
if (settled) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
settled = true;
|
|
1554
|
+
cleanup();
|
|
1555
|
+
deferred.reject(error);
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
const onAbort = () => reject(new AbortError());
|
|
1559
|
+
|
|
1560
|
+
const onEvent = async () => {
|
|
1561
|
+
if (checking) {
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
checking = true;
|
|
1565
|
+
try {
|
|
1566
|
+
if (await ready()) {
|
|
1567
|
+
resolve();
|
|
1568
|
+
}
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
1571
|
+
} finally {
|
|
1572
|
+
checking = false;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
if (signal) {
|
|
1577
|
+
signal.addEventListener("abort", onAbort);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
if (timeout > 0) {
|
|
1581
|
+
timer = setTimeout(() => {
|
|
1582
|
+
if (onTimeout === "error") {
|
|
1583
|
+
reject(
|
|
1584
|
+
new TimeoutError("Timeout waiting for participating replicator"),
|
|
1585
|
+
);
|
|
1586
|
+
} else {
|
|
1587
|
+
resolve();
|
|
1588
|
+
}
|
|
1589
|
+
}, timeout);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
this._log.events.addEventListener("replicator:join", onEvent);
|
|
1593
|
+
this._log.events.addEventListener("replication:change", onEvent);
|
|
1594
|
+
this._log.events.addEventListener("replicator:mature", onEvent);
|
|
1595
|
+
|
|
1596
|
+
try {
|
|
1597
|
+
await deferred.promise;
|
|
1598
|
+
} finally {
|
|
1599
|
+
cleanup();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1367
1603
|
// Utility: attach a join listener that waits until a peer is a replicator,
|
|
1368
1604
|
// then invokes the provided callback. Returns a detach function.
|
|
1369
1605
|
private attachJoinListener(params: {
|
|
1370
1606
|
signal?: AbortSignal;
|
|
1607
|
+
eager?: boolean;
|
|
1371
1608
|
onPeer: (pk: PublicSignKey) => Promise<void> | void;
|
|
1372
1609
|
}): () => void {
|
|
1373
1610
|
const active = new Set<string>();
|
|
@@ -1382,6 +1619,7 @@ export class DocumentIndex<
|
|
|
1382
1619
|
await this._log
|
|
1383
1620
|
.waitForReplicator(pk, {
|
|
1384
1621
|
signal: params.signal,
|
|
1622
|
+
eager: params.eager,
|
|
1385
1623
|
})
|
|
1386
1624
|
.catch(() => undefined);
|
|
1387
1625
|
if (params.signal?.aborted) return;
|
|
@@ -1419,7 +1657,8 @@ export class DocumentIndex<
|
|
|
1419
1657
|
RT extends types.Result = types.ResultTypeFromRequest<R, T, I>,
|
|
1420
1658
|
>(
|
|
1421
1659
|
queryRequest: R,
|
|
1422
|
-
options?: QueryDetailedOptions<T, D, boolean | undefined>,
|
|
1660
|
+
options?: QueryDetailedOptions<T, I, D, boolean | undefined>,
|
|
1661
|
+
fetchFirstForRemote?: Set<string>,
|
|
1423
1662
|
): Promise<types.Results<RT>[]> {
|
|
1424
1663
|
const local = typeof options?.local === "boolean" ? options?.local : true;
|
|
1425
1664
|
let remote:
|
|
@@ -1477,8 +1716,8 @@ export class DocumentIndex<
|
|
|
1477
1716
|
? options?.remote?.from
|
|
1478
1717
|
: await this._log.getCover(remote.domain ?? { args: undefined }, {
|
|
1479
1718
|
roleAge: remote.minAge,
|
|
1480
|
-
eager: remote.eager,
|
|
1481
|
-
reachableOnly: !!remote.
|
|
1719
|
+
eager: remote.reach?.eager,
|
|
1720
|
+
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
|
|
1482
1721
|
});
|
|
1483
1722
|
|
|
1484
1723
|
if (replicatorGroups) {
|
|
@@ -1508,6 +1747,13 @@ export class DocumentIndex<
|
|
|
1508
1747
|
if (hash === this.node.identity.publicKey.hashcode()) {
|
|
1509
1748
|
return false;
|
|
1510
1749
|
}
|
|
1750
|
+
|
|
1751
|
+
if (fetchFirstForRemote?.has(hash)) {
|
|
1752
|
+
// we already fetched this one for remote, no need to do it again
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
fetchFirstForRemote?.add(hash);
|
|
1756
|
+
|
|
1511
1757
|
const resultAlready = this._prefetch?.accumulator.consume(
|
|
1512
1758
|
queryRequest,
|
|
1513
1759
|
hash,
|
|
@@ -1637,11 +1883,11 @@ export class DocumentIndex<
|
|
|
1637
1883
|
|
|
1638
1884
|
public search(
|
|
1639
1885
|
queryRequest: QueryLike,
|
|
1640
|
-
options?: SearchOptions<T, D, true>,
|
|
1886
|
+
options?: SearchOptions<T, I, D, true>,
|
|
1641
1887
|
): Promise<ValueTypeFromRequest<true, T, I>[]>;
|
|
1642
1888
|
public search(
|
|
1643
1889
|
queryRequest: QueryLike,
|
|
1644
|
-
options?: SearchOptions<T, D, false>,
|
|
1890
|
+
options?: SearchOptions<T, I, D, false>,
|
|
1645
1891
|
): Promise<ValueTypeFromRequest<false, T, I>[]>;
|
|
1646
1892
|
|
|
1647
1893
|
/**
|
|
@@ -1652,11 +1898,11 @@ export class DocumentIndex<
|
|
|
1652
1898
|
*/
|
|
1653
1899
|
public async search<
|
|
1654
1900
|
R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
|
|
1655
|
-
O extends SearchOptions<T, D, Resolve>,
|
|
1656
|
-
Resolve extends boolean
|
|
1901
|
+
O extends SearchOptions<T, I, D, Resolve>,
|
|
1902
|
+
Resolve extends boolean = ExtractResolveFromOptions<O>,
|
|
1657
1903
|
>(
|
|
1658
1904
|
queryRequest: R,
|
|
1659
|
-
options?:
|
|
1905
|
+
options?: O,
|
|
1660
1906
|
): Promise<ValueTypeFromRequest<Resolve, T, I>[]> {
|
|
1661
1907
|
// Set fetch to search size, or max value (default to max u32 (4294967295))
|
|
1662
1908
|
const coercedRequest: types.SearchRequest | types.SearchRequestIndexed =
|
|
@@ -1664,7 +1910,7 @@ export class DocumentIndex<
|
|
|
1664
1910
|
coercedRequest.fetch = coercedRequest.fetch ?? 0xffffffff;
|
|
1665
1911
|
|
|
1666
1912
|
// So that the iterator is pre-fetching the right amount of entries
|
|
1667
|
-
const iterator = this.iterate(coercedRequest, options);
|
|
1913
|
+
const iterator = this.iterate<Resolve>(coercedRequest, options);
|
|
1668
1914
|
|
|
1669
1915
|
// So that this call will not do any remote requests
|
|
1670
1916
|
const allResults: ValueTypeFromRequest<Resolve, T, I>[] = [];
|
|
@@ -1729,11 +1975,11 @@ export class DocumentIndex<
|
|
|
1729
1975
|
|
|
1730
1976
|
public iterate(
|
|
1731
1977
|
query?: QueryLike,
|
|
1732
|
-
options?: QueryOptions<T, D, undefined>,
|
|
1978
|
+
options?: QueryOptions<T, I, D, undefined>,
|
|
1733
1979
|
): ResultsIterator<ValueTypeFromRequest<true, T, I>>;
|
|
1734
1980
|
public iterate<Resolve extends boolean>(
|
|
1735
1981
|
query?: QueryLike,
|
|
1736
|
-
options?: QueryOptions<T, D, Resolve>,
|
|
1982
|
+
options?: QueryOptions<T, I, D, Resolve>,
|
|
1737
1983
|
): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>>;
|
|
1738
1984
|
|
|
1739
1985
|
/**
|
|
@@ -1744,11 +1990,11 @@ export class DocumentIndex<
|
|
|
1744
1990
|
*/
|
|
1745
1991
|
public iterate<
|
|
1746
1992
|
R extends types.SearchRequest | types.SearchRequestIndexed | QueryLike,
|
|
1747
|
-
O extends SearchOptions<T, D, Resolve>,
|
|
1993
|
+
O extends SearchOptions<T, I, D, Resolve>,
|
|
1748
1994
|
Resolve extends boolean | undefined = ExtractResolveFromOptions<O>,
|
|
1749
1995
|
>(
|
|
1750
1996
|
queryRequest?: R,
|
|
1751
|
-
options?: QueryOptions<T, D, Resolve>,
|
|
1997
|
+
options?: QueryOptions<T, I, D, Resolve>,
|
|
1752
1998
|
): ResultsIterator<ValueTypeFromRequest<Resolve, T, I>> {
|
|
1753
1999
|
if (
|
|
1754
2000
|
queryRequest instanceof types.SearchRequest &&
|
|
@@ -1803,6 +2049,7 @@ export class DocumentIndex<
|
|
|
1803
2049
|
const visited = new Set<string | number | bigint>();
|
|
1804
2050
|
|
|
1805
2051
|
let done = false;
|
|
2052
|
+
let drain = false; // if true, close on empty once (overrides manual)
|
|
1806
2053
|
let first = false;
|
|
1807
2054
|
|
|
1808
2055
|
// TODO handle join/leave while iterating
|
|
@@ -1827,147 +2074,186 @@ export class DocumentIndex<
|
|
|
1827
2074
|
return [...peerBufferMap.values()].map((x) => x.buffer).flat();
|
|
1828
2075
|
};
|
|
1829
2076
|
|
|
1830
|
-
let maybeSetDone
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
maybeSetDone = () => {
|
|
1842
|
-
if (t0 + waitForTime < +new Date()) {
|
|
1843
|
-
cleanup();
|
|
1844
|
-
done = true;
|
|
1845
|
-
} else {
|
|
1846
|
-
setDoneIfTimeout = true;
|
|
1847
|
-
}
|
|
1848
|
-
};
|
|
1849
|
-
unsetDone = () => {
|
|
1850
|
-
setDoneIfTimeout = false;
|
|
1851
|
-
done = false;
|
|
1852
|
-
};
|
|
1853
|
-
let timeout = setTimeout(() => {
|
|
1854
|
-
if (setDoneIfTimeout) {
|
|
1855
|
-
cleanup();
|
|
1856
|
-
done = true;
|
|
1857
|
-
}
|
|
1858
|
-
}, waitForTime);
|
|
2077
|
+
let maybeSetDone = () => {
|
|
2078
|
+
cleanup();
|
|
2079
|
+
done = true;
|
|
2080
|
+
};
|
|
2081
|
+
let unsetDone = () => {
|
|
2082
|
+
cleanup();
|
|
2083
|
+
done = false;
|
|
2084
|
+
};
|
|
2085
|
+
let cleanup = () => {
|
|
2086
|
+
this.clearResultsQueue(queryRequestCoerced);
|
|
2087
|
+
};
|
|
1859
2088
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2089
|
+
let warmupPromise: Promise<any> | undefined = undefined;
|
|
2090
|
+
|
|
2091
|
+
if (typeof options?.remote === "object") {
|
|
2092
|
+
let waitForTime: number | undefined = undefined;
|
|
2093
|
+
if (options.remote.wait) {
|
|
2094
|
+
let t0 = +new Date();
|
|
2095
|
+
|
|
2096
|
+
waitForTime =
|
|
2097
|
+
typeof options.remote.wait === "boolean"
|
|
2098
|
+
? DEFAULT_TIMEOUT
|
|
2099
|
+
: (options.remote.wait.timeout ?? DEFAULT_TIMEOUT);
|
|
2100
|
+
let setDoneIfTimeout = false;
|
|
2101
|
+
maybeSetDone = () => {
|
|
2102
|
+
if (t0 + waitForTime! < +new Date()) {
|
|
2103
|
+
cleanup();
|
|
2104
|
+
done = true;
|
|
2105
|
+
} else {
|
|
2106
|
+
setDoneIfTimeout = true;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
unsetDone = () => {
|
|
2110
|
+
setDoneIfTimeout = false;
|
|
2111
|
+
done = false;
|
|
2112
|
+
};
|
|
2113
|
+
let timeout = setTimeout(() => {
|
|
2114
|
+
if (setDoneIfTimeout) {
|
|
2115
|
+
cleanup();
|
|
2116
|
+
done = true;
|
|
2117
|
+
}
|
|
2118
|
+
}, waitForTime);
|
|
2119
|
+
|
|
2120
|
+
cleanup = () => {
|
|
2121
|
+
this.clearResultsQueue(queryRequestCoerced);
|
|
2122
|
+
clearTimeout(timeout);
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
if (options.remote.reach?.discover) {
|
|
2127
|
+
warmupPromise = this.waitFor(options.remote.reach.discover, {
|
|
2128
|
+
signal: controller.signal,
|
|
2129
|
+
seek: "present",
|
|
2130
|
+
timeout: waitForTime ?? DEFAULT_TIMEOUT,
|
|
2131
|
+
});
|
|
2132
|
+
options.remote.reach.eager = true; // include the results from the discovered peer even if it is not mature
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
const waitPolicy =
|
|
2136
|
+
typeof options.remote.wait === "object"
|
|
2137
|
+
? options.remote.wait
|
|
2138
|
+
: undefined;
|
|
2139
|
+
if (
|
|
2140
|
+
waitPolicy?.behavior === "block" &&
|
|
2141
|
+
(waitPolicy.until ?? "any") === "any"
|
|
2142
|
+
) {
|
|
2143
|
+
const blockPromise = this.waitForCoverReady({
|
|
2144
|
+
domain: options.remote.domain,
|
|
2145
|
+
eager: options.remote.reach?.eager,
|
|
2146
|
+
settle: "any",
|
|
2147
|
+
timeout: waitPolicy.timeout ?? DEFAULT_TIMEOUT,
|
|
2148
|
+
signal: controller.signal,
|
|
2149
|
+
onTimeout: waitPolicy.onTimeout,
|
|
2150
|
+
});
|
|
2151
|
+
warmupPromise = warmupPromise
|
|
2152
|
+
? Promise.all([warmupPromise, blockPromise]).then(() => undefined)
|
|
2153
|
+
: blockPromise;
|
|
2154
|
+
}
|
|
1876
2155
|
}
|
|
1877
2156
|
|
|
1878
2157
|
const fetchFirst = async (
|
|
1879
2158
|
n: number,
|
|
1880
|
-
fetchOptions?: { from?: string[] },
|
|
2159
|
+
fetchOptions?: { from?: string[]; fetchedFirstForRemote?: Set<string> },
|
|
1881
2160
|
): Promise<boolean> => {
|
|
2161
|
+
await warmupPromise;
|
|
1882
2162
|
let hasMore = false;
|
|
1883
2163
|
|
|
1884
2164
|
queryRequestCoerced.fetch = n;
|
|
1885
|
-
await this.queryCommence(
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
return;
|
|
1903
|
-
} else if (response instanceof types.Results) {
|
|
1904
|
-
const results = response as types.Results<
|
|
1905
|
-
types.ResultTypeFromRequest<R, T, I>
|
|
1906
|
-
>;
|
|
1907
|
-
|
|
1908
|
-
if (results.kept === 0n && results.results.length === 0) {
|
|
2165
|
+
await this.queryCommence(
|
|
2166
|
+
queryRequestCoerced,
|
|
2167
|
+
{
|
|
2168
|
+
local: fetchOptions?.from != null ? false : options?.local,
|
|
2169
|
+
remote:
|
|
2170
|
+
options?.remote !== false
|
|
2171
|
+
? {
|
|
2172
|
+
...(typeof options?.remote === "object"
|
|
2173
|
+
? options.remote
|
|
2174
|
+
: {}),
|
|
2175
|
+
from: fetchOptions?.from,
|
|
2176
|
+
}
|
|
2177
|
+
: false,
|
|
2178
|
+
resolve,
|
|
2179
|
+
onResponse: async (response, from) => {
|
|
2180
|
+
if (!from) {
|
|
2181
|
+
logger.error("Missing response from");
|
|
1909
2182
|
return;
|
|
1910
2183
|
}
|
|
2184
|
+
if (response instanceof types.NoAccess) {
|
|
2185
|
+
logger.error("Dont have access");
|
|
2186
|
+
return;
|
|
2187
|
+
} else if (response instanceof types.Results) {
|
|
2188
|
+
const results = response as types.Results<
|
|
2189
|
+
types.ResultTypeFromRequest<R, T, I>
|
|
2190
|
+
>;
|
|
1911
2191
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
const buffer: BufferedResult<
|
|
1916
|
-
types.ResultTypeFromRequest<R, T, I> | I,
|
|
1917
|
-
I
|
|
1918
|
-
>[] = [];
|
|
1919
|
-
|
|
1920
|
-
for (const result of results.results) {
|
|
1921
|
-
if (result instanceof types.ResultValue) {
|
|
1922
|
-
const indexKey = indexerTypes.toId(
|
|
1923
|
-
this.indexByResolver(result.value),
|
|
1924
|
-
).primitive;
|
|
1925
|
-
if (visited.has(indexKey)) {
|
|
1926
|
-
continue;
|
|
1927
|
-
}
|
|
1928
|
-
visited.add(indexKey);
|
|
1929
|
-
|
|
1930
|
-
buffer.push({
|
|
1931
|
-
value: result.value as types.ResultTypeFromRequest<R, T, I>,
|
|
1932
|
-
context: result.context,
|
|
1933
|
-
from,
|
|
1934
|
-
indexed: await this.resolveIndexed<R>(
|
|
1935
|
-
result,
|
|
1936
|
-
results.results,
|
|
1937
|
-
),
|
|
1938
|
-
});
|
|
1939
|
-
} else {
|
|
1940
|
-
const indexKey = indexerTypes.toId(
|
|
1941
|
-
this.indexByResolver(result.value),
|
|
1942
|
-
).primitive;
|
|
2192
|
+
if (results.kept === 0n && results.results.length === 0) {
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
1943
2195
|
|
|
1944
|
-
|
|
1945
|
-
|
|
2196
|
+
if (results.kept > 0n) {
|
|
2197
|
+
hasMore = true;
|
|
2198
|
+
}
|
|
2199
|
+
const buffer: BufferedResult<
|
|
2200
|
+
types.ResultTypeFromRequest<R, T, I> | I,
|
|
2201
|
+
I
|
|
2202
|
+
>[] = [];
|
|
2203
|
+
|
|
2204
|
+
for (const result of results.results) {
|
|
2205
|
+
if (result instanceof types.ResultValue) {
|
|
2206
|
+
const indexKey = indexerTypes.toId(
|
|
2207
|
+
this.indexByResolver(result.value),
|
|
2208
|
+
).primitive;
|
|
2209
|
+
if (visited.has(indexKey)) {
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
visited.add(indexKey);
|
|
2213
|
+
|
|
2214
|
+
buffer.push({
|
|
2215
|
+
value: result.value as types.ResultTypeFromRequest<R, T, I>,
|
|
2216
|
+
context: result.context,
|
|
2217
|
+
from,
|
|
2218
|
+
indexed: await this.resolveIndexed<R>(
|
|
2219
|
+
result,
|
|
2220
|
+
results.results,
|
|
2221
|
+
),
|
|
2222
|
+
});
|
|
2223
|
+
} else {
|
|
2224
|
+
const indexKey = indexerTypes.toId(
|
|
2225
|
+
this.indexByResolver(result.value),
|
|
2226
|
+
).primitive;
|
|
2227
|
+
|
|
2228
|
+
if (visited.has(indexKey)) {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
visited.add(indexKey);
|
|
2232
|
+
buffer.push({
|
|
2233
|
+
value: result.value,
|
|
2234
|
+
context: result.context,
|
|
2235
|
+
from,
|
|
2236
|
+
indexed: coerceWithContext(
|
|
2237
|
+
result.indexed || result.value,
|
|
2238
|
+
result.context,
|
|
2239
|
+
),
|
|
2240
|
+
});
|
|
1946
2241
|
}
|
|
1947
|
-
visited.add(indexKey);
|
|
1948
|
-
buffer.push({
|
|
1949
|
-
value: result.value,
|
|
1950
|
-
context: result.context,
|
|
1951
|
-
from,
|
|
1952
|
-
indexed: coerceWithContext(
|
|
1953
|
-
result.indexed || result.value,
|
|
1954
|
-
result.context,
|
|
1955
|
-
),
|
|
1956
|
-
});
|
|
1957
2242
|
}
|
|
1958
|
-
}
|
|
1959
2243
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2244
|
+
peerBufferMap.set(from.hashcode(), {
|
|
2245
|
+
buffer,
|
|
2246
|
+
kept: Number(response.kept),
|
|
2247
|
+
});
|
|
2248
|
+
} else {
|
|
2249
|
+
throw new Error(
|
|
2250
|
+
"Unsupported result type: " + response?.constructor?.name,
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
},
|
|
1969
2254
|
},
|
|
1970
|
-
|
|
2255
|
+
fetchOptions?.fetchedFirstForRemote,
|
|
2256
|
+
);
|
|
1971
2257
|
|
|
1972
2258
|
if (!hasMore) {
|
|
1973
2259
|
maybeSetDone();
|
|
@@ -2015,7 +2301,10 @@ export class DocumentIndex<
|
|
|
2015
2301
|
amount: n - buffer.buffer.length,
|
|
2016
2302
|
});
|
|
2017
2303
|
// Fetch locally?
|
|
2018
|
-
if (
|
|
2304
|
+
if (
|
|
2305
|
+
peer === this.node.identity.publicKey.hashcode() &&
|
|
2306
|
+
this._resumableIterators.has(queryRequestCoerced.idString)
|
|
2307
|
+
) {
|
|
2019
2308
|
promises.push(
|
|
2020
2309
|
this.processQuery(
|
|
2021
2310
|
collectRequest,
|
|
@@ -2116,7 +2405,7 @@ export class DocumentIndex<
|
|
|
2116
2405
|
this.documentType,
|
|
2117
2406
|
this.indexedType,
|
|
2118
2407
|
this._sync,
|
|
2119
|
-
options,
|
|
2408
|
+
options as QueryDetailedOptions<T, I, D, any>,
|
|
2120
2409
|
)
|
|
2121
2410
|
.then(async (responses) => {
|
|
2122
2411
|
return Promise.all(
|
|
@@ -2229,11 +2518,10 @@ export class DocumentIndex<
|
|
|
2229
2518
|
),
|
|
2230
2519
|
);
|
|
2231
2520
|
|
|
2232
|
-
const pendingMoreResults = n < results.length;
|
|
2233
|
-
|
|
2234
2521
|
lastValueInOrder = results[0] || lastValueInOrder;
|
|
2235
|
-
|
|
2522
|
+
const pendingMoreResults = n < results.length; // check if there are more results to fetch, before splicing
|
|
2236
2523
|
const batch = results.splice(0, n);
|
|
2524
|
+
const hasMore = !fetchedAll || pendingMoreResults;
|
|
2237
2525
|
|
|
2238
2526
|
for (const result of batch) {
|
|
2239
2527
|
const arr = peerBufferMap.get(result.from.hashcode());
|
|
@@ -2247,7 +2535,6 @@ export class DocumentIndex<
|
|
|
2247
2535
|
}
|
|
2248
2536
|
}
|
|
2249
2537
|
|
|
2250
|
-
const hasMore = !fetchedAll || pendingMoreResults;
|
|
2251
2538
|
if (hasMore) {
|
|
2252
2539
|
unsetDone();
|
|
2253
2540
|
} else {
|
|
@@ -2281,6 +2568,8 @@ export class DocumentIndex<
|
|
|
2281
2568
|
) as ValueTypeFromRequest<Resolve, T, I>[];
|
|
2282
2569
|
}
|
|
2283
2570
|
|
|
2571
|
+
// no extra queued-first/last in simplified API
|
|
2572
|
+
|
|
2284
2573
|
return dedup(coercedBatch, this.indexByResolver);
|
|
2285
2574
|
};
|
|
2286
2575
|
|
|
@@ -2329,26 +2618,141 @@ export class DocumentIndex<
|
|
|
2329
2618
|
|
|
2330
2619
|
let joinListener: (() => void) | undefined;
|
|
2331
2620
|
|
|
2621
|
+
let fetchedFirstForRemote: Set<string> | undefined = undefined;
|
|
2622
|
+
|
|
2332
2623
|
let updateDeferred: ReturnType<typeof pDefer> | undefined;
|
|
2333
2624
|
const signalUpdate = () => updateDeferred?.resolve();
|
|
2334
|
-
const
|
|
2625
|
+
const _waitForUpdate = () =>
|
|
2335
2626
|
updateDeferred ? updateDeferred.promise : Promise.resolve();
|
|
2336
2627
|
|
|
2337
|
-
|
|
2628
|
+
// ---------------- Live updates wiring (sorted-only with optional filter) ----------------
|
|
2629
|
+
const normalizeUpdatesOption = (
|
|
2630
|
+
u?: UpdateOptions<T, I, Resolve>,
|
|
2631
|
+
):
|
|
2632
|
+
| {
|
|
2633
|
+
merge?:
|
|
2634
|
+
| {
|
|
2635
|
+
filter?: (
|
|
2636
|
+
evt: DocumentsChange<T, I>,
|
|
2637
|
+
) => MaybePromise<DocumentsChange<T, I> | void>;
|
|
2638
|
+
}
|
|
2639
|
+
| undefined;
|
|
2640
|
+
}
|
|
2641
|
+
| undefined => {
|
|
2642
|
+
if (u == null || u === false) return undefined;
|
|
2643
|
+
if (u === true)
|
|
2644
|
+
return {
|
|
2645
|
+
merge: {
|
|
2646
|
+
filter: (evt) => evt,
|
|
2647
|
+
},
|
|
2648
|
+
};
|
|
2649
|
+
if (typeof u === "object") {
|
|
2650
|
+
return {
|
|
2651
|
+
merge: u.merge
|
|
2652
|
+
? {
|
|
2653
|
+
filter:
|
|
2654
|
+
typeof u.merge === "object" ? u.merge.filter : (evt) => evt,
|
|
2655
|
+
}
|
|
2656
|
+
: {},
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
return undefined;
|
|
2660
|
+
};
|
|
2661
|
+
|
|
2662
|
+
const mergePolicy = normalizeUpdatesOption(options?.updates);
|
|
2663
|
+
const hasLiveUpdates = mergePolicy !== undefined;
|
|
2664
|
+
|
|
2665
|
+
// sorted-only mode: no per-queue handling
|
|
2666
|
+
|
|
2667
|
+
// If live updates enabled, ensure deferred exists so awaiting paths can block until changes
|
|
2668
|
+
if (hasLiveUpdates && !updateDeferred) {
|
|
2669
|
+
updateDeferred = pDefer<void>();
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
let updatesCleanup: (() => void) | undefined;
|
|
2673
|
+
if (hasLiveUpdates) {
|
|
2674
|
+
const localHash = this.node.identity.publicKey.hashcode();
|
|
2675
|
+
if (mergePolicy?.merge) {
|
|
2676
|
+
// Ensure local buffer exists for sorted merging
|
|
2677
|
+
if (!peerBufferMap.has(localHash)) {
|
|
2678
|
+
peerBufferMap.set(localHash, { kept: 0, buffer: [] });
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
const onChange = async (evt: CustomEvent<DocumentsChange<T, I>>) => {
|
|
2683
|
+
// Optional filter to mutate/suppress change events
|
|
2684
|
+
let filtered: DocumentsChange<T, I> | void = evt.detail;
|
|
2685
|
+
if (mergePolicy?.merge?.filter) {
|
|
2686
|
+
filtered = await mergePolicy.merge?.filter(evt.detail);
|
|
2687
|
+
}
|
|
2688
|
+
if (filtered) {
|
|
2689
|
+
// Remove entries that were deleted from all pending structures
|
|
2690
|
+
if (filtered.removed?.length) {
|
|
2691
|
+
// Remove from peer buffers
|
|
2692
|
+
for (const [_peer, entry] of peerBufferMap) {
|
|
2693
|
+
entry.buffer = entry.buffer.filter((x) => {
|
|
2694
|
+
const id = indexerTypes.toId(
|
|
2695
|
+
this.indexByResolver(x.indexed),
|
|
2696
|
+
).primitive;
|
|
2697
|
+
return !filtered!.removed!.some(
|
|
2698
|
+
(r) =>
|
|
2699
|
+
indexerTypes.toId(this.indexByResolver(r.__indexed))
|
|
2700
|
+
.primitive === id,
|
|
2701
|
+
);
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
// no non-sorted queues in simplified mode
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// Add new entries per strategy (sorted-only)
|
|
2708
|
+
if (filtered.added?.length) {
|
|
2709
|
+
const buf = peerBufferMap.get(localHash)!;
|
|
2710
|
+
for (const added of filtered.added) {
|
|
2711
|
+
const id = indexerTypes.toId(
|
|
2712
|
+
this.indexByResolver(added.__indexed),
|
|
2713
|
+
).primitive;
|
|
2714
|
+
if (visited.has(id)) continue; // already presented
|
|
2715
|
+
visited.add(id);
|
|
2716
|
+
buf.buffer.push({
|
|
2717
|
+
value: (resolve ? added : added.__indexed) as any,
|
|
2718
|
+
context: added.__context,
|
|
2719
|
+
from: this.node.identity.publicKey,
|
|
2720
|
+
indexed: coerceWithContext(added.__indexed, added.__context),
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
buf.kept = buf.buffer.length;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
typeof options?.updates === "object" &&
|
|
2727
|
+
options?.updates?.onChange?.(evt.detail);
|
|
2728
|
+
signalUpdate();
|
|
2729
|
+
};
|
|
2730
|
+
|
|
2731
|
+
this.documentEvents.addEventListener("change", onChange);
|
|
2732
|
+
updatesCleanup = () => {
|
|
2733
|
+
this.documentEvents.removeEventListener("change", onChange);
|
|
2734
|
+
};
|
|
2735
|
+
const cleanupDefaultUpdates = cleanup;
|
|
2736
|
+
cleanup = () => {
|
|
2737
|
+
updatesCleanup?.();
|
|
2738
|
+
return cleanupDefaultUpdates();
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
if (typeof options?.remote === "object" && options?.remote.wait) {
|
|
2338
2743
|
// was used to account for missed results when a peer joins; omitted in this minimal handler
|
|
2339
2744
|
|
|
2340
2745
|
updateDeferred = pDefer<void>();
|
|
2341
2746
|
|
|
2342
2747
|
// derive optional onMissedResults callback if provided
|
|
2343
2748
|
let onMissedResults =
|
|
2344
|
-
typeof options?.remote?.
|
|
2345
|
-
typeof options?.remote.
|
|
2346
|
-
? options.remote.
|
|
2749
|
+
typeof options?.remote?.wait === "object" &&
|
|
2750
|
+
typeof options?.remote.onLateResults === "function"
|
|
2751
|
+
? options.remote.onLateResults
|
|
2347
2752
|
: undefined;
|
|
2348
2753
|
|
|
2349
2754
|
const waitForTime =
|
|
2350
|
-
typeof options.remote.
|
|
2351
|
-
options.remote.joining.waitFor;
|
|
2755
|
+
typeof options.remote.wait === "object" && options.remote.wait.timeout;
|
|
2352
2756
|
|
|
2353
2757
|
const prevMaybeSetDone = maybeSetDone;
|
|
2354
2758
|
maybeSetDone = () => {
|
|
@@ -2362,15 +2766,22 @@ export class DocumentIndex<
|
|
|
2362
2766
|
signalUpdate();
|
|
2363
2767
|
}, waitForTime);
|
|
2364
2768
|
controller.signal.addEventListener("abort", () => signalUpdate());
|
|
2365
|
-
|
|
2769
|
+
fetchedFirstForRemote = new Set<string>();
|
|
2366
2770
|
joinListener = this.attachJoinListener({
|
|
2367
2771
|
signal: controller.signal,
|
|
2772
|
+
eager: options.remote.reach?.eager,
|
|
2368
2773
|
onPeer: async (pk) => {
|
|
2369
2774
|
if (done) return;
|
|
2370
2775
|
const hash = pk.hashcode();
|
|
2776
|
+
await fetchPromise; // ensure fetches in flight are done
|
|
2371
2777
|
if (peerBufferMap.has(hash)) return;
|
|
2778
|
+
if (fetchedFirstForRemote!.has(hash)) return;
|
|
2372
2779
|
if (totalFetchedCounter > 0) {
|
|
2373
|
-
|
|
2780
|
+
fetchPromise = fetchFirst(totalFetchedCounter, {
|
|
2781
|
+
from: [hash],
|
|
2782
|
+
fetchedFirstForRemote,
|
|
2783
|
+
});
|
|
2784
|
+
await fetchPromise;
|
|
2374
2785
|
if (onMissedResults) {
|
|
2375
2786
|
const pending = peerBufferMap.get(hash)?.buffer;
|
|
2376
2787
|
if (pending && pending.length > 0) {
|
|
@@ -2409,6 +2820,29 @@ export class DocumentIndex<
|
|
|
2409
2820
|
};
|
|
2410
2821
|
}
|
|
2411
2822
|
|
|
2823
|
+
if (options?.closePolicy === "manual") {
|
|
2824
|
+
let prevMaybeSetDone = maybeSetDone;
|
|
2825
|
+
maybeSetDone = () => {
|
|
2826
|
+
if (drain) {
|
|
2827
|
+
prevMaybeSetDone();
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
const remoteWaitActive =
|
|
2832
|
+
typeof options?.remote === "object" && !!options.remote.wait;
|
|
2833
|
+
|
|
2834
|
+
const waitForUpdateAndResetDeferred = async () => {
|
|
2835
|
+
if (remoteWaitActive) {
|
|
2836
|
+
// wait until: join fetch adds results, cleanup runs, or the join-wait times out
|
|
2837
|
+
await _waitForUpdate();
|
|
2838
|
+
|
|
2839
|
+
// re-arm the deferred for the next cycle (only if joining is enabled and we're not done)
|
|
2840
|
+
if (updateDeferred && !doneFn()) {
|
|
2841
|
+
updateDeferred = pDefer<void>();
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
};
|
|
2845
|
+
|
|
2412
2846
|
return {
|
|
2413
2847
|
close,
|
|
2414
2848
|
next,
|
|
@@ -2421,26 +2855,24 @@ export class DocumentIndex<
|
|
|
2421
2855
|
return kept; // TODO this should be more accurate
|
|
2422
2856
|
},
|
|
2423
2857
|
all: async () => {
|
|
2858
|
+
drain = true;
|
|
2424
2859
|
let result: ValueTypeFromRequest<Resolve, T, I>[] = [];
|
|
2425
2860
|
let c = 0;
|
|
2426
2861
|
while (doneFn() !== true) {
|
|
2427
|
-
c++;
|
|
2428
2862
|
let batch = await next(100);
|
|
2429
|
-
|
|
2430
|
-
|
|
2863
|
+
c += batch.length;
|
|
2864
|
+
if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
|
|
2865
|
+
logger.warn(
|
|
2866
|
+
"Iterating for more than " +
|
|
2867
|
+
WARNING_WHEN_ITERATING_FOR_MORE_THAN +
|
|
2868
|
+
" results",
|
|
2869
|
+
);
|
|
2431
2870
|
}
|
|
2432
2871
|
if (batch.length > 0) {
|
|
2433
2872
|
result.push(...batch);
|
|
2434
2873
|
continue;
|
|
2435
2874
|
}
|
|
2436
|
-
|
|
2437
|
-
// wait until: join fetch adds results, cleanup runs, or the join-wait times out
|
|
2438
|
-
await waitForUpdate();
|
|
2439
|
-
|
|
2440
|
-
// re-arm the deferred for the next cycle (only if joining is enabled and we’re not done)
|
|
2441
|
-
if (updateDeferred && !doneFn()) {
|
|
2442
|
-
updateDeferred = pDefer<void>();
|
|
2443
|
-
}
|
|
2875
|
+
await waitForUpdateAndResetDeferred();
|
|
2444
2876
|
}
|
|
2445
2877
|
cleanupAndDone();
|
|
2446
2878
|
return result;
|
|
@@ -2453,6 +2885,26 @@ export class DocumentIndex<
|
|
|
2453
2885
|
cleanupAndDone();
|
|
2454
2886
|
return batch[0];
|
|
2455
2887
|
},
|
|
2888
|
+
[Symbol.asyncIterator]: async function* () {
|
|
2889
|
+
drain = true;
|
|
2890
|
+
let c = 0;
|
|
2891
|
+
while (doneFn() !== true) {
|
|
2892
|
+
const batch = await next(100);
|
|
2893
|
+
c += batch.length;
|
|
2894
|
+
if (c > WARNING_WHEN_ITERATING_FOR_MORE_THAN) {
|
|
2895
|
+
logger.warn(
|
|
2896
|
+
"Iterating for more than " +
|
|
2897
|
+
WARNING_WHEN_ITERATING_FOR_MORE_THAN +
|
|
2898
|
+
" results",
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
for (const entry of batch) {
|
|
2902
|
+
yield entry;
|
|
2903
|
+
}
|
|
2904
|
+
await waitForUpdateAndResetDeferred();
|
|
2905
|
+
}
|
|
2906
|
+
cleanupAndDone();
|
|
2907
|
+
},
|
|
2456
2908
|
};
|
|
2457
2909
|
}
|
|
2458
2910
|
|
|
@@ -2568,26 +3020,15 @@ export class DocumentIndex<
|
|
|
2568
3020
|
}
|
|
2569
3021
|
|
|
2570
3022
|
public async waitFor(
|
|
2571
|
-
other:
|
|
2572
|
-
|
|
2573
|
-
|
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
): Promise<
|
|
2578
|
-
await super.waitFor(other, options);
|
|
2579
|
-
const
|
|
2580
|
-
const expectedHashes = new Set(
|
|
2581
|
-
ids.map((x) =>
|
|
2582
|
-
typeof x === "string"
|
|
2583
|
-
? x
|
|
2584
|
-
: x instanceof PublicSignKey
|
|
2585
|
-
? x.hashcode()
|
|
2586
|
-
: getPublicKeyFromPeerId(x).hashcode(),
|
|
2587
|
-
),
|
|
2588
|
-
);
|
|
2589
|
-
|
|
2590
|
-
for (const key of expectedHashes) {
|
|
3023
|
+
other: PeerRefs,
|
|
3024
|
+
options?: {
|
|
3025
|
+
seek?: "any" | "present";
|
|
3026
|
+
signal?: AbortSignal;
|
|
3027
|
+
timeout?: number;
|
|
3028
|
+
},
|
|
3029
|
+
): Promise<string[]> {
|
|
3030
|
+
const hashes = await super.waitFor(other, options);
|
|
3031
|
+
for (const key of hashes) {
|
|
2591
3032
|
await waitFor(
|
|
2592
3033
|
async () =>
|
|
2593
3034
|
(await this._log.replicationIndex.count({ query: { hash: key } })) >
|
|
@@ -2595,5 +3036,6 @@ export class DocumentIndex<
|
|
|
2595
3036
|
options,
|
|
2596
3037
|
);
|
|
2597
3038
|
}
|
|
3039
|
+
return hashes;
|
|
2598
3040
|
}
|
|
2599
3041
|
}
|