@peerbit/log 6.1.0 → 6.2.0
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/benchmark/native-graph.js +447 -5
- package/dist/benchmark/native-graph.js.map +1 -1
- package/dist/src/clock.d.ts +1 -0
- package/dist/src/clock.d.ts.map +1 -1
- package/dist/src/clock.js +32 -0
- package/dist/src/clock.js.map +1 -1
- package/dist/src/entry-create.d.ts +1 -0
- package/dist/src/entry-create.d.ts.map +1 -1
- package/dist/src/entry-create.js.map +1 -1
- package/dist/src/entry-index.d.ts +288 -12
- package/dist/src/entry-index.d.ts.map +1 -1
- package/dist/src/entry-index.js +1478 -33
- package/dist/src/entry-index.js.map +1 -1
- package/dist/src/entry-v0.d.ts +184 -1
- package/dist/src/entry-v0.d.ts.map +1 -1
- package/dist/src/entry-v0.js +1021 -12
- package/dist/src/entry-v0.js.map +1 -1
- package/dist/src/entry.d.ts +73 -1
- package/dist/src/entry.d.ts.map +1 -1
- package/dist/src/entry.js +124 -1
- package/dist/src/entry.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/log.d.ts +62 -13
- package/dist/src/log.d.ts.map +1 -1
- package/dist/src/log.js +2282 -100
- package/dist/src/log.js.map +1 -1
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/trim.d.ts +14 -2
- package/dist/src/trim.d.ts.map +1 -1
- package/dist/src/trim.js +92 -4
- package/dist/src/trim.js.map +1 -1
- package/package.json +18 -15
- package/src/clock.ts +34 -0
- package/src/entry-create.ts +1 -0
- package/src/entry-index.ts +2232 -68
- package/src/entry-v0.ts +1488 -13
- package/src/entry.ts +235 -1
- package/src/index.ts +7 -1
- package/src/log.ts +3246 -206
- package/src/runtime.ts +16 -0
- package/src/trim.ts +133 -7
package/src/entry-index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deserialize, serialize } from "@dao-xyz/borsh";
|
|
2
|
-
import { type Blocks } from "@peerbit/blocks-interface";
|
|
2
|
+
import { type Blocks, type GetOptions } from "@peerbit/blocks-interface";
|
|
3
3
|
import { Cache } from "@peerbit/cache";
|
|
4
4
|
import type { PublicSignKey } from "@peerbit/crypto";
|
|
5
5
|
import {
|
|
@@ -17,10 +17,16 @@ import {
|
|
|
17
17
|
toId,
|
|
18
18
|
} from "@peerbit/indexer-interface";
|
|
19
19
|
import { FOREGROUND_READ_MESSAGE_PRIORITY } from "@peerbit/stream-interface";
|
|
20
|
-
import
|
|
20
|
+
import { LamportClock as Clock, Timestamp } from "./clock.js";
|
|
21
|
+
import { ShallowEntry, ShallowMeta } from "./entry-shallow.js";
|
|
21
22
|
import { EntryType } from "./entry-type.js";
|
|
22
|
-
import {
|
|
23
|
-
|
|
23
|
+
import {
|
|
24
|
+
Entry,
|
|
25
|
+
type PreparedEntryBlock,
|
|
26
|
+
type PreparedNativeLogEntry,
|
|
27
|
+
type ShallowOrFullEntry,
|
|
28
|
+
} from "./entry.js";
|
|
29
|
+
import type { SortFn, SortableEntry } from "./log-sorting.js";
|
|
24
30
|
import { logger as baseLogger } from "./logger.js";
|
|
25
31
|
|
|
26
32
|
const log = baseLogger.newScope("entry-index");
|
|
@@ -36,33 +42,360 @@ export type ResultsIterator<T> = {
|
|
|
36
42
|
const ENTRY_CACHE_MAX_SIZE = 10; // TODO as param for log
|
|
37
43
|
const DEFERRED_INDEX_FLUSH_IDLE_MS = 250;
|
|
38
44
|
const NATIVE_GRAPH_REBUILD_BATCH_SIZE = 512;
|
|
45
|
+
const TIMESTAMP_WALL_TIME_KEY = ["meta", "clock", "timestamp", "wallTime"];
|
|
46
|
+
const TIMESTAMP_LOGICAL_KEY = ["meta", "clock", "timestamp", "logical"];
|
|
47
|
+
|
|
48
|
+
type EntryIndexProfileValue = string | number | boolean | undefined;
|
|
49
|
+
type EntryIndexProfileEvent = {
|
|
50
|
+
name: string;
|
|
51
|
+
component?: string;
|
|
52
|
+
durationMs?: number;
|
|
53
|
+
entries?: number;
|
|
54
|
+
messages?: number;
|
|
55
|
+
count?: number;
|
|
56
|
+
details?: Record<string, EntryIndexProfileValue>;
|
|
57
|
+
};
|
|
58
|
+
type EntryIndexProfileSink = (event: EntryIndexProfileEvent) => void;
|
|
59
|
+
|
|
60
|
+
const entryIndexProfileNow = () =>
|
|
61
|
+
globalThis.performance?.now?.() ?? Date.now();
|
|
62
|
+
const entryIndexProfileStart = (sink: EntryIndexProfileSink | undefined) =>
|
|
63
|
+
sink ? entryIndexProfileNow() : 0;
|
|
64
|
+
const emitEntryIndexProfileDuration = (
|
|
65
|
+
sink: EntryIndexProfileSink | undefined,
|
|
66
|
+
startedAt: number,
|
|
67
|
+
event: Omit<EntryIndexProfileEvent, "durationMs">,
|
|
68
|
+
) => {
|
|
69
|
+
if (!sink) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
sink({
|
|
73
|
+
...event,
|
|
74
|
+
durationMs: entryIndexProfileNow() - startedAt,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type BlocksWithGetMany = Blocks & {
|
|
79
|
+
getMany?: (
|
|
80
|
+
cids: string[],
|
|
81
|
+
options?: GetOptions,
|
|
82
|
+
) => Promise<Array<Uint8Array | undefined>> | Array<Uint8Array | undefined>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type BlocksWithRmMany = Blocks & {
|
|
86
|
+
rmMany?: (cids: string[]) => Promise<number | void> | number | void;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type BlocksWithPutKnown = Blocks & {
|
|
90
|
+
putKnown?: (cid: string, bytes: Uint8Array) => Promise<string> | string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const hasRmMany = (store: Blocks): store is BlocksWithRmMany =>
|
|
94
|
+
typeof (store as BlocksWithRmMany).rmMany === "function";
|
|
95
|
+
|
|
96
|
+
type IndexWithExactDelete = Index<any> & {
|
|
97
|
+
delIds: (
|
|
98
|
+
deleteIds: string[],
|
|
99
|
+
) => Promise<ReturnType<typeof toId>[]> | ReturnType<typeof toId>[];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const hasExactDelete = (index: Index<any>): index is IndexWithExactDelete =>
|
|
103
|
+
typeof (index as IndexWithExactDelete).delIds === "function";
|
|
104
|
+
|
|
105
|
+
type IndexWithExactDeleteCount = Index<any> & {
|
|
106
|
+
delIdsCount: (deleteIds: string[]) => Promise<number> | number;
|
|
107
|
+
};
|
|
39
108
|
|
|
40
|
-
|
|
109
|
+
const hasExactDeleteCount = (
|
|
110
|
+
index: Index<any>,
|
|
111
|
+
): index is IndexWithExactDeleteCount =>
|
|
112
|
+
typeof (index as IndexWithExactDeleteCount).delIdsCount === "function";
|
|
113
|
+
|
|
114
|
+
const putPreparedEntryBlock = async (
|
|
115
|
+
store: Blocks,
|
|
116
|
+
prepared: PreparedEntryBlock,
|
|
117
|
+
) => {
|
|
118
|
+
const storeWithKnown = store as BlocksWithPutKnown;
|
|
119
|
+
return typeof storeWithKnown.putKnown === "function"
|
|
120
|
+
? await storeWithKnown.putKnown(prepared.cid, prepared.block.bytes)
|
|
121
|
+
: await store.put(prepared);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
type NativeLogEntry = PreparedNativeLogEntry;
|
|
125
|
+
|
|
126
|
+
export type PreparedAppendIndexFacts = {
|
|
41
127
|
hash: string;
|
|
128
|
+
meta: {
|
|
129
|
+
clock: Clock;
|
|
130
|
+
gid: string;
|
|
131
|
+
next: string[];
|
|
132
|
+
type: EntryType;
|
|
133
|
+
data?: Uint8Array;
|
|
134
|
+
};
|
|
135
|
+
size?: number;
|
|
136
|
+
shallowEntry?: ShallowEntry;
|
|
137
|
+
getShallowEntry?: (isHead?: boolean) => ShallowEntry;
|
|
138
|
+
nativeEntry?: NativeLogEntry;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
type NativeJoinCutCheck = {
|
|
42
142
|
gid: string;
|
|
143
|
+
wallTime: bigint | number | string;
|
|
144
|
+
logical?: number;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type NativeJoinPlanInput = {
|
|
148
|
+
hash: string;
|
|
43
149
|
next: string[];
|
|
44
150
|
type: number;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
151
|
+
cutCheck?: NativeJoinCutCheck;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
155
|
+
type PendingIndexWrite = ShallowEntry | (() => ShallowEntry);
|
|
156
|
+
|
|
157
|
+
const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
|
|
158
|
+
!!value && typeof (value as { then?: unknown }).then === "function";
|
|
159
|
+
|
|
160
|
+
const mapMaybePromise = <T, R>(
|
|
161
|
+
value: MaybePromise<T>,
|
|
162
|
+
fn: (value: T) => MaybePromise<R>,
|
|
163
|
+
): MaybePromise<R> => (isPromiseLike(value) ? value.then(fn) : fn(value));
|
|
164
|
+
|
|
165
|
+
const materializePreparedAppendShallowEntry = (
|
|
166
|
+
entry: PreparedAppendIndexFacts,
|
|
167
|
+
isHead: boolean,
|
|
168
|
+
): ShallowEntry => {
|
|
169
|
+
const shallowEntry =
|
|
170
|
+
entry.shallowEntry ?? entry.getShallowEntry?.(isHead);
|
|
171
|
+
if (!shallowEntry) {
|
|
172
|
+
throw new Error("Missing prepared append shallow entry");
|
|
173
|
+
}
|
|
174
|
+
shallowEntry.head = isHead;
|
|
175
|
+
entry.shallowEntry = shallowEntry;
|
|
176
|
+
return shallowEntry;
|
|
53
177
|
};
|
|
54
178
|
|
|
55
179
|
export type NativeLogGraph = {
|
|
180
|
+
readonly length: number;
|
|
181
|
+
has: (hash: string) => boolean;
|
|
182
|
+
hasMany: (hashes: Iterable<string>) => Set<string>;
|
|
56
183
|
put: (entry: NativeLogEntry) => void;
|
|
184
|
+
putBatch?: (entries: NativeLogEntry[]) => void;
|
|
185
|
+
putAppendChain?: (entries: NativeLogEntry[]) => void;
|
|
186
|
+
prepareEntryV0PlainChainAndPut?: (input: {
|
|
187
|
+
clockId: Uint8Array;
|
|
188
|
+
privateKey: Uint8Array;
|
|
189
|
+
publicKey: Uint8Array;
|
|
190
|
+
wallTimes: Array<bigint | number | string>;
|
|
191
|
+
logicals?: number[];
|
|
192
|
+
gid: string;
|
|
193
|
+
initialNext?: string[];
|
|
194
|
+
type?: number;
|
|
195
|
+
metaDatas?: Array<Uint8Array | undefined>;
|
|
196
|
+
payloadDatas: Uint8Array[];
|
|
197
|
+
}) => MaybePromise<
|
|
198
|
+
Array<{
|
|
199
|
+
bytes: Uint8Array;
|
|
200
|
+
cid: string;
|
|
201
|
+
byteLength: number;
|
|
202
|
+
signature: Uint8Array;
|
|
203
|
+
next: string[];
|
|
204
|
+
metaBytes: Uint8Array;
|
|
205
|
+
payloadBytes: Uint8Array;
|
|
206
|
+
signatureBytes: Uint8Array;
|
|
207
|
+
}>
|
|
208
|
+
>;
|
|
209
|
+
prepareEntryV0PlainEntryAndPut?: (input: {
|
|
210
|
+
clockId: Uint8Array;
|
|
211
|
+
privateKey: Uint8Array;
|
|
212
|
+
publicKey: Uint8Array;
|
|
213
|
+
wallTime: bigint | number | string;
|
|
214
|
+
logical?: number;
|
|
215
|
+
gid: string;
|
|
216
|
+
next?: string[];
|
|
217
|
+
type?: number;
|
|
218
|
+
metaData?: Uint8Array;
|
|
219
|
+
payloadData: Uint8Array;
|
|
220
|
+
includeMaterializationBytes?: boolean;
|
|
221
|
+
includeAppendFactsBytes?: boolean;
|
|
222
|
+
trimLengthTo?: number;
|
|
223
|
+
}) => MaybePromise<{
|
|
224
|
+
bytes: Uint8Array;
|
|
225
|
+
cid: string;
|
|
226
|
+
byteLength: number;
|
|
227
|
+
signature?: Uint8Array;
|
|
228
|
+
next: string[];
|
|
229
|
+
metaBytes?: Uint8Array;
|
|
230
|
+
payloadBytes?: Uint8Array;
|
|
231
|
+
signatureBytes?: Uint8Array;
|
|
232
|
+
hashDigestBytes?: Uint8Array;
|
|
233
|
+
trimmedEntries?: PreparedNativeLogEntry[];
|
|
234
|
+
}>;
|
|
235
|
+
prepareEntryV0PlainChainCommit?: (
|
|
236
|
+
input: {
|
|
237
|
+
clockId: Uint8Array;
|
|
238
|
+
privateKey: Uint8Array;
|
|
239
|
+
publicKey: Uint8Array;
|
|
240
|
+
wallTimes: Array<bigint | number | string>;
|
|
241
|
+
logicals?: number[];
|
|
242
|
+
gid: string;
|
|
243
|
+
initialNext?: string[];
|
|
244
|
+
type?: number;
|
|
245
|
+
metaDatas?: Array<Uint8Array | undefined>;
|
|
246
|
+
payloadDatas: Uint8Array[];
|
|
247
|
+
},
|
|
248
|
+
blockStore: unknown,
|
|
249
|
+
) => MaybePromise<
|
|
250
|
+
| Array<{
|
|
251
|
+
bytes?: Uint8Array;
|
|
252
|
+
cid: string;
|
|
253
|
+
byteLength: number;
|
|
254
|
+
signature: Uint8Array;
|
|
255
|
+
next: string[];
|
|
256
|
+
metaBytes: Uint8Array;
|
|
257
|
+
payloadBytes: Uint8Array;
|
|
258
|
+
signatureBytes: Uint8Array;
|
|
259
|
+
}>
|
|
260
|
+
| undefined
|
|
261
|
+
>;
|
|
262
|
+
prepareEntryV0PlainEntryCommit?: (
|
|
263
|
+
input: {
|
|
264
|
+
clockId: Uint8Array;
|
|
265
|
+
privateKey: Uint8Array;
|
|
266
|
+
publicKey: Uint8Array;
|
|
267
|
+
wallTime: bigint | number | string;
|
|
268
|
+
logical?: number;
|
|
269
|
+
gid: string;
|
|
270
|
+
next?: string[];
|
|
271
|
+
type?: number;
|
|
272
|
+
metaData?: Uint8Array;
|
|
273
|
+
payloadData: Uint8Array;
|
|
274
|
+
resolveTrimmedEntries?: boolean;
|
|
275
|
+
},
|
|
276
|
+
blockStore: unknown,
|
|
277
|
+
) => MaybePromise<
|
|
278
|
+
| {
|
|
279
|
+
bytes?: Uint8Array;
|
|
280
|
+
cid: string;
|
|
281
|
+
byteLength: number;
|
|
282
|
+
signature?: Uint8Array;
|
|
283
|
+
next: string[];
|
|
284
|
+
metaBytes?: Uint8Array;
|
|
285
|
+
payloadBytes?: Uint8Array;
|
|
286
|
+
signatureBytes?: Uint8Array;
|
|
287
|
+
hashDigestBytes?: Uint8Array;
|
|
288
|
+
trimmedEntries?: PreparedNativeLogEntry[];
|
|
289
|
+
trimmedEntryHashes?: string[];
|
|
290
|
+
}
|
|
291
|
+
| undefined
|
|
292
|
+
>;
|
|
293
|
+
prepareEntryV0PlainEntriesCommit?: (
|
|
294
|
+
input: {
|
|
295
|
+
clockId: Uint8Array;
|
|
296
|
+
privateKey: Uint8Array;
|
|
297
|
+
publicKey: Uint8Array;
|
|
298
|
+
wallTimes: Array<bigint | number | string>;
|
|
299
|
+
logicals?: number[];
|
|
300
|
+
gids: string[];
|
|
301
|
+
nexts: string[][];
|
|
302
|
+
type?: number;
|
|
303
|
+
metaDatas?: Array<Uint8Array | undefined>;
|
|
304
|
+
payloadDatas: Uint8Array[];
|
|
305
|
+
},
|
|
306
|
+
blockStore: unknown,
|
|
307
|
+
) => MaybePromise<
|
|
308
|
+
| Array<{
|
|
309
|
+
bytes?: Uint8Array;
|
|
310
|
+
cid: string;
|
|
311
|
+
byteLength: number;
|
|
312
|
+
signature?: Uint8Array;
|
|
313
|
+
next: string[];
|
|
314
|
+
metaBytes?: Uint8Array;
|
|
315
|
+
payloadBytes?: Uint8Array;
|
|
316
|
+
signatureBytes?: Uint8Array;
|
|
317
|
+
hashDigestBytes?: Uint8Array;
|
|
318
|
+
}>
|
|
319
|
+
| undefined
|
|
320
|
+
>;
|
|
57
321
|
delete: (hash: string) => boolean;
|
|
322
|
+
deleteMany?: (hashes: Iterable<string>) => number;
|
|
323
|
+
oldestEntries?: (limit: number) => NativeLogEntry[];
|
|
58
324
|
clear: () => void;
|
|
59
325
|
heads: (gid?: string) => string[];
|
|
326
|
+
hasHead: (gid?: string) => boolean;
|
|
327
|
+
hasAnyHead: (gids: Iterable<string>) => boolean;
|
|
328
|
+
hasAnyHeadBatch: (gidSets: Iterable<Iterable<string>>) => boolean[];
|
|
329
|
+
headDataEntries: (gid?: string) => NativeLogHeadDataEntry[];
|
|
330
|
+
maxHeadDataU32: (gid?: string) => number | undefined;
|
|
331
|
+
maxHeadDataU32Batch?: (gids: Iterable<string>) => Array<number | undefined>;
|
|
332
|
+
headEntries: (gid?: string) => SortableEntry[];
|
|
333
|
+
joinHeadEntries: (gid?: string) => NativeLogJoinEntry[];
|
|
334
|
+
childJoinEntries: (hash: string) => NativeLogJoinEntry[];
|
|
335
|
+
entryMetadataBatch?: (
|
|
336
|
+
hashes: Iterable<string>,
|
|
337
|
+
) => Array<NativeLogEntryMetadata | undefined>;
|
|
338
|
+
entryMetadataHintsBatch?: (
|
|
339
|
+
hashes: Iterable<string>,
|
|
340
|
+
) => Array<NativeLogEntryMetadata | undefined>;
|
|
341
|
+
entrySignaturePublicKeysBatch?: (
|
|
342
|
+
hashes: Iterable<string>,
|
|
343
|
+
) => Array<Uint8Array | undefined>;
|
|
344
|
+
uniqueReferenceGids: (hash: string) => string[] | undefined;
|
|
345
|
+
uniqueReferenceGidRowsBatch?: (
|
|
346
|
+
hashes: Iterable<string>,
|
|
347
|
+
) => Array<Array<[string, string]> | undefined>;
|
|
348
|
+
uniqueReferenceGidRowsFlatBatch?: (
|
|
349
|
+
hashes: Iterable<string>,
|
|
350
|
+
) => Array<[number, string, string]> | undefined;
|
|
351
|
+
planDeleteRecursively: (
|
|
352
|
+
hashes: Iterable<string>,
|
|
353
|
+
skipFirst?: boolean,
|
|
354
|
+
) => string[];
|
|
355
|
+
payloadSizeSum: () => number;
|
|
356
|
+
oldestHash?: () => string | undefined;
|
|
357
|
+
newestHash?: () => string | undefined;
|
|
60
358
|
countHasNext: (next: string, excludeHash?: string) => number;
|
|
61
|
-
shadowedGids: (
|
|
62
|
-
|
|
359
|
+
shadowedGids: (gid: string, next: string[], excludeHash?: string) => string[];
|
|
360
|
+
planJoin: (
|
|
361
|
+
hash: string,
|
|
63
362
|
next: string[],
|
|
64
|
-
|
|
65
|
-
|
|
363
|
+
type: number,
|
|
364
|
+
reset?: boolean,
|
|
365
|
+
cutCheck?: NativeJoinCutCheck,
|
|
366
|
+
) => JoinPlan;
|
|
367
|
+
planJoinBatch?: (
|
|
368
|
+
entries: NativeJoinPlanInput[],
|
|
369
|
+
reset?: boolean,
|
|
370
|
+
) => JoinPlan[];
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export type JoinPlan = {
|
|
374
|
+
skip: boolean;
|
|
375
|
+
missingParents: string[];
|
|
376
|
+
cutChecked: boolean;
|
|
377
|
+
coveredByCut: boolean;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
export type NativeLogJoinEntry = SortableEntry & {
|
|
381
|
+
meta: SortableEntry["meta"] & {
|
|
382
|
+
type: EntryType;
|
|
383
|
+
next: string[];
|
|
384
|
+
};
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
export type NativeLogHeadDataEntry = {
|
|
388
|
+
hash: string;
|
|
389
|
+
meta: {
|
|
390
|
+
data?: Uint8Array;
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export type NativeLogEntryMetadata = {
|
|
395
|
+
hash: string;
|
|
396
|
+
gid: string;
|
|
397
|
+
data?: Uint8Array;
|
|
398
|
+
replicas?: number;
|
|
66
399
|
};
|
|
67
400
|
|
|
68
401
|
type ResolveFullyOptions =
|
|
@@ -80,6 +413,7 @@ type ResolveFullyOptions =
|
|
|
80
413
|
| boolean;
|
|
81
414
|
ignoreMissing?: boolean;
|
|
82
415
|
};
|
|
416
|
+
|
|
83
417
|
type ResolveShapeOptions = { type: "shape"; shape: Shape };
|
|
84
418
|
export type MaybeResolveOptions =
|
|
85
419
|
| false
|
|
@@ -123,6 +457,36 @@ const withDefaultRemoteReadPriority = <
|
|
|
123
457
|
},
|
|
124
458
|
} as O;
|
|
125
459
|
};
|
|
460
|
+
|
|
461
|
+
const canBatchResolveFromStore = (
|
|
462
|
+
store: Blocks,
|
|
463
|
+
options?: ResolveFullyOptions,
|
|
464
|
+
): store is BlocksWithGetMany => {
|
|
465
|
+
if (typeof (store as BlocksWithGetMany).getMany !== "function") {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
return typeof options !== "object" || !options.remote;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const sortKeyEquals = (sort: Sort | undefined, key: string[]) =>
|
|
472
|
+
Array.isArray(sort?.key) &&
|
|
473
|
+
sort.key.length === key.length &&
|
|
474
|
+
sort.key.every((part, index) => part === key[index]);
|
|
475
|
+
|
|
476
|
+
const timestampSortDirection = (sort: Sort[]): SortDirection | undefined => {
|
|
477
|
+
if (sort.length < 2) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const [wallTime, logical] = sort;
|
|
481
|
+
if (
|
|
482
|
+
!sortKeyEquals(wallTime, TIMESTAMP_WALL_TIME_KEY) ||
|
|
483
|
+
!sortKeyEquals(logical, TIMESTAMP_LOGICAL_KEY) ||
|
|
484
|
+
wallTime.direction !== logical.direction
|
|
485
|
+
) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
return wallTime.direction;
|
|
489
|
+
};
|
|
126
490
|
export type ReturnTypeFromResolveOptions<
|
|
127
491
|
R extends MaybeResolveOptions,
|
|
128
492
|
T,
|
|
@@ -138,8 +502,9 @@ export class EntryIndex<T> {
|
|
|
138
502
|
private initialied = false;
|
|
139
503
|
private _length: number;
|
|
140
504
|
private insertionPromises: Map<string, Promise<void>>;
|
|
141
|
-
private pendingIndexWrites: Map<string,
|
|
505
|
+
private pendingIndexWrites: Map<string, PendingIndexWrite>;
|
|
142
506
|
private pendingIndexFlushTimer?: ReturnType<typeof setTimeout>;
|
|
507
|
+
private pendingIndexFlushLastWriteMs = 0;
|
|
143
508
|
constructor(
|
|
144
509
|
readonly properties: {
|
|
145
510
|
store: Blocks;
|
|
@@ -175,15 +540,48 @@ export class EntryIndex<T> {
|
|
|
175
540
|
this.pendingIndexWrites = new Map();
|
|
176
541
|
}
|
|
177
542
|
|
|
543
|
+
private materializePendingIndexWrite(
|
|
544
|
+
hash: string,
|
|
545
|
+
pending: PendingIndexWrite,
|
|
546
|
+
): ShallowEntry {
|
|
547
|
+
if (typeof pending !== "function") {
|
|
548
|
+
return pending;
|
|
549
|
+
}
|
|
550
|
+
const shallowEntry = pending();
|
|
551
|
+
this.pendingIndexWrites.set(hash, shallowEntry);
|
|
552
|
+
return shallowEntry;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private getPendingIndexWrite(hash: string): ShallowEntry | undefined {
|
|
556
|
+
const pending = this.pendingIndexWrites.get(hash);
|
|
557
|
+
return pending
|
|
558
|
+
? this.materializePendingIndexWrite(hash, pending)
|
|
559
|
+
: undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
178
562
|
private schedulePendingIndexWriteFlush() {
|
|
563
|
+
this.pendingIndexFlushLastWriteMs = Date.now();
|
|
179
564
|
if (this.pendingIndexFlushTimer) {
|
|
180
|
-
|
|
565
|
+
return;
|
|
181
566
|
}
|
|
182
|
-
|
|
567
|
+
const flushAfterIdle = () => {
|
|
568
|
+
const remainingMs =
|
|
569
|
+
DEFERRED_INDEX_FLUSH_IDLE_MS -
|
|
570
|
+
(Date.now() - this.pendingIndexFlushLastWriteMs);
|
|
571
|
+
if (remainingMs > 0) {
|
|
572
|
+
this.pendingIndexFlushTimer = setTimeout(flushAfterIdle, remainingMs);
|
|
573
|
+
this.pendingIndexFlushTimer.unref?.();
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
this.pendingIndexFlushTimer = undefined;
|
|
183
577
|
void this.flushPendingWrites().catch((error) => {
|
|
184
578
|
log.error("Failed to flush deferred entry-index writes", error);
|
|
185
579
|
});
|
|
186
|
-
}
|
|
580
|
+
};
|
|
581
|
+
this.pendingIndexFlushTimer = setTimeout(
|
|
582
|
+
flushAfterIdle,
|
|
583
|
+
DEFERRED_INDEX_FLUSH_IDLE_MS,
|
|
584
|
+
);
|
|
187
585
|
this.pendingIndexFlushTimer.unref?.();
|
|
188
586
|
}
|
|
189
587
|
|
|
@@ -203,13 +601,26 @@ export class EntryIndex<T> {
|
|
|
203
601
|
return;
|
|
204
602
|
}
|
|
205
603
|
this.clearPendingIndexFlushTimer();
|
|
604
|
+
const writes: ShallowEntry[] = [];
|
|
206
605
|
for (const hash of keys) {
|
|
207
606
|
const pending = this.pendingIndexWrites.get(hash);
|
|
208
607
|
if (!pending) {
|
|
209
608
|
continue;
|
|
210
609
|
}
|
|
211
|
-
|
|
212
|
-
|
|
610
|
+
writes.push(this.materializePendingIndexWrite(hash, pending));
|
|
611
|
+
}
|
|
612
|
+
if (writes.length === 0) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (writes.length > 1 && this.properties.index.putBatch) {
|
|
616
|
+
await this.properties.index.putBatch(writes);
|
|
617
|
+
} else {
|
|
618
|
+
for (const write of writes) {
|
|
619
|
+
await this.properties.index.put(write);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const write of writes) {
|
|
623
|
+
this.pendingIndexWrites.delete(write.hash);
|
|
213
624
|
}
|
|
214
625
|
if (this.pendingIndexWrites.size > 0) {
|
|
215
626
|
this.schedulePendingIndexWriteFlush();
|
|
@@ -221,6 +632,22 @@ export class EntryIndex<T> {
|
|
|
221
632
|
resolve: R = false as R,
|
|
222
633
|
): ResultsIterator<ReturnTypeFromResolveOptions<R, T>> {
|
|
223
634
|
if (this.properties.nativeGraph?.useHeads) {
|
|
635
|
+
const shape = getResolveShape(resolve);
|
|
636
|
+
if (shape && isHeadHashOnlyShape(shape)) {
|
|
637
|
+
return this.iterateNativeProjected(
|
|
638
|
+
() =>
|
|
639
|
+
this.properties
|
|
640
|
+
.nativeGraph!.graph.heads(gid)
|
|
641
|
+
.map((hash) => ({ hash })),
|
|
642
|
+
shape,
|
|
643
|
+
) as ResultsIterator<ReturnTypeFromResolveOptions<R, T>>;
|
|
644
|
+
}
|
|
645
|
+
if (shape && isHeadDataShape(shape)) {
|
|
646
|
+
return this.iterateNativeProjected(
|
|
647
|
+
() => this.properties.nativeGraph!.graph.headDataEntries(gid),
|
|
648
|
+
shape,
|
|
649
|
+
) as ResultsIterator<ReturnTypeFromResolveOptions<R, T>>;
|
|
650
|
+
}
|
|
224
651
|
return this.iterateNativeHashes(
|
|
225
652
|
() => this.properties.nativeGraph!.graph.heads(gid),
|
|
226
653
|
resolve,
|
|
@@ -242,6 +669,146 @@ export class EntryIndex<T> {
|
|
|
242
669
|
return this.iterate(query, undefined, resolve);
|
|
243
670
|
}
|
|
244
671
|
|
|
672
|
+
getHeadsForAppend(gid?: string): SortableEntry[] | undefined {
|
|
673
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
674
|
+
return undefined;
|
|
675
|
+
}
|
|
676
|
+
return this.properties.nativeGraph.graph.headEntries(gid);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async hasHead(gid?: string): Promise<boolean | undefined> {
|
|
680
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
return this.properties.nativeGraph.graph.hasHead(gid);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async hasAnyHead(gids: Iterable<string>): Promise<boolean | undefined> {
|
|
687
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
688
|
+
return undefined;
|
|
689
|
+
}
|
|
690
|
+
const uniqueGids = new Set([...gids].filter(Boolean));
|
|
691
|
+
if (uniqueGids.size === 0) {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
return this.properties.nativeGraph.graph.hasAnyHead(uniqueGids);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async hasAnyHeadBatch(
|
|
698
|
+
gidSets: Iterable<Iterable<string>>,
|
|
699
|
+
): Promise<boolean[] | undefined> {
|
|
700
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
const normalized = [...gidSets].map(
|
|
704
|
+
(gids) => new Set([...gids].filter(Boolean)),
|
|
705
|
+
);
|
|
706
|
+
if (normalized.length === 0) {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
return this.properties.nativeGraph.graph.hasAnyHeadBatch(normalized);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async getMaxHeadDataU32(gid?: string): Promise<number | undefined> {
|
|
713
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
return this.properties.nativeGraph.graph.maxHeadDataU32(gid);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async getMaxHeadDataU32Batch(
|
|
720
|
+
gids: Iterable<string>,
|
|
721
|
+
): Promise<Array<number | undefined> | undefined> {
|
|
722
|
+
if (!this.properties.nativeGraph?.useHeads) {
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
const normalized = [...gids];
|
|
726
|
+
if (normalized.length === 0) {
|
|
727
|
+
return [];
|
|
728
|
+
}
|
|
729
|
+
return this.properties.nativeGraph.graph.maxHeadDataU32Batch?.(normalized);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
getJoinHeads(gid?: string): Promise<NativeLogJoinEntry[]> {
|
|
733
|
+
if (this.properties.nativeGraph?.useHeads) {
|
|
734
|
+
return Promise.resolve(
|
|
735
|
+
this.properties.nativeGraph.graph.joinHeadEntries(gid),
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
return this.getHeads(gid, {
|
|
739
|
+
type: "shape",
|
|
740
|
+
shape: {
|
|
741
|
+
hash: true,
|
|
742
|
+
meta: { type: true, next: true, gid: true, clock: true },
|
|
743
|
+
},
|
|
744
|
+
}).all() as Promise<NativeLogJoinEntry[]>;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
getJoinChildren(next: string): Promise<NativeLogJoinEntry[]> {
|
|
748
|
+
if (this.properties.nativeGraph) {
|
|
749
|
+
return Promise.resolve(
|
|
750
|
+
this.properties.nativeGraph.graph.childJoinEntries(next),
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
return this.getHasNext(next, {
|
|
754
|
+
type: "shape",
|
|
755
|
+
shape: {
|
|
756
|
+
hash: true,
|
|
757
|
+
meta: { type: true, next: true, gid: true, clock: true },
|
|
758
|
+
},
|
|
759
|
+
}).all() as Promise<NativeLogJoinEntry[]>;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
getUniqueReferenceGids(hash: string): string[] | undefined {
|
|
763
|
+
if (!this.properties.nativeGraph) {
|
|
764
|
+
return undefined;
|
|
765
|
+
}
|
|
766
|
+
return this.properties.nativeGraph.graph.uniqueReferenceGids(hash);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
getUniqueReferenceGidRowsBatch(
|
|
770
|
+
hashes: Iterable<string>,
|
|
771
|
+
): Array<Array<[string, string]> | undefined> | undefined {
|
|
772
|
+
if (!this.properties.nativeGraph) {
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
const normalized = [...hashes];
|
|
776
|
+
if (normalized.length === 0) {
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
779
|
+
return this.properties.nativeGraph.graph.uniqueReferenceGidRowsBatch?.(
|
|
780
|
+
normalized,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
getUniqueReferenceGidRowsFlatBatch(
|
|
785
|
+
hashes: Iterable<string>,
|
|
786
|
+
): Array<[number, string, string]> | undefined {
|
|
787
|
+
if (!this.properties.nativeGraph) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
const normalized = [...hashes];
|
|
791
|
+
if (normalized.length === 0) {
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
return this.properties.nativeGraph.graph.uniqueReferenceGidRowsFlatBatch?.(
|
|
795
|
+
normalized,
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
planDeleteRecursively(
|
|
800
|
+
from: Iterable<{ hash: string }>,
|
|
801
|
+
skipFirst = false,
|
|
802
|
+
): string[] | undefined {
|
|
803
|
+
if (!this.properties.nativeGraph) {
|
|
804
|
+
return undefined;
|
|
805
|
+
}
|
|
806
|
+
return this.properties.nativeGraph.graph.planDeleteRecursively(
|
|
807
|
+
[...from].map((entry) => entry.hash),
|
|
808
|
+
skipFirst,
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
245
812
|
getHasNext<R extends MaybeResolveOptions>(
|
|
246
813
|
next: string,
|
|
247
814
|
resolve?: R,
|
|
@@ -265,10 +832,10 @@ export class EntryIndex<T> {
|
|
|
265
832
|
next: string,
|
|
266
833
|
excludeHash: string | undefined = undefined,
|
|
267
834
|
) {
|
|
268
|
-
await this.flushPendingWrites();
|
|
269
835
|
if (this.properties.nativeGraph) {
|
|
270
836
|
return this.properties.nativeGraph.graph.countHasNext(next, excludeHash);
|
|
271
837
|
}
|
|
838
|
+
await this.flushPendingWrites();
|
|
272
839
|
const query: Query[] = [
|
|
273
840
|
new StringMatch({
|
|
274
841
|
key: ["meta", "next"],
|
|
@@ -314,7 +881,7 @@ export class EntryIndex<T> {
|
|
|
314
881
|
|
|
315
882
|
const getHashes = async () => {
|
|
316
883
|
if (!hashPromise) {
|
|
317
|
-
hashPromise =
|
|
884
|
+
hashPromise = Promise.resolve(hashes());
|
|
318
885
|
}
|
|
319
886
|
return hashPromise;
|
|
320
887
|
};
|
|
@@ -323,13 +890,10 @@ export class EntryIndex<T> {
|
|
|
323
890
|
hashes: string[],
|
|
324
891
|
): Promise<ReturnTypeFromResolveOptions<R, T>[]> => {
|
|
325
892
|
if (resolveInFull) {
|
|
326
|
-
const resolved = await
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
R,
|
|
331
|
-
T
|
|
332
|
-
>[];
|
|
893
|
+
const resolved = await this.resolveMany(hashes, resolveInFullOptions);
|
|
894
|
+
return resolved.filter(
|
|
895
|
+
(entry) => !!entry,
|
|
896
|
+
) as ReturnTypeFromResolveOptions<R, T>[];
|
|
333
897
|
}
|
|
334
898
|
|
|
335
899
|
const shallow = await Promise.all(
|
|
@@ -362,6 +926,44 @@ export class EntryIndex<T> {
|
|
|
362
926
|
};
|
|
363
927
|
}
|
|
364
928
|
|
|
929
|
+
private iterateNativeProjected<TValue>(
|
|
930
|
+
values: () => TValue[],
|
|
931
|
+
shape: Shape,
|
|
932
|
+
): ResultsIterator<unknown> {
|
|
933
|
+
let valuesPromise: Promise<TValue[]> | undefined;
|
|
934
|
+
let offset = 0;
|
|
935
|
+
let complete = false;
|
|
936
|
+
|
|
937
|
+
const getValues = async () => {
|
|
938
|
+
if (!valuesPromise) {
|
|
939
|
+
valuesPromise = Promise.resolve(values());
|
|
940
|
+
}
|
|
941
|
+
return valuesPromise;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const coerce = (values: TValue[]) =>
|
|
945
|
+
values.map((value) => projectShape(value, shape));
|
|
946
|
+
|
|
947
|
+
return {
|
|
948
|
+
close: () => undefined,
|
|
949
|
+
done: () => complete,
|
|
950
|
+
next: async (amount: number) => {
|
|
951
|
+
const all = await getValues();
|
|
952
|
+
const batch = all.slice(offset, offset + amount);
|
|
953
|
+
offset += batch.length;
|
|
954
|
+
complete = offset >= all.length;
|
|
955
|
+
return coerce(batch);
|
|
956
|
+
},
|
|
957
|
+
all: async () => {
|
|
958
|
+
const all = await getValues();
|
|
959
|
+
const remaining = all.slice(offset);
|
|
960
|
+
offset = all.length;
|
|
961
|
+
complete = true;
|
|
962
|
+
return coerce(remaining);
|
|
963
|
+
},
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
365
967
|
iterate<R extends MaybeResolveOptions>(
|
|
366
968
|
query: Query[],
|
|
367
969
|
sort = this.properties.sort.sort,
|
|
@@ -380,7 +982,9 @@ export class EntryIndex<T> {
|
|
|
380
982
|
? ({ hash: true } as const)
|
|
381
983
|
: ((options as { shape: Shape })?.shape as Shape);
|
|
382
984
|
|
|
383
|
-
let iteratorRef:
|
|
985
|
+
let iteratorRef:
|
|
986
|
+
| ReturnType<typeof this.properties.index.iterate>
|
|
987
|
+
| undefined;
|
|
384
988
|
let iteratorPromise:
|
|
385
989
|
| Promise<ReturnType<typeof this.properties.index.iterate>>
|
|
386
990
|
| undefined;
|
|
@@ -415,8 +1019,9 @@ export class EntryIndex<T> {
|
|
|
415
1019
|
}>,
|
|
416
1020
|
): Promise<ReturnTypeFromResolveOptions<R, T>[]> => {
|
|
417
1021
|
if (resolveInFull) {
|
|
418
|
-
const maybeResolved = await
|
|
419
|
-
results.map((x) =>
|
|
1022
|
+
const maybeResolved = await this.resolveMany(
|
|
1023
|
+
results.map((x) => x.value.hash),
|
|
1024
|
+
resolveInFullOptions,
|
|
420
1025
|
);
|
|
421
1026
|
return maybeResolved.filter((x) => !!x) as ReturnTypeFromResolveOptions<
|
|
422
1027
|
R,
|
|
@@ -442,22 +1047,111 @@ export class EntryIndex<T> {
|
|
|
442
1047
|
T extends boolean,
|
|
443
1048
|
R = T extends true ? Entry<any> : ShallowEntry,
|
|
444
1049
|
>(resolve?: T): Promise<R | undefined> {
|
|
1050
|
+
const nativeHash = this.getNativeTimestampOrderedHash("oldest");
|
|
1051
|
+
if (nativeHash) {
|
|
1052
|
+
const nativeResult = resolve
|
|
1053
|
+
? await this.resolve(nativeHash)
|
|
1054
|
+
: (await this.getShallow(nativeHash))?.value;
|
|
1055
|
+
if (nativeResult) {
|
|
1056
|
+
return nativeResult as R;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
445
1059
|
const iterator = this.iterate([], this.properties.sort.sort, resolve);
|
|
446
1060
|
const results = await iterator.next(1);
|
|
447
1061
|
await iterator.close();
|
|
448
1062
|
return results[0] as R;
|
|
449
1063
|
}
|
|
450
1064
|
|
|
1065
|
+
async getOldestMany<
|
|
1066
|
+
T extends boolean,
|
|
1067
|
+
R = T extends true ? Entry<any> : ShallowEntry,
|
|
1068
|
+
>(limit: number, resolve?: T): Promise<R[]> {
|
|
1069
|
+
return this.getOldestManyMaybe(limit, resolve);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
getOldestManyMaybe<
|
|
1073
|
+
T extends boolean,
|
|
1074
|
+
R = T extends true ? Entry<any> : ShallowEntry,
|
|
1075
|
+
>(limit: number, resolve?: T): MaybePromise<R[]> {
|
|
1076
|
+
if (limit <= 0) {
|
|
1077
|
+
return [];
|
|
1078
|
+
}
|
|
1079
|
+
const nativeEntries = this.getNativeOldestEntries(limit, resolve);
|
|
1080
|
+
if (nativeEntries) {
|
|
1081
|
+
return nativeEntries as R[];
|
|
1082
|
+
}
|
|
1083
|
+
return this.getOldestManyFromIterator(limit, resolve);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
private async getOldestManyFromIterator<
|
|
1087
|
+
T extends boolean,
|
|
1088
|
+
R = T extends true ? Entry<any> : ShallowEntry,
|
|
1089
|
+
>(limit: number, resolve?: T): Promise<R[]> {
|
|
1090
|
+
const iterator = this.iterate([], this.properties.sort.sort, resolve);
|
|
1091
|
+
try {
|
|
1092
|
+
return (await iterator.next(limit)) as R[];
|
|
1093
|
+
} finally {
|
|
1094
|
+
await iterator.close();
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private getNativeOldestEntries<T extends boolean>(
|
|
1099
|
+
limit: number,
|
|
1100
|
+
resolve?: T,
|
|
1101
|
+
): ShallowEntry[] | undefined {
|
|
1102
|
+
if (resolve || limit <= 0) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const graph = this.properties.nativeGraph?.graph;
|
|
1106
|
+
if (!graph?.oldestEntries) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
const direction = timestampSortDirection(this.properties.sort.sort);
|
|
1110
|
+
if (direction !== SortDirection.ASC) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
return graph
|
|
1114
|
+
.oldestEntries(limit)
|
|
1115
|
+
.map((entry) => this.nativeLogEntryToShallowEntry(entry));
|
|
1116
|
+
}
|
|
1117
|
+
|
|
451
1118
|
async getNewest<
|
|
452
1119
|
T extends boolean,
|
|
453
1120
|
R = T extends true ? Entry<any> : ShallowEntry,
|
|
454
1121
|
>(resolve?: T): Promise<R | undefined> {
|
|
1122
|
+
const nativeHash = this.getNativeTimestampOrderedHash("newest");
|
|
1123
|
+
if (nativeHash) {
|
|
1124
|
+
const nativeResult = resolve
|
|
1125
|
+
? await this.resolve(nativeHash)
|
|
1126
|
+
: (await this.getShallow(nativeHash))?.value;
|
|
1127
|
+
if (nativeResult) {
|
|
1128
|
+
return nativeResult as R;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
455
1131
|
const iterator = this.iterate([], this.sortReversed, resolve);
|
|
456
1132
|
const results = await iterator.next(1);
|
|
457
1133
|
await iterator.close();
|
|
458
1134
|
return results[0] as R;
|
|
459
1135
|
}
|
|
460
1136
|
|
|
1137
|
+
private getNativeTimestampOrderedHash(
|
|
1138
|
+
position: "oldest" | "newest",
|
|
1139
|
+
): string | undefined {
|
|
1140
|
+
const graph = this.properties.nativeGraph?.graph;
|
|
1141
|
+
if (!graph?.oldestHash || !graph.newestHash) {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const direction = timestampSortDirection(this.properties.sort.sort);
|
|
1145
|
+
if (direction == null) {
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const useNewest =
|
|
1149
|
+
direction === SortDirection.ASC
|
|
1150
|
+
? position === "newest"
|
|
1151
|
+
: position === "oldest";
|
|
1152
|
+
return useNewest ? graph.newestHash() : graph.oldestHash();
|
|
1153
|
+
}
|
|
1154
|
+
|
|
461
1155
|
async getBefore<
|
|
462
1156
|
T extends boolean,
|
|
463
1157
|
R = T extends true ? Entry<any> : ShallowEntry,
|
|
@@ -490,18 +1184,163 @@ export class EntryIndex<T> {
|
|
|
490
1184
|
return this.resolve(k, options);
|
|
491
1185
|
}
|
|
492
1186
|
|
|
1187
|
+
async getMany(k: string[], options?: ResolveFullyOptions) {
|
|
1188
|
+
return this.resolveMany(k, options);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
async planJoin(
|
|
1192
|
+
entry: Pick<Entry<T>, "hash" | "meta">,
|
|
1193
|
+
reset?: boolean,
|
|
1194
|
+
): Promise<JoinPlan> {
|
|
1195
|
+
if (this.properties.nativeGraph) {
|
|
1196
|
+
const cutCheck = this.properties.nativeGraph.useHeads
|
|
1197
|
+
? {
|
|
1198
|
+
gid: entry.meta.gid,
|
|
1199
|
+
wallTime: entry.meta.clock.timestamp.wallTime,
|
|
1200
|
+
logical: entry.meta.clock.timestamp.logical,
|
|
1201
|
+
}
|
|
1202
|
+
: undefined;
|
|
1203
|
+
return this.properties.nativeGraph.graph.planJoin(
|
|
1204
|
+
entry.hash,
|
|
1205
|
+
entry.meta.next,
|
|
1206
|
+
entry.meta.type,
|
|
1207
|
+
reset === true,
|
|
1208
|
+
cutCheck,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
if (!reset && (await this.getShallow(entry.hash)) != null) {
|
|
1212
|
+
return {
|
|
1213
|
+
skip: true,
|
|
1214
|
+
missingParents: [],
|
|
1215
|
+
cutChecked: false,
|
|
1216
|
+
coveredByCut: false,
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
if (entry.meta.type === EntryType.CUT) {
|
|
1220
|
+
return {
|
|
1221
|
+
skip: false,
|
|
1222
|
+
missingParents: [],
|
|
1223
|
+
cutChecked: false,
|
|
1224
|
+
coveredByCut: false,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const missingParents: string[] = [];
|
|
1229
|
+
for (const next of entry.meta.next) {
|
|
1230
|
+
if (reset || (await this.getShallow(next)) == null) {
|
|
1231
|
+
missingParents.push(next);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
skip: false,
|
|
1236
|
+
missingParents,
|
|
1237
|
+
cutChecked: false,
|
|
1238
|
+
coveredByCut: false,
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
async planJoinBatch(
|
|
1243
|
+
entries: Array<Pick<Entry<T>, "hash" | "meta">>,
|
|
1244
|
+
reset?: boolean,
|
|
1245
|
+
profile?: EntryIndexProfileSink,
|
|
1246
|
+
): Promise<JoinPlan[]> {
|
|
1247
|
+
if (entries.length === 0) {
|
|
1248
|
+
return [];
|
|
1249
|
+
}
|
|
1250
|
+
if (this.properties.nativeGraph?.graph.planJoinBatch) {
|
|
1251
|
+
const prepareStartedAt = entryIndexProfileStart(profile);
|
|
1252
|
+
const nativeInputs = entries.map((entry) => ({
|
|
1253
|
+
hash: entry.hash,
|
|
1254
|
+
next: entry.meta.next,
|
|
1255
|
+
type: entry.meta.type,
|
|
1256
|
+
cutCheck: this.properties.nativeGraph!.useHeads
|
|
1257
|
+
? {
|
|
1258
|
+
gid: entry.meta.gid,
|
|
1259
|
+
wallTime: entry.meta.clock.timestamp.wallTime,
|
|
1260
|
+
logical: entry.meta.clock.timestamp.logical,
|
|
1261
|
+
}
|
|
1262
|
+
: undefined,
|
|
1263
|
+
}));
|
|
1264
|
+
emitEntryIndexProfileDuration(profile, prepareStartedAt, {
|
|
1265
|
+
name: "log.entryIndex.planJoinBatch.prepareNative",
|
|
1266
|
+
component: "log",
|
|
1267
|
+
entries: entries.length,
|
|
1268
|
+
messages: 1,
|
|
1269
|
+
details: {
|
|
1270
|
+
cutChecks: nativeInputs.reduce(
|
|
1271
|
+
(sum, input) => sum + (input.cutCheck ? 1 : 0),
|
|
1272
|
+
0,
|
|
1273
|
+
),
|
|
1274
|
+
},
|
|
1275
|
+
});
|
|
1276
|
+
const nativeStartedAt = entryIndexProfileStart(profile);
|
|
1277
|
+
const plans = this.properties.nativeGraph.graph.planJoinBatch(
|
|
1278
|
+
nativeInputs,
|
|
1279
|
+
reset === true,
|
|
1280
|
+
);
|
|
1281
|
+
emitEntryIndexProfileDuration(profile, nativeStartedAt, {
|
|
1282
|
+
name: "log.entryIndex.planJoinBatch.nativeGraph",
|
|
1283
|
+
component: "log",
|
|
1284
|
+
entries: entries.length,
|
|
1285
|
+
messages: 1,
|
|
1286
|
+
});
|
|
1287
|
+
return plans;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const fallbackStartedAt = entryIndexProfileStart(profile);
|
|
1291
|
+
const plans: JoinPlan[] = [];
|
|
1292
|
+
for (const entry of entries) {
|
|
1293
|
+
plans.push(await this.planJoin(entry, reset));
|
|
1294
|
+
}
|
|
1295
|
+
emitEntryIndexProfileDuration(profile, fallbackStartedAt, {
|
|
1296
|
+
name: "log.entryIndex.planJoinBatch.fallback",
|
|
1297
|
+
component: "log",
|
|
1298
|
+
entries: entries.length,
|
|
1299
|
+
messages: 1,
|
|
1300
|
+
});
|
|
1301
|
+
return plans;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
493
1304
|
async getShallow(k: string) {
|
|
494
|
-
const pending = this.
|
|
1305
|
+
const pending = this.getPendingIndexWrite(k);
|
|
495
1306
|
if (pending) {
|
|
496
1307
|
return { id: toId(k), value: pending };
|
|
497
1308
|
}
|
|
498
1309
|
return this.properties.index.get(toId(k));
|
|
499
1310
|
}
|
|
500
1311
|
|
|
1312
|
+
getNativeEntryMetadataBatch(
|
|
1313
|
+
hashes: Iterable<string>,
|
|
1314
|
+
): Array<NativeLogEntryMetadata | undefined> | undefined {
|
|
1315
|
+
if (!this.properties.nativeGraph) {
|
|
1316
|
+
return undefined;
|
|
1317
|
+
}
|
|
1318
|
+
const normalized = [...hashes];
|
|
1319
|
+
if (normalized.length === 0) {
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
return this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
getNativeEntryMetadataHintsBatch(
|
|
1326
|
+
hashes: Iterable<string>,
|
|
1327
|
+
): Array<NativeLogEntryMetadata | undefined> | undefined {
|
|
1328
|
+
if (!this.properties.nativeGraph) {
|
|
1329
|
+
return undefined;
|
|
1330
|
+
}
|
|
1331
|
+
const normalized = [...hashes];
|
|
1332
|
+
if (normalized.length === 0) {
|
|
1333
|
+
return [];
|
|
1334
|
+
}
|
|
1335
|
+
return (
|
|
1336
|
+
this.properties.nativeGraph.graph.entryMetadataHintsBatch?.(normalized) ??
|
|
1337
|
+
this.properties.nativeGraph.graph.entryMetadataBatch?.(normalized)
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
501
1341
|
async has(k: string) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return true;
|
|
1342
|
+
if (this.properties.nativeGraph) {
|
|
1343
|
+
return this.properties.nativeGraph.graph.has(k);
|
|
505
1344
|
}
|
|
506
1345
|
if (this.pendingIndexWrites.has(k)) {
|
|
507
1346
|
return true;
|
|
@@ -513,11 +1352,16 @@ export class EntryIndex<T> {
|
|
|
513
1352
|
}
|
|
514
1353
|
|
|
515
1354
|
async hasMany(hashes: Iterable<string>) {
|
|
1355
|
+
if (this.properties.nativeGraph) {
|
|
1356
|
+
return this.properties.nativeGraph.graph.hasMany(
|
|
1357
|
+
new Set([...hashes].filter(Boolean)),
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
516
1360
|
const batchSize = 64;
|
|
517
1361
|
const existing = new Set<string>();
|
|
518
1362
|
const missing: string[] = [];
|
|
519
1363
|
for (const hash of new Set([...hashes].filter(Boolean))) {
|
|
520
|
-
if (this.
|
|
1364
|
+
if (this.pendingIndexWrites.has(hash)) {
|
|
521
1365
|
existing.add(hash);
|
|
522
1366
|
continue;
|
|
523
1367
|
}
|
|
@@ -576,9 +1420,19 @@ export class EntryIndex<T> {
|
|
|
576
1420
|
) {
|
|
577
1421
|
if (properties.toMultiHash) {
|
|
578
1422
|
const existingHash = entry.hash;
|
|
579
|
-
|
|
1423
|
+
const preparedBlock = Entry.takePreparedBlock(entry);
|
|
580
1424
|
try {
|
|
581
|
-
|
|
1425
|
+
let hash: string;
|
|
1426
|
+
if (preparedBlock) {
|
|
1427
|
+
hash = await putPreparedEntryBlock(
|
|
1428
|
+
this.properties.store,
|
|
1429
|
+
preparedBlock,
|
|
1430
|
+
);
|
|
1431
|
+
entry.size = preparedBlock.block.bytes.length;
|
|
1432
|
+
} else {
|
|
1433
|
+
entry.hash = undefined as any;
|
|
1434
|
+
hash = await Entry.toMultihash(this.properties.store, entry);
|
|
1435
|
+
}
|
|
582
1436
|
entry.hash = existingHash;
|
|
583
1437
|
if (entry.hash === undefined) {
|
|
584
1438
|
entry.hash = hash; // can happen if you sync entries that you load directly from ipfs
|
|
@@ -607,7 +1461,12 @@ export class EntryIndex<T> {
|
|
|
607
1461
|
|
|
608
1462
|
// add cache after .has check before .has uses the cache
|
|
609
1463
|
this.cache.add(entry.hash, entry);
|
|
610
|
-
const shallowEntry =
|
|
1464
|
+
const shallowEntry =
|
|
1465
|
+
Entry.takePreparedShallowEntry(entry, properties.isHead) ??
|
|
1466
|
+
entry.toShallow(properties.isHead);
|
|
1467
|
+
const nativeEntry =
|
|
1468
|
+
Entry.takePreparedNativeLogEntry(entry, properties.isHead) ??
|
|
1469
|
+
toNativeLogEntry(shallowEntry);
|
|
611
1470
|
const shouldDeferIndexWrite =
|
|
612
1471
|
properties.deferIndexWrite === true &&
|
|
613
1472
|
properties.isHead &&
|
|
@@ -621,24 +1480,10 @@ export class EntryIndex<T> {
|
|
|
621
1480
|
await this.flushPendingWrites(entry.meta.next);
|
|
622
1481
|
await this.properties.index.put(shallowEntry);
|
|
623
1482
|
}
|
|
624
|
-
this.properties.nativeGraph?.graph.put(
|
|
1483
|
+
this.properties.nativeGraph?.graph.put(nativeEntry);
|
|
625
1484
|
|
|
626
1485
|
// check if gids has been shadowed, by query all nexts that have a different gid
|
|
627
|
-
|
|
628
|
-
const shadowedGids: Set<string> = this.properties.nativeGraph
|
|
629
|
-
? new Set<string>(
|
|
630
|
-
this.properties.nativeGraph.graph.shadowedGids(
|
|
631
|
-
entry.meta.gid,
|
|
632
|
-
entry.meta.next,
|
|
633
|
-
entry.hash,
|
|
634
|
-
),
|
|
635
|
-
)
|
|
636
|
-
: await this.findShadowedGids(entry);
|
|
637
|
-
|
|
638
|
-
if (shadowedGids.size > 0) {
|
|
639
|
-
this.properties.onGidRemoved?.([...shadowedGids]);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
1486
|
+
await this.notifyShadowedGids(entry);
|
|
642
1487
|
|
|
643
1488
|
// mark all next entries as not heads
|
|
644
1489
|
await this.privateUpdateNextHeadProperty(entry, false);
|
|
@@ -651,6 +1496,679 @@ export class EntryIndex<T> {
|
|
|
651
1496
|
}
|
|
652
1497
|
}
|
|
653
1498
|
|
|
1499
|
+
async putAppendBatch(
|
|
1500
|
+
entries: Entry<any>[],
|
|
1501
|
+
properties: {
|
|
1502
|
+
unique: boolean;
|
|
1503
|
+
externalNextHashes?: string[];
|
|
1504
|
+
prepared?: {
|
|
1505
|
+
shallowEntries: ShallowEntry[];
|
|
1506
|
+
nativeEntries?: NativeLogEntry[];
|
|
1507
|
+
nativeGraphUpdated?: boolean;
|
|
1508
|
+
nativeBlocksCommitted?: boolean;
|
|
1509
|
+
};
|
|
1510
|
+
heads?: boolean[];
|
|
1511
|
+
deferIndexWrite?: boolean;
|
|
1512
|
+
profile?: EntryIndexProfileSink;
|
|
1513
|
+
},
|
|
1514
|
+
) {
|
|
1515
|
+
if (entries.length === 0) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
if (
|
|
1519
|
+
entries.length === 1 &&
|
|
1520
|
+
properties.prepared?.nativeGraphUpdated !== true
|
|
1521
|
+
) {
|
|
1522
|
+
return this.put(entries[0], {
|
|
1523
|
+
unique: properties.unique,
|
|
1524
|
+
isHead: true,
|
|
1525
|
+
toMultiHash: false,
|
|
1526
|
+
deferIndexWrite: properties.deferIndexWrite,
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
for (const entry of entries) {
|
|
1531
|
+
if (!entry.hash) {
|
|
1532
|
+
throw new Error("Missing hash");
|
|
1533
|
+
}
|
|
1534
|
+
const existingPromise = this.insertionPromises.get(entry.hash);
|
|
1535
|
+
if (existingPromise) {
|
|
1536
|
+
await existingPromise;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const promise = (async () => {
|
|
1541
|
+
const profile = properties.profile;
|
|
1542
|
+
const prepareStartedAt = entryIndexProfileStart(profile);
|
|
1543
|
+
const externalNexts = new Set<string>(
|
|
1544
|
+
properties.externalNextHashes ?? [],
|
|
1545
|
+
);
|
|
1546
|
+
const shouldDiscoverExternalNexts = !properties.externalNextHashes;
|
|
1547
|
+
const batchHashes = shouldDiscoverExternalNexts
|
|
1548
|
+
? new Set(entries.map((entry) => entry.hash))
|
|
1549
|
+
: undefined;
|
|
1550
|
+
const putBatch =
|
|
1551
|
+
!this.properties.onGidRemoved && this.properties.index.putBatch;
|
|
1552
|
+
const shallowEntries: ShallowEntry[] = [];
|
|
1553
|
+
const nativeGraphUpdated =
|
|
1554
|
+
properties.prepared?.nativeGraphUpdated === true;
|
|
1555
|
+
const nativeCommitOwnsHotIndex =
|
|
1556
|
+
nativeGraphUpdated &&
|
|
1557
|
+
properties.prepared?.nativeBlocksCommitted === true &&
|
|
1558
|
+
!this.properties.onGidRemoved;
|
|
1559
|
+
const nativeGraphPutAppendChain =
|
|
1560
|
+
!nativeGraphUpdated &&
|
|
1561
|
+
!this.properties.onGidRemoved &&
|
|
1562
|
+
properties.externalNextHashes &&
|
|
1563
|
+
this.properties.nativeGraph?.graph.putAppendChain
|
|
1564
|
+
? this.properties.nativeGraph.graph.putAppendChain.bind(
|
|
1565
|
+
this.properties.nativeGraph.graph,
|
|
1566
|
+
)
|
|
1567
|
+
: undefined;
|
|
1568
|
+
const nativeGraphPutBatch =
|
|
1569
|
+
!nativeGraphUpdated &&
|
|
1570
|
+
!this.properties.onGidRemoved &&
|
|
1571
|
+
this.properties.nativeGraph?.graph.putBatch
|
|
1572
|
+
? this.properties.nativeGraph.graph.putBatch.bind(
|
|
1573
|
+
this.properties.nativeGraph.graph,
|
|
1574
|
+
)
|
|
1575
|
+
: undefined;
|
|
1576
|
+
const deferBatchIndexWrite =
|
|
1577
|
+
properties.deferIndexWrite === true &&
|
|
1578
|
+
!!putBatch &&
|
|
1579
|
+
!!this.properties.nativeGraph &&
|
|
1580
|
+
!this.properties.onGidRemoved &&
|
|
1581
|
+
entries.every((entry) => entry.meta.type !== EntryType.CUT);
|
|
1582
|
+
const nativeEntries: NativeLogEntry[] = [];
|
|
1583
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1584
|
+
const entry = entries[i];
|
|
1585
|
+
const isHead = properties.heads?.[i] ?? i === entries.length - 1;
|
|
1586
|
+
if (properties.unique === true || !(await this.has(entry.hash))) {
|
|
1587
|
+
this._length++;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
this.cache.add(entry.hash, entry);
|
|
1591
|
+
const preparedShallowEntry = properties.prepared?.shallowEntries[i];
|
|
1592
|
+
if (preparedShallowEntry) {
|
|
1593
|
+
preparedShallowEntry.head = isHead;
|
|
1594
|
+
}
|
|
1595
|
+
const shallowEntry =
|
|
1596
|
+
preparedShallowEntry ??
|
|
1597
|
+
Entry.takePreparedShallowEntry(entry, isHead) ??
|
|
1598
|
+
entry.toShallow(isHead);
|
|
1599
|
+
if (nativeCommitOwnsHotIndex) {
|
|
1600
|
+
this.pendingIndexWrites.set(entry.hash, shallowEntry);
|
|
1601
|
+
if (batchHashes) {
|
|
1602
|
+
for (const next of entry.meta.next) {
|
|
1603
|
+
if (!batchHashes.has(next)) {
|
|
1604
|
+
externalNexts.add(next);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
const preparedNativeEntry = properties.prepared?.nativeEntries?.[i];
|
|
1611
|
+
if (preparedNativeEntry) {
|
|
1612
|
+
preparedNativeEntry.head = isHead;
|
|
1613
|
+
}
|
|
1614
|
+
const nativeEntry =
|
|
1615
|
+
!nativeGraphUpdated &&
|
|
1616
|
+
this.properties.nativeGraph &&
|
|
1617
|
+
(preparedNativeEntry ??
|
|
1618
|
+
Entry.takePreparedNativeLogEntry(entry, isHead) ??
|
|
1619
|
+
toNativeLogEntry(shallowEntry));
|
|
1620
|
+
if (putBatch) {
|
|
1621
|
+
shallowEntries.push(shallowEntry);
|
|
1622
|
+
nativeEntry && nativeEntries.push(nativeEntry);
|
|
1623
|
+
} else {
|
|
1624
|
+
await this.properties.index.put(shallowEntry);
|
|
1625
|
+
if (nativeGraphPutAppendChain || nativeGraphPutBatch) {
|
|
1626
|
+
nativeEntry && nativeEntries.push(nativeEntry);
|
|
1627
|
+
} else {
|
|
1628
|
+
if (nativeEntry) {
|
|
1629
|
+
this.properties.nativeGraph?.graph.put(nativeEntry);
|
|
1630
|
+
await this.notifyShadowedGids(entry);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
if (batchHashes) {
|
|
1636
|
+
for (const next of entry.meta.next) {
|
|
1637
|
+
if (!batchHashes.has(next)) {
|
|
1638
|
+
externalNexts.add(next);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
emitEntryIndexProfileDuration(profile, prepareStartedAt, {
|
|
1644
|
+
name: "log.entryIndex.putAppendBatch.prepare",
|
|
1645
|
+
component: "log",
|
|
1646
|
+
entries: entries.length,
|
|
1647
|
+
messages: 1,
|
|
1648
|
+
details: {
|
|
1649
|
+
unique: properties.unique,
|
|
1650
|
+
usedPreparedShallowEntries:
|
|
1651
|
+
properties.prepared?.shallowEntries.length ?? 0,
|
|
1652
|
+
usedPreparedNativeEntries:
|
|
1653
|
+
properties.prepared?.nativeEntries?.length ?? 0,
|
|
1654
|
+
nativeEntries: nativeEntries.length,
|
|
1655
|
+
discoverExternalNexts: shouldDiscoverExternalNexts,
|
|
1656
|
+
},
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
const putNativeEntries = (allowLoopFallback: boolean) => {
|
|
1660
|
+
if (nativeEntries.length === 0) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (nativeGraphPutAppendChain) {
|
|
1664
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1665
|
+
nativeGraphPutAppendChain(nativeEntries);
|
|
1666
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1667
|
+
name: "log.entryIndex.putAppendBatch.nativeGraphPut",
|
|
1668
|
+
component: "log",
|
|
1669
|
+
entries: nativeEntries.length,
|
|
1670
|
+
messages: 1,
|
|
1671
|
+
details: { method: "putAppendChain" },
|
|
1672
|
+
});
|
|
1673
|
+
} else if (nativeGraphPutBatch) {
|
|
1674
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1675
|
+
nativeGraphPutBatch(nativeEntries);
|
|
1676
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1677
|
+
name: "log.entryIndex.putAppendBatch.nativeGraphPut",
|
|
1678
|
+
component: "log",
|
|
1679
|
+
entries: nativeEntries.length,
|
|
1680
|
+
messages: 1,
|
|
1681
|
+
details: { method: "putBatch" },
|
|
1682
|
+
});
|
|
1683
|
+
} else if (allowLoopFallback) {
|
|
1684
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1685
|
+
for (const nativeEntry of nativeEntries) {
|
|
1686
|
+
this.properties.nativeGraph?.graph.put(nativeEntry);
|
|
1687
|
+
}
|
|
1688
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1689
|
+
name: "log.entryIndex.putAppendBatch.nativeGraphPut",
|
|
1690
|
+
component: "log",
|
|
1691
|
+
entries: nativeEntries.length,
|
|
1692
|
+
messages: 1,
|
|
1693
|
+
details: { method: "putLoop" },
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
|
|
1698
|
+
if (nativeCommitOwnsHotIndex) {
|
|
1699
|
+
this.schedulePendingIndexWriteFlush();
|
|
1700
|
+
} else if (deferBatchIndexWrite) {
|
|
1701
|
+
const indexPutStartedAt = entryIndexProfileStart(profile);
|
|
1702
|
+
for (const shallowEntry of shallowEntries) {
|
|
1703
|
+
this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
|
|
1704
|
+
}
|
|
1705
|
+
this.schedulePendingIndexWriteFlush();
|
|
1706
|
+
emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
|
|
1707
|
+
name: "log.entryIndex.putAppendBatch.indexPut",
|
|
1708
|
+
component: "log",
|
|
1709
|
+
entries: shallowEntries.length,
|
|
1710
|
+
messages: 1,
|
|
1711
|
+
details: { deferred: true },
|
|
1712
|
+
});
|
|
1713
|
+
putNativeEntries(true);
|
|
1714
|
+
} else if (putBatch) {
|
|
1715
|
+
const indexPutStartedAt = entryIndexProfileStart(profile);
|
|
1716
|
+
await putBatch.call(this.properties.index, shallowEntries);
|
|
1717
|
+
emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
|
|
1718
|
+
name: "log.entryIndex.putAppendBatch.indexPut",
|
|
1719
|
+
component: "log",
|
|
1720
|
+
entries: shallowEntries.length,
|
|
1721
|
+
messages: 1,
|
|
1722
|
+
});
|
|
1723
|
+
putNativeEntries(true);
|
|
1724
|
+
} else if (nativeEntries.length > 0) {
|
|
1725
|
+
putNativeEntries(false);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
if (externalNexts.size > 0) {
|
|
1729
|
+
const externalNextStartedAt = entryIndexProfileStart(profile);
|
|
1730
|
+
await this.privateUpdateNextHeadHashes([...externalNexts], false);
|
|
1731
|
+
emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
|
|
1732
|
+
name: "log.entryIndex.putAppendBatch.externalNexts",
|
|
1733
|
+
component: "log",
|
|
1734
|
+
entries: externalNexts.size,
|
|
1735
|
+
messages: 1,
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
})().finally(() => {
|
|
1739
|
+
for (const entry of entries) {
|
|
1740
|
+
this.insertionPromises.delete(entry.hash);
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
for (const entry of entries) {
|
|
1745
|
+
this.insertionPromises.set(entry.hash, promise);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
return promise;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// Internal trusted receive path for callers that can supply prepared append facts.
|
|
1752
|
+
async putAppendFactsBatch(
|
|
1753
|
+
entries: PreparedAppendIndexFacts[],
|
|
1754
|
+
properties: {
|
|
1755
|
+
unique: boolean;
|
|
1756
|
+
externalNextHashes?: string[];
|
|
1757
|
+
heads?: boolean[];
|
|
1758
|
+
deferIndexWrite?: boolean;
|
|
1759
|
+
nativeGraphUpdated?: boolean;
|
|
1760
|
+
profile?: EntryIndexProfileSink;
|
|
1761
|
+
},
|
|
1762
|
+
) {
|
|
1763
|
+
if (entries.length === 0) {
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (this.properties.onGidRemoved) {
|
|
1767
|
+
throw new Error(
|
|
1768
|
+
"Prepared append facts batch requires no onGidRemoved hook",
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
for (const entry of entries) {
|
|
1773
|
+
if (!entry.hash) {
|
|
1774
|
+
throw new Error("Missing hash");
|
|
1775
|
+
}
|
|
1776
|
+
const existingPromise = this.insertionPromises.get(entry.hash);
|
|
1777
|
+
if (existingPromise) {
|
|
1778
|
+
await existingPromise;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
const promise = (async () => {
|
|
1783
|
+
const profile = properties.profile;
|
|
1784
|
+
const prepareStartedAt = entryIndexProfileStart(profile);
|
|
1785
|
+
const externalNexts = new Set<string>(
|
|
1786
|
+
properties.externalNextHashes ?? [],
|
|
1787
|
+
);
|
|
1788
|
+
const shouldDiscoverExternalNexts = !properties.externalNextHashes;
|
|
1789
|
+
const batchHashes = shouldDiscoverExternalNexts
|
|
1790
|
+
? new Set(entries.map((entry) => entry.hash))
|
|
1791
|
+
: undefined;
|
|
1792
|
+
const putBatch = this.properties.index.putBatch;
|
|
1793
|
+
const shallowEntries: ShallowEntry[] = [];
|
|
1794
|
+
const nativeEntries: NativeLogEntry[] = [];
|
|
1795
|
+
const nativeGraphUpdated = properties.nativeGraphUpdated === true;
|
|
1796
|
+
const nativeGraphPutAppendChain =
|
|
1797
|
+
!nativeGraphUpdated &&
|
|
1798
|
+
properties.externalNextHashes &&
|
|
1799
|
+
this.properties.nativeGraph?.graph.putAppendChain
|
|
1800
|
+
? this.properties.nativeGraph.graph.putAppendChain.bind(
|
|
1801
|
+
this.properties.nativeGraph.graph,
|
|
1802
|
+
)
|
|
1803
|
+
: undefined;
|
|
1804
|
+
const nativeGraphPutBatch =
|
|
1805
|
+
!nativeGraphUpdated && this.properties.nativeGraph?.graph.putBatch
|
|
1806
|
+
? this.properties.nativeGraph.graph.putBatch.bind(
|
|
1807
|
+
this.properties.nativeGraph.graph,
|
|
1808
|
+
)
|
|
1809
|
+
: undefined;
|
|
1810
|
+
const deferBatchIndexWrite =
|
|
1811
|
+
properties.deferIndexWrite === true &&
|
|
1812
|
+
!!this.properties.nativeGraph &&
|
|
1813
|
+
entries.every((entry) => entry.meta.type !== EntryType.CUT);
|
|
1814
|
+
const lazyNativeGraphUpdatedDeferredIndexWrite =
|
|
1815
|
+
nativeGraphUpdated && deferBatchIndexWrite;
|
|
1816
|
+
|
|
1817
|
+
for (let i = 0; i < entries.length; i++) {
|
|
1818
|
+
const entry = entries[i]!;
|
|
1819
|
+
const isHead = properties.heads?.[i] ?? i === entries.length - 1;
|
|
1820
|
+
if (properties.unique === true || !(await this.has(entry.hash))) {
|
|
1821
|
+
this._length++;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
let shallowEntry: ShallowEntry | undefined;
|
|
1825
|
+
if (lazyNativeGraphUpdatedDeferredIndexWrite) {
|
|
1826
|
+
this.pendingIndexWrites.set(entry.hash, () =>
|
|
1827
|
+
materializePreparedAppendShallowEntry(entry, isHead),
|
|
1828
|
+
);
|
|
1829
|
+
} else {
|
|
1830
|
+
shallowEntry = materializePreparedAppendShallowEntry(entry, isHead);
|
|
1831
|
+
}
|
|
1832
|
+
const nativeEntry =
|
|
1833
|
+
!nativeGraphUpdated &&
|
|
1834
|
+
this.properties.nativeGraph &&
|
|
1835
|
+
(entry.nativeEntry ??
|
|
1836
|
+
toNativeLogEntry(
|
|
1837
|
+
shallowEntry ??
|
|
1838
|
+
materializePreparedAppendShallowEntry(entry, isHead),
|
|
1839
|
+
));
|
|
1840
|
+
if (nativeEntry) {
|
|
1841
|
+
nativeEntry.head = isHead;
|
|
1842
|
+
}
|
|
1843
|
+
if (lazyNativeGraphUpdatedDeferredIndexWrite) {
|
|
1844
|
+
// The native commit already updated blocks/graph; keep the JS
|
|
1845
|
+
// compatibility index write lazy for later flushes.
|
|
1846
|
+
} else if (deferBatchIndexWrite || putBatch) {
|
|
1847
|
+
shallowEntries.push(shallowEntry!);
|
|
1848
|
+
nativeEntry && nativeEntries.push(nativeEntry);
|
|
1849
|
+
} else {
|
|
1850
|
+
await this.properties.index.put(shallowEntry!);
|
|
1851
|
+
nativeEntry && nativeEntries.push(nativeEntry);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
if (batchHashes) {
|
|
1855
|
+
for (const next of entry.meta.next) {
|
|
1856
|
+
if (!batchHashes.has(next)) {
|
|
1857
|
+
externalNexts.add(next);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
emitEntryIndexProfileDuration(profile, prepareStartedAt, {
|
|
1863
|
+
name: "log.entryIndex.putAppendFactsBatch.prepare",
|
|
1864
|
+
component: "log",
|
|
1865
|
+
entries: entries.length,
|
|
1866
|
+
messages: 1,
|
|
1867
|
+
details: {
|
|
1868
|
+
unique: properties.unique,
|
|
1869
|
+
nativeEntries: nativeEntries.length,
|
|
1870
|
+
discoverExternalNexts: shouldDiscoverExternalNexts,
|
|
1871
|
+
nativeGraphUpdated,
|
|
1872
|
+
},
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
const putNativeEntries = (allowLoopFallback: boolean) => {
|
|
1876
|
+
if (nativeEntries.length === 0) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (nativeGraphPutAppendChain) {
|
|
1880
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1881
|
+
nativeGraphPutAppendChain(nativeEntries);
|
|
1882
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1883
|
+
name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
|
|
1884
|
+
component: "log",
|
|
1885
|
+
entries: nativeEntries.length,
|
|
1886
|
+
messages: 1,
|
|
1887
|
+
details: { method: "putAppendChain" },
|
|
1888
|
+
});
|
|
1889
|
+
} else if (nativeGraphPutBatch) {
|
|
1890
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1891
|
+
nativeGraphPutBatch(nativeEntries);
|
|
1892
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1893
|
+
name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
|
|
1894
|
+
component: "log",
|
|
1895
|
+
entries: nativeEntries.length,
|
|
1896
|
+
messages: 1,
|
|
1897
|
+
details: { method: "putBatch" },
|
|
1898
|
+
});
|
|
1899
|
+
} else if (allowLoopFallback) {
|
|
1900
|
+
const nativePutStartedAt = entryIndexProfileStart(profile);
|
|
1901
|
+
for (const nativeEntry of nativeEntries) {
|
|
1902
|
+
this.properties.nativeGraph?.graph.put(nativeEntry);
|
|
1903
|
+
}
|
|
1904
|
+
emitEntryIndexProfileDuration(profile, nativePutStartedAt, {
|
|
1905
|
+
name: "log.entryIndex.putAppendFactsBatch.nativeGraphPut",
|
|
1906
|
+
component: "log",
|
|
1907
|
+
entries: nativeEntries.length,
|
|
1908
|
+
messages: 1,
|
|
1909
|
+
details: { method: "putLoop" },
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1914
|
+
if (deferBatchIndexWrite) {
|
|
1915
|
+
const indexPutStartedAt = entryIndexProfileStart(profile);
|
|
1916
|
+
if (!lazyNativeGraphUpdatedDeferredIndexWrite) {
|
|
1917
|
+
for (const shallowEntry of shallowEntries) {
|
|
1918
|
+
this.pendingIndexWrites.set(shallowEntry.hash, shallowEntry);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
this.schedulePendingIndexWriteFlush();
|
|
1922
|
+
emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
|
|
1923
|
+
name: "log.entryIndex.putAppendFactsBatch.indexPut",
|
|
1924
|
+
component: "log",
|
|
1925
|
+
entries: lazyNativeGraphUpdatedDeferredIndexWrite
|
|
1926
|
+
? entries.length
|
|
1927
|
+
: shallowEntries.length,
|
|
1928
|
+
messages: 1,
|
|
1929
|
+
details: {
|
|
1930
|
+
deferred: true,
|
|
1931
|
+
lazy: lazyNativeGraphUpdatedDeferredIndexWrite,
|
|
1932
|
+
},
|
|
1933
|
+
});
|
|
1934
|
+
putNativeEntries(true);
|
|
1935
|
+
} else if (putBatch) {
|
|
1936
|
+
const indexPutStartedAt = entryIndexProfileStart(profile);
|
|
1937
|
+
await putBatch.call(this.properties.index, shallowEntries);
|
|
1938
|
+
emitEntryIndexProfileDuration(profile, indexPutStartedAt, {
|
|
1939
|
+
name: "log.entryIndex.putAppendFactsBatch.indexPut",
|
|
1940
|
+
component: "log",
|
|
1941
|
+
entries: shallowEntries.length,
|
|
1942
|
+
messages: 1,
|
|
1943
|
+
});
|
|
1944
|
+
putNativeEntries(true);
|
|
1945
|
+
} else if (nativeEntries.length > 0) {
|
|
1946
|
+
putNativeEntries(true);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
if (externalNexts.size > 0) {
|
|
1950
|
+
const externalNextStartedAt = entryIndexProfileStart(profile);
|
|
1951
|
+
await this.privateUpdateNextHeadHashes([...externalNexts], false);
|
|
1952
|
+
emitEntryIndexProfileDuration(profile, externalNextStartedAt, {
|
|
1953
|
+
name: "log.entryIndex.putAppendFactsBatch.externalNexts",
|
|
1954
|
+
component: "log",
|
|
1955
|
+
entries: externalNexts.size,
|
|
1956
|
+
messages: 1,
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
})().finally(() => {
|
|
1960
|
+
for (const entry of entries) {
|
|
1961
|
+
this.insertionPromises.delete(entry.hash);
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
for (const entry of entries) {
|
|
1966
|
+
this.insertionPromises.set(entry.hash, promise);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
return promise;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
/** @internal */
|
|
1973
|
+
async putNativeCommittedAppend(
|
|
1974
|
+
entry: Entry<any>,
|
|
1975
|
+
properties: {
|
|
1976
|
+
unique: boolean;
|
|
1977
|
+
externalNextHashes: string[];
|
|
1978
|
+
shallowEntry?: ShallowEntry;
|
|
1979
|
+
isHead?: boolean;
|
|
1980
|
+
},
|
|
1981
|
+
) {
|
|
1982
|
+
if (!entry.hash) {
|
|
1983
|
+
throw new Error("Missing hash");
|
|
1984
|
+
}
|
|
1985
|
+
const existingPromise = this.insertionPromises.get(entry.hash);
|
|
1986
|
+
if (existingPromise) {
|
|
1987
|
+
await existingPromise;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
const promise = (async () => {
|
|
1991
|
+
const isHead = properties.isHead ?? true;
|
|
1992
|
+
if (properties.unique === true || !(await this.has(entry.hash))) {
|
|
1993
|
+
this._length++;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
this.cache.add(entry.hash, entry);
|
|
1997
|
+
const shallowEntry =
|
|
1998
|
+
properties.shallowEntry ??
|
|
1999
|
+
Entry.takePreparedShallowEntry(entry, isHead) ??
|
|
2000
|
+
entry.toShallow(isHead);
|
|
2001
|
+
shallowEntry.head = isHead;
|
|
2002
|
+
this.pendingIndexWrites.set(entry.hash, shallowEntry);
|
|
2003
|
+
this.schedulePendingIndexWriteFlush();
|
|
2004
|
+
|
|
2005
|
+
if (properties.externalNextHashes.length > 0) {
|
|
2006
|
+
await this.privateUpdateNextHeadHashes(
|
|
2007
|
+
properties.externalNextHashes,
|
|
2008
|
+
false,
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
})().finally(() => {
|
|
2012
|
+
this.insertionPromises.delete(entry.hash);
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
this.insertionPromises.set(entry.hash, promise);
|
|
2016
|
+
return promise;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/** @internal */
|
|
2020
|
+
putNativeCommittedAppendFacts(properties: {
|
|
2021
|
+
hash: string;
|
|
2022
|
+
unique: boolean;
|
|
2023
|
+
externalNextHashes: string[];
|
|
2024
|
+
shallowEntry?: ShallowEntry;
|
|
2025
|
+
getShallowEntry?: () => ShallowEntry;
|
|
2026
|
+
isHead?: boolean;
|
|
2027
|
+
}): Promise<void> | void {
|
|
2028
|
+
if (!properties.hash) {
|
|
2029
|
+
throw new Error("Missing hash");
|
|
2030
|
+
}
|
|
2031
|
+
const existingPromise = this.insertionPromises.get(properties.hash);
|
|
2032
|
+
if (
|
|
2033
|
+
!existingPromise &&
|
|
2034
|
+
properties.unique === true &&
|
|
2035
|
+
properties.externalNextHashes.length === 0
|
|
2036
|
+
) {
|
|
2037
|
+
const isHead = properties.isHead ?? true;
|
|
2038
|
+
this._length++;
|
|
2039
|
+
if (!properties.shallowEntry && !properties.getShallowEntry) {
|
|
2040
|
+
throw new Error("Missing shallow entry");
|
|
2041
|
+
}
|
|
2042
|
+
const pending =
|
|
2043
|
+
properties.shallowEntry ??
|
|
2044
|
+
(() => {
|
|
2045
|
+
const shallowEntry = properties.getShallowEntry!();
|
|
2046
|
+
shallowEntry.head = isHead;
|
|
2047
|
+
return shallowEntry;
|
|
2048
|
+
});
|
|
2049
|
+
if (properties.shallowEntry) {
|
|
2050
|
+
properties.shallowEntry.head = isHead;
|
|
2051
|
+
}
|
|
2052
|
+
this.pendingIndexWrites.set(properties.hash, pending);
|
|
2053
|
+
this.schedulePendingIndexWriteFlush();
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
return this.putNativeCommittedAppendFactsAsync(properties, existingPromise);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
/** @internal */
|
|
2061
|
+
putNativeCommittedAppendFactsBatch(
|
|
2062
|
+
rows: Array<{
|
|
2063
|
+
hash: string;
|
|
2064
|
+
unique: boolean;
|
|
2065
|
+
externalNextHashes: string[];
|
|
2066
|
+
shallowEntry?: ShallowEntry;
|
|
2067
|
+
getShallowEntry?: () => ShallowEntry;
|
|
2068
|
+
isHead?: boolean;
|
|
2069
|
+
}>,
|
|
2070
|
+
): Promise<void> | void {
|
|
2071
|
+
if (rows.length === 0) {
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
const asyncRows: typeof rows = [];
|
|
2075
|
+
const externalNextHashes: string[] = [];
|
|
2076
|
+
let scheduledPendingFlush = false;
|
|
2077
|
+
for (const row of rows) {
|
|
2078
|
+
if (!row.hash) {
|
|
2079
|
+
throw new Error("Missing hash");
|
|
2080
|
+
}
|
|
2081
|
+
const existingPromise = this.insertionPromises.get(row.hash);
|
|
2082
|
+
if (!existingPromise && row.unique === true) {
|
|
2083
|
+
const isHead = row.isHead ?? true;
|
|
2084
|
+
this._length++;
|
|
2085
|
+
if (!row.shallowEntry && !row.getShallowEntry) {
|
|
2086
|
+
throw new Error("Missing shallow entry");
|
|
2087
|
+
}
|
|
2088
|
+
const pending =
|
|
2089
|
+
row.shallowEntry ??
|
|
2090
|
+
(() => {
|
|
2091
|
+
const shallowEntry = row.getShallowEntry!();
|
|
2092
|
+
shallowEntry.head = isHead;
|
|
2093
|
+
return shallowEntry;
|
|
2094
|
+
});
|
|
2095
|
+
if (row.shallowEntry) {
|
|
2096
|
+
row.shallowEntry.head = isHead;
|
|
2097
|
+
}
|
|
2098
|
+
this.pendingIndexWrites.set(row.hash, pending);
|
|
2099
|
+
if (row.externalNextHashes.length > 0) {
|
|
2100
|
+
externalNextHashes.push(...row.externalNextHashes);
|
|
2101
|
+
}
|
|
2102
|
+
scheduledPendingFlush = true;
|
|
2103
|
+
} else {
|
|
2104
|
+
asyncRows.push(row);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (scheduledPendingFlush) {
|
|
2108
|
+
this.schedulePendingIndexWriteFlush();
|
|
2109
|
+
}
|
|
2110
|
+
const batchedNextUpdate =
|
|
2111
|
+
externalNextHashes.length > 0
|
|
2112
|
+
? this.privateUpdateNextHeadHashes(externalNextHashes, false)
|
|
2113
|
+
: undefined;
|
|
2114
|
+
if (asyncRows.length > 0 || batchedNextUpdate) {
|
|
2115
|
+
return Promise.all([
|
|
2116
|
+
batchedNextUpdate,
|
|
2117
|
+
...asyncRows.map((row) => this.putNativeCommittedAppendFacts(row)),
|
|
2118
|
+
]).then(() => undefined);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
private async putNativeCommittedAppendFactsAsync(
|
|
2123
|
+
properties: {
|
|
2124
|
+
hash: string;
|
|
2125
|
+
unique: boolean;
|
|
2126
|
+
externalNextHashes: string[];
|
|
2127
|
+
shallowEntry?: ShallowEntry;
|
|
2128
|
+
getShallowEntry?: () => ShallowEntry;
|
|
2129
|
+
isHead?: boolean;
|
|
2130
|
+
},
|
|
2131
|
+
existingPromise?: Promise<void>,
|
|
2132
|
+
) {
|
|
2133
|
+
if (existingPromise) {
|
|
2134
|
+
await existingPromise;
|
|
2135
|
+
}
|
|
2136
|
+
const promise = (async () => {
|
|
2137
|
+
const isHead = properties.isHead ?? true;
|
|
2138
|
+
if (properties.unique === true || !(await this.has(properties.hash))) {
|
|
2139
|
+
this._length++;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
if (!properties.shallowEntry && !properties.getShallowEntry) {
|
|
2143
|
+
throw new Error("Missing shallow entry");
|
|
2144
|
+
}
|
|
2145
|
+
const pending =
|
|
2146
|
+
properties.shallowEntry ??
|
|
2147
|
+
(() => {
|
|
2148
|
+
const shallowEntry = properties.getShallowEntry!();
|
|
2149
|
+
shallowEntry.head = isHead;
|
|
2150
|
+
return shallowEntry;
|
|
2151
|
+
});
|
|
2152
|
+
if (properties.shallowEntry) {
|
|
2153
|
+
properties.shallowEntry.head = isHead;
|
|
2154
|
+
}
|
|
2155
|
+
this.pendingIndexWrites.set(properties.hash, pending);
|
|
2156
|
+
this.schedulePendingIndexWriteFlush();
|
|
2157
|
+
|
|
2158
|
+
if (properties.externalNextHashes.length > 0) {
|
|
2159
|
+
await this.privateUpdateNextHeadHashes(
|
|
2160
|
+
properties.externalNextHashes,
|
|
2161
|
+
false,
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
})().finally(() => {
|
|
2165
|
+
this.insertionPromises.delete(properties.hash);
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
this.insertionPromises.set(properties.hash, promise);
|
|
2169
|
+
return promise;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
654
2172
|
async delete(k: string, from?: Entry<any> | ShallowEntry) {
|
|
655
2173
|
this.cache.del(k);
|
|
656
2174
|
|
|
@@ -658,7 +2176,7 @@ export class EntryIndex<T> {
|
|
|
658
2176
|
throw new Error("Shallow hash doesn't match the key");
|
|
659
2177
|
}
|
|
660
2178
|
|
|
661
|
-
const pending = this.
|
|
2179
|
+
const pending = this.getPendingIndexWrite(k);
|
|
662
2180
|
from = from || pending || (await this.getShallow(k))?.value;
|
|
663
2181
|
if (!from) {
|
|
664
2182
|
return; // already deleted
|
|
@@ -685,14 +2203,523 @@ export class EntryIndex<T> {
|
|
|
685
2203
|
}
|
|
686
2204
|
}
|
|
687
2205
|
|
|
2206
|
+
canDeleteMany(): boolean {
|
|
2207
|
+
return (
|
|
2208
|
+
!!this.properties.nativeGraph?.graph.deleteMany ||
|
|
2209
|
+
hasRmMany(this.properties.store)
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
async deleteMany(
|
|
2214
|
+
from: ShallowEntry[],
|
|
2215
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2216
|
+
): Promise<ShallowEntry[]> {
|
|
2217
|
+
return this.deleteManyMaybe(from, options);
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
deleteManyMaybe(
|
|
2221
|
+
from: ShallowEntry[],
|
|
2222
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2223
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2224
|
+
if (from.length === 0) {
|
|
2225
|
+
return [];
|
|
2226
|
+
}
|
|
2227
|
+
if (from.length === 1) {
|
|
2228
|
+
return this.deleteSingleMaybe(from[0]!, options);
|
|
2229
|
+
}
|
|
2230
|
+
const nodes: ShallowEntry[] = [];
|
|
2231
|
+
const seen = new Set<string>();
|
|
2232
|
+
for (const node of from) {
|
|
2233
|
+
if (seen.has(node.hash)) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
seen.add(node.hash);
|
|
2237
|
+
nodes.push(node);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
|
|
2241
|
+
const deletedByHash = new Map<string, ShallowEntry>();
|
|
2242
|
+
const indexedHashes: string[] = [];
|
|
2243
|
+
const storeHashes: string[] = [];
|
|
2244
|
+
|
|
2245
|
+
for (const node of nodes) {
|
|
2246
|
+
this.cache.del(node.hash);
|
|
2247
|
+
const pending = this.getPendingIndexWrite(node.hash);
|
|
2248
|
+
if (pending) {
|
|
2249
|
+
this.pendingIndexWrites.delete(node.hash);
|
|
2250
|
+
deletedByHash.set(node.hash, pending);
|
|
2251
|
+
storeHashes.push(node.hash);
|
|
2252
|
+
} else {
|
|
2253
|
+
indexedHashes.push(node.hash);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
|
|
2258
|
+
this.properties.index,
|
|
2259
|
+
)
|
|
2260
|
+
? (this.properties.index as IndexWithExactDelete)
|
|
2261
|
+
: undefined;
|
|
2262
|
+
if (indexedHashes.length > 0 && exactDeleteIndex) {
|
|
2263
|
+
return mapMaybePromise(
|
|
2264
|
+
exactDeleteIndex.delIds(indexedHashes),
|
|
2265
|
+
(deleted) => {
|
|
2266
|
+
for (const id of deleted) {
|
|
2267
|
+
const hash = String(id.primitive);
|
|
2268
|
+
const node = indexedByHash.get(hash);
|
|
2269
|
+
if (!node || deletedByHash.has(hash)) {
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
deletedByHash.set(hash, node);
|
|
2273
|
+
storeHashes.push(hash);
|
|
2274
|
+
}
|
|
2275
|
+
return this.finishDeleteMany(
|
|
2276
|
+
deletedByHash,
|
|
2277
|
+
nodes,
|
|
2278
|
+
storeHashes,
|
|
2279
|
+
options,
|
|
2280
|
+
);
|
|
2281
|
+
},
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
if (indexedHashes.length > 0) {
|
|
2285
|
+
return this.deleteManyByQuery(
|
|
2286
|
+
indexedHashes,
|
|
2287
|
+
indexedByHash,
|
|
2288
|
+
deletedByHash,
|
|
2289
|
+
nodes,
|
|
2290
|
+
storeHashes,
|
|
2291
|
+
options,
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
consumeNativeTrimmedEntriesMaybe(
|
|
2298
|
+
from: ShallowEntry[],
|
|
2299
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2300
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2301
|
+
if (from.length === 0) {
|
|
2302
|
+
return [];
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
const nodes: ShallowEntry[] = [];
|
|
2306
|
+
const seen = new Set<string>();
|
|
2307
|
+
for (const node of from) {
|
|
2308
|
+
if (seen.has(node.hash)) {
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
seen.add(node.hash);
|
|
2312
|
+
nodes.push(node);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
consumeNativeTrimmedEntryHashesMaybe(
|
|
2319
|
+
hashes: string[],
|
|
2320
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2321
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2322
|
+
if (hashes.length === 0) {
|
|
2323
|
+
return [];
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
const nodes: ShallowEntry[] = [];
|
|
2327
|
+
const seen = new Set<string>();
|
|
2328
|
+
for (const hash of hashes) {
|
|
2329
|
+
if (seen.has(hash)) {
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
seen.add(hash);
|
|
2333
|
+
nodes.push(this.nativeTrimmedHashToShallowEntry(hash));
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
return this.consumeNativeTrimmedEntryNodesMaybe(nodes, options);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
consumeNativeTrimmedEntryHashesNoReturnMaybe(
|
|
2340
|
+
hashes: string[],
|
|
2341
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2342
|
+
): MaybePromise<boolean> | undefined {
|
|
2343
|
+
if (
|
|
2344
|
+
!options?.skipNextHeadUpdates ||
|
|
2345
|
+
options.deleteBlocks !== false ||
|
|
2346
|
+
hashes.length === 0
|
|
2347
|
+
) {
|
|
2348
|
+
return undefined;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
const indexedHashes: string[] = [];
|
|
2352
|
+
let pendingDeleted = 0;
|
|
2353
|
+
const seen = new Set<string>();
|
|
2354
|
+
for (const hash of hashes) {
|
|
2355
|
+
if (seen.has(hash)) {
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
seen.add(hash);
|
|
2359
|
+
this.cache.del(hash);
|
|
2360
|
+
if (this.pendingIndexWrites.delete(hash)) {
|
|
2361
|
+
pendingDeleted++;
|
|
2362
|
+
} else {
|
|
2363
|
+
indexedHashes.push(hash);
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
const finish = (indexedDeleted: number) => {
|
|
2368
|
+
this._length -= pendingDeleted + indexedDeleted;
|
|
2369
|
+
return true;
|
|
2370
|
+
};
|
|
2371
|
+
if (indexedHashes.length === 0) {
|
|
2372
|
+
return finish(0);
|
|
2373
|
+
}
|
|
2374
|
+
const exactDeleteCountIndex = hasExactDeleteCount(this.properties.index)
|
|
2375
|
+
? this.properties.index
|
|
2376
|
+
: undefined;
|
|
2377
|
+
if (exactDeleteCountIndex) {
|
|
2378
|
+
return mapMaybePromise(
|
|
2379
|
+
exactDeleteCountIndex.delIdsCount(indexedHashes),
|
|
2380
|
+
finish,
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
const exactDeleteIndex = hasExactDelete(this.properties.index)
|
|
2384
|
+
? this.properties.index
|
|
2385
|
+
: undefined;
|
|
2386
|
+
if (exactDeleteIndex) {
|
|
2387
|
+
return mapMaybePromise(
|
|
2388
|
+
exactDeleteIndex.delIds(indexedHashes),
|
|
2389
|
+
(deleted) => finish(deleted.length),
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
return this.consumeNativeTrimmedEntryHashesNoReturnByQuery(
|
|
2393
|
+
indexedHashes,
|
|
2394
|
+
finish,
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
private async consumeNativeTrimmedEntryHashesNoReturnByQuery(
|
|
2399
|
+
indexedHashes: string[],
|
|
2400
|
+
finish: (indexedDeleted: number) => boolean,
|
|
2401
|
+
): Promise<boolean> {
|
|
2402
|
+
const batchSize = 64;
|
|
2403
|
+
let deletedCount = 0;
|
|
2404
|
+
for (let i = 0; i < indexedHashes.length; i += batchSize) {
|
|
2405
|
+
const hashes = indexedHashes.slice(i, i + batchSize);
|
|
2406
|
+
const deleted = await this.properties.index.del({
|
|
2407
|
+
query: createHashMatchQuery(hashes),
|
|
2408
|
+
});
|
|
2409
|
+
deletedCount += deleted.length;
|
|
2410
|
+
}
|
|
2411
|
+
return finish(deletedCount);
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
private consumeNativeTrimmedEntryNodesMaybe(
|
|
2415
|
+
nodes: ShallowEntry[],
|
|
2416
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2417
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2418
|
+
const indexedByHash = new Map(nodes.map((node) => [node.hash, node]));
|
|
2419
|
+
const deletedByHash = new Map<string, ShallowEntry>();
|
|
2420
|
+
const indexedHashes: string[] = [];
|
|
2421
|
+
for (const node of nodes) {
|
|
2422
|
+
this.cache.del(node.hash);
|
|
2423
|
+
const pending = this.getPendingIndexWrite(node.hash);
|
|
2424
|
+
if (pending) {
|
|
2425
|
+
this.pendingIndexWrites.delete(node.hash);
|
|
2426
|
+
deletedByHash.set(node.hash, pending);
|
|
2427
|
+
} else {
|
|
2428
|
+
indexedHashes.push(node.hash);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
const finish = (): MaybePromise<ShallowEntry[]> =>
|
|
2433
|
+
this.finishConsumeNativeTrimmedEntries(deletedByHash, nodes, options);
|
|
2434
|
+
|
|
2435
|
+
const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
|
|
2436
|
+
this.properties.index,
|
|
2437
|
+
)
|
|
2438
|
+
? (this.properties.index as IndexWithExactDelete)
|
|
2439
|
+
: undefined;
|
|
2440
|
+
if (indexedHashes.length > 0 && exactDeleteIndex) {
|
|
2441
|
+
return mapMaybePromise(
|
|
2442
|
+
exactDeleteIndex.delIds(indexedHashes),
|
|
2443
|
+
(deleted) => {
|
|
2444
|
+
for (const id of deleted) {
|
|
2445
|
+
const hash = String(id.primitive);
|
|
2446
|
+
const node = indexedByHash.get(hash);
|
|
2447
|
+
if (!node || deletedByHash.has(hash)) {
|
|
2448
|
+
continue;
|
|
2449
|
+
}
|
|
2450
|
+
deletedByHash.set(hash, node);
|
|
2451
|
+
}
|
|
2452
|
+
return finish();
|
|
2453
|
+
},
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
if (indexedHashes.length > 0) {
|
|
2457
|
+
return this.consumeNativeTrimmedEntriesByQuery(
|
|
2458
|
+
indexedHashes,
|
|
2459
|
+
indexedByHash,
|
|
2460
|
+
deletedByHash,
|
|
2461
|
+
nodes,
|
|
2462
|
+
options,
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
return finish();
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
private deleteSingleMaybe(
|
|
2469
|
+
node: ShallowEntry,
|
|
2470
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2471
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2472
|
+
this.cache.del(node.hash);
|
|
2473
|
+
const pending = this.getPendingIndexWrite(node.hash);
|
|
2474
|
+
if (pending) {
|
|
2475
|
+
this.pendingIndexWrites.delete(node.hash);
|
|
2476
|
+
return this.finishDeleteSingle(pending, options);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
const exactDeleteIndex: IndexWithExactDelete | undefined = hasExactDelete(
|
|
2480
|
+
this.properties.index,
|
|
2481
|
+
)
|
|
2482
|
+
? (this.properties.index as IndexWithExactDelete)
|
|
2483
|
+
: undefined;
|
|
2484
|
+
if (exactDeleteIndex) {
|
|
2485
|
+
return mapMaybePromise(exactDeleteIndex.delIds([node.hash]), (deleted) =>
|
|
2486
|
+
deleted.some((id) => String(id.primitive) === node.hash)
|
|
2487
|
+
? this.finishDeleteSingle(node, options)
|
|
2488
|
+
: [],
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
return mapMaybePromise(
|
|
2492
|
+
this.properties.index.del({ query: createHashMatchQuery([node.hash]) }),
|
|
2493
|
+
(deleted) =>
|
|
2494
|
+
deleted.some((id) => String(id.primitive) === node.hash)
|
|
2495
|
+
? this.finishDeleteSingle(node, options)
|
|
2496
|
+
: [],
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
private finishDeleteSingle(
|
|
2501
|
+
node: ShallowEntry,
|
|
2502
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2503
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2504
|
+
const afterStoreDelete = (): MaybePromise<ShallowEntry[]> => {
|
|
2505
|
+
this._length--;
|
|
2506
|
+
this.properties.nativeGraph?.graph.delete(node.hash);
|
|
2507
|
+
if (!options?.skipNextHeadUpdates && node.meta.type !== EntryType.CUT) {
|
|
2508
|
+
return mapMaybePromise(
|
|
2509
|
+
this.privateUpdateNextHeadHashes(node.meta.next, true),
|
|
2510
|
+
() => [node],
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
return [node];
|
|
2514
|
+
};
|
|
2515
|
+
return mapMaybePromise(
|
|
2516
|
+
this.properties.store.rm(node.hash),
|
|
2517
|
+
afterStoreDelete,
|
|
2518
|
+
);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
private async deleteManyByQuery(
|
|
2522
|
+
indexedHashes: string[],
|
|
2523
|
+
indexedByHash: Map<string, ShallowEntry>,
|
|
2524
|
+
deletedByHash: Map<string, ShallowEntry>,
|
|
2525
|
+
nodes: ShallowEntry[],
|
|
2526
|
+
storeHashes: string[],
|
|
2527
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2528
|
+
): Promise<ShallowEntry[]> {
|
|
2529
|
+
const batchSize = 64;
|
|
2530
|
+
for (let i = 0; i < indexedHashes.length; i += batchSize) {
|
|
2531
|
+
const hashes = indexedHashes.slice(i, i + batchSize);
|
|
2532
|
+
const deleted = await this.properties.index.del({
|
|
2533
|
+
query: createHashMatchQuery(hashes),
|
|
2534
|
+
});
|
|
2535
|
+
for (const id of deleted) {
|
|
2536
|
+
const hash = String(id.primitive);
|
|
2537
|
+
const node = indexedByHash.get(hash);
|
|
2538
|
+
if (!node || deletedByHash.has(hash)) {
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
deletedByHash.set(hash, node);
|
|
2542
|
+
storeHashes.push(hash);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return this.finishDeleteMany(deletedByHash, nodes, storeHashes, options);
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
private async consumeNativeTrimmedEntriesByQuery(
|
|
2549
|
+
indexedHashes: string[],
|
|
2550
|
+
indexedByHash: Map<string, ShallowEntry>,
|
|
2551
|
+
deletedByHash: Map<string, ShallowEntry>,
|
|
2552
|
+
nodes: ShallowEntry[],
|
|
2553
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2554
|
+
): Promise<ShallowEntry[]> {
|
|
2555
|
+
const batchSize = 64;
|
|
2556
|
+
for (let i = 0; i < indexedHashes.length; i += batchSize) {
|
|
2557
|
+
const hashes = indexedHashes.slice(i, i + batchSize);
|
|
2558
|
+
const deleted = await this.properties.index.del({
|
|
2559
|
+
query: createHashMatchQuery(hashes),
|
|
2560
|
+
});
|
|
2561
|
+
for (const id of deleted) {
|
|
2562
|
+
const hash = String(id.primitive);
|
|
2563
|
+
const node = indexedByHash.get(hash);
|
|
2564
|
+
if (!node || deletedByHash.has(hash)) {
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
deletedByHash.set(hash, node);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
return this.finishConsumeNativeTrimmedEntries(
|
|
2571
|
+
deletedByHash,
|
|
2572
|
+
nodes,
|
|
2573
|
+
options,
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
private finishConsumeNativeTrimmedEntries(
|
|
2578
|
+
deletedByHash: Map<string, ShallowEntry>,
|
|
2579
|
+
nodes: ShallowEntry[],
|
|
2580
|
+
options?: { skipNextHeadUpdates?: boolean; deleteBlocks?: boolean },
|
|
2581
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2582
|
+
const deleted = nodes
|
|
2583
|
+
.map((node) => deletedByHash.get(node.hash))
|
|
2584
|
+
.filter((node): node is ShallowEntry => !!node);
|
|
2585
|
+
if (deleted.length === 0) {
|
|
2586
|
+
return [];
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
const afterStoreDelete = () => {
|
|
2590
|
+
this._length -= deleted.length;
|
|
2591
|
+
if (!options?.skipNextHeadUpdates) {
|
|
2592
|
+
const deletedNexts: string[] = [];
|
|
2593
|
+
for (const node of deleted) {
|
|
2594
|
+
if (node.meta.type !== EntryType.CUT) {
|
|
2595
|
+
deletedNexts.push(...node.meta.next);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
return mapMaybePromise(
|
|
2599
|
+
this.privateUpdateNextHeadHashes(deletedNexts, true),
|
|
2600
|
+
() => deleted,
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
return deleted;
|
|
2604
|
+
};
|
|
2605
|
+
|
|
2606
|
+
if (options?.deleteBlocks) {
|
|
2607
|
+
const store = this.properties.store;
|
|
2608
|
+
const hashes = deleted.map((node) => node.hash);
|
|
2609
|
+
const deleteResult =
|
|
2610
|
+
hasRmMany(store) && store.rmMany
|
|
2611
|
+
? store.rmMany(hashes)
|
|
2612
|
+
: Promise.all(hashes.map((hash) => store.rm(hash))).then(() => {});
|
|
2613
|
+
return mapMaybePromise(deleteResult, afterStoreDelete);
|
|
2614
|
+
}
|
|
2615
|
+
return afterStoreDelete();
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
private finishDeleteMany(
|
|
2619
|
+
deletedByHash: Map<string, ShallowEntry>,
|
|
2620
|
+
nodes: ShallowEntry[],
|
|
2621
|
+
storeHashes: string[],
|
|
2622
|
+
options?: { skipNextHeadUpdates?: boolean },
|
|
2623
|
+
): MaybePromise<ShallowEntry[]> {
|
|
2624
|
+
const deleted = nodes
|
|
2625
|
+
.map((node) => deletedByHash.get(node.hash))
|
|
2626
|
+
.filter((node): node is ShallowEntry => !!node);
|
|
2627
|
+
if (deleted.length === 0) {
|
|
2628
|
+
return [];
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
const store = this.properties.store;
|
|
2632
|
+
const afterStoreDelete = () => {
|
|
2633
|
+
this._length -= deleted.length;
|
|
2634
|
+
const graph = this.properties.nativeGraph?.graph;
|
|
2635
|
+
if (graph?.deleteMany) {
|
|
2636
|
+
graph.deleteMany(storeHashes);
|
|
2637
|
+
} else {
|
|
2638
|
+
for (const hash of storeHashes) {
|
|
2639
|
+
graph?.delete(hash);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
if (!options?.skipNextHeadUpdates) {
|
|
2643
|
+
const deletedNexts: string[] = [];
|
|
2644
|
+
for (const node of deleted) {
|
|
2645
|
+
if (node.meta.type !== EntryType.CUT) {
|
|
2646
|
+
deletedNexts.push(...node.meta.next);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
return mapMaybePromise(
|
|
2650
|
+
this.privateUpdateNextHeadHashes(deletedNexts, true),
|
|
2651
|
+
() => deleted,
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
return deleted;
|
|
2655
|
+
};
|
|
2656
|
+
if (hasRmMany(store) && store.rmMany) {
|
|
2657
|
+
return mapMaybePromise(store.rmMany(storeHashes), afterStoreDelete);
|
|
2658
|
+
}
|
|
2659
|
+
return Promise.all(storeHashes.map((hash) => store.rm(hash))).then(
|
|
2660
|
+
afterStoreDelete,
|
|
2661
|
+
);
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
private nativeLogEntryToShallowEntry(entry: NativeLogEntry): ShallowEntry {
|
|
2665
|
+
return new ShallowEntry({
|
|
2666
|
+
hash: entry.hash,
|
|
2667
|
+
head: entry.head ?? false,
|
|
2668
|
+
payloadSize: entry.payloadSize ?? 0,
|
|
2669
|
+
meta: new ShallowMeta({
|
|
2670
|
+
gid: entry.gid,
|
|
2671
|
+
next: entry.next,
|
|
2672
|
+
type: entry.type,
|
|
2673
|
+
data: entry.data,
|
|
2674
|
+
clock: new Clock({
|
|
2675
|
+
id: this.properties.publicKey.bytes,
|
|
2676
|
+
timestamp: new Timestamp({
|
|
2677
|
+
wallTime: BigInt(entry.clock.timestamp.wallTime),
|
|
2678
|
+
logical: entry.clock.timestamp.logical ?? 0,
|
|
2679
|
+
}),
|
|
2680
|
+
}),
|
|
2681
|
+
}),
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
private nativeTrimmedHashToShallowEntry(hash: string): ShallowEntry {
|
|
2686
|
+
return new ShallowEntry({
|
|
2687
|
+
hash,
|
|
2688
|
+
head: false,
|
|
2689
|
+
payloadSize: 0,
|
|
2690
|
+
meta: new ShallowMeta({
|
|
2691
|
+
gid: "",
|
|
2692
|
+
next: [],
|
|
2693
|
+
type: EntryType.APPEND,
|
|
2694
|
+
clock: new Clock({
|
|
2695
|
+
id: this.properties.publicKey.bytes,
|
|
2696
|
+
timestamp: new Timestamp({
|
|
2697
|
+
wallTime: 0n,
|
|
2698
|
+
logical: 0,
|
|
2699
|
+
}),
|
|
2700
|
+
}),
|
|
2701
|
+
}),
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
nativeLogEntriesToShallowEntries(entries: NativeLogEntry[]): ShallowEntry[] {
|
|
2706
|
+
return entries.map((entry) => this.nativeLogEntryToShallowEntry(entry));
|
|
2707
|
+
}
|
|
2708
|
+
|
|
688
2709
|
async getMemoryUsage() {
|
|
2710
|
+
if (this.properties.nativeGraph) {
|
|
2711
|
+
return this.properties.nativeGraph.graph.payloadSizeSum();
|
|
2712
|
+
}
|
|
689
2713
|
const indexed =
|
|
690
2714
|
(await this.properties.index.sum({ key: "payloadSize" })) || 0;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
2715
|
+
let pending = 0;
|
|
2716
|
+
for (const [hash, write] of this.pendingIndexWrites) {
|
|
2717
|
+
pending +=
|
|
2718
|
+
this.materializePendingIndexWrite(hash, write).payloadSize || 0;
|
|
2719
|
+
}
|
|
2720
|
+
return typeof indexed === "bigint"
|
|
2721
|
+
? indexed + BigInt(pending)
|
|
2722
|
+
: indexed + pending;
|
|
696
2723
|
}
|
|
697
2724
|
|
|
698
2725
|
private async privateUpdateNextHeadProperty(
|
|
@@ -704,8 +2731,23 @@ export class EntryIndex<T> {
|
|
|
704
2731
|
return;
|
|
705
2732
|
}
|
|
706
2733
|
|
|
707
|
-
|
|
708
|
-
|
|
2734
|
+
await this.privateUpdateNextHeadHashes(from.meta.next, isHead);
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
private async privateUpdateNextHeadHashes(nexts: string[], isHead: boolean) {
|
|
2738
|
+
const hashes = [...new Set(nexts.filter(Boolean))];
|
|
2739
|
+
if (hashes.length === 0) {
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
const existingNexts =
|
|
2743
|
+
isHead && this.properties.nativeGraph
|
|
2744
|
+
? this.properties.nativeGraph.graph.hasMany(hashes)
|
|
2745
|
+
: undefined;
|
|
2746
|
+
for (const next of hashes) {
|
|
2747
|
+
const pending = this.getPendingIndexWrite(next);
|
|
2748
|
+
if (!pending && existingNexts && !existingNexts.has(next)) {
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
709
2751
|
const indexedEntry = pending
|
|
710
2752
|
? { id: toId(next), value: pending }
|
|
711
2753
|
: await this.properties.index.get(toId(next));
|
|
@@ -735,6 +2777,25 @@ export class EntryIndex<T> {
|
|
|
735
2777
|
}
|
|
736
2778
|
}
|
|
737
2779
|
|
|
2780
|
+
private async notifyShadowedGids(entry: Entry<any>) {
|
|
2781
|
+
if (!this.properties.onGidRemoved || entry.meta.next.length === 0) {
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
const shadowedGids: Set<string> = this.properties.nativeGraph
|
|
2785
|
+
? new Set<string>(
|
|
2786
|
+
this.properties.nativeGraph.graph.shadowedGids(
|
|
2787
|
+
entry.meta.gid,
|
|
2788
|
+
entry.meta.next,
|
|
2789
|
+
entry.hash,
|
|
2790
|
+
),
|
|
2791
|
+
)
|
|
2792
|
+
: await this.findShadowedGids(entry);
|
|
2793
|
+
|
|
2794
|
+
if (shadowedGids.size > 0) {
|
|
2795
|
+
this.properties.onGidRemoved([...shadowedGids]);
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
738
2799
|
async clear() {
|
|
739
2800
|
this.clearPendingIndexFlushTimer();
|
|
740
2801
|
const hashes = new Set<string>(this.pendingIndexWrites.keys());
|
|
@@ -862,7 +2923,63 @@ export class EntryIndex<T> {
|
|
|
862
2923
|
}
|
|
863
2924
|
return mem ? mem : undefined;
|
|
864
2925
|
/* }
|
|
865
|
-
|
|
2926
|
+
return undefined; */
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
private async resolveMany(
|
|
2930
|
+
hashes: string[],
|
|
2931
|
+
options?: ResolveFullyOptions,
|
|
2932
|
+
): Promise<Array<Entry<T> | undefined>> {
|
|
2933
|
+
if (hashes.length === 0) {
|
|
2934
|
+
return [];
|
|
2935
|
+
}
|
|
2936
|
+
if (!canBatchResolveFromStore(this.properties.store, options)) {
|
|
2937
|
+
return Promise.all(hashes.map((hash) => this.resolve(hash, options)));
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
const coercedOptions = typeof options === "object" ? options : undefined;
|
|
2941
|
+
const resolved: Array<Entry<T> | undefined> = new Array(hashes.length);
|
|
2942
|
+
const missingHashes: string[] = [];
|
|
2943
|
+
const missingPositions: number[] = [];
|
|
2944
|
+
|
|
2945
|
+
for (let i = 0; i < hashes.length; i++) {
|
|
2946
|
+
const hash = hashes[i]!;
|
|
2947
|
+
const mem = this.cache.get(hash);
|
|
2948
|
+
if (mem !== undefined) {
|
|
2949
|
+
resolved[i] = mem ? mem : undefined;
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
missingHashes.push(hash);
|
|
2953
|
+
missingPositions.push(i);
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
if (missingHashes.length === 0) {
|
|
2957
|
+
return resolved;
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
const values = await this.properties.store.getMany!(
|
|
2961
|
+
missingHashes,
|
|
2962
|
+
withDefaultRemoteReadPriority(coercedOptions),
|
|
2963
|
+
);
|
|
2964
|
+
for (let i = 0; i < values.length; i++) {
|
|
2965
|
+
const hash = missingHashes[i]!;
|
|
2966
|
+
const value = values[i];
|
|
2967
|
+
if (!value) {
|
|
2968
|
+
if (coercedOptions?.ignoreMissing !== true) {
|
|
2969
|
+
throw new Error("Failed to load entry from head with hash: " + hash);
|
|
2970
|
+
}
|
|
2971
|
+
continue;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
const entry = deserialize(value, Entry) as Entry<T>;
|
|
2975
|
+
this.properties.init(entry);
|
|
2976
|
+
entry.hash = hash;
|
|
2977
|
+
entry.size = value.length;
|
|
2978
|
+
this.cache.add(hash, entry);
|
|
2979
|
+
resolved[missingPositions[i]!] = entry;
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
return resolved;
|
|
866
2983
|
}
|
|
867
2984
|
|
|
868
2985
|
private async resolveFromStore(
|
|
@@ -922,6 +3039,26 @@ export class EntryIndex<T> {
|
|
|
922
3039
|
}
|
|
923
3040
|
}
|
|
924
3041
|
|
|
3042
|
+
const createHashMatchQuery = (hashes: string[]): Query =>
|
|
3043
|
+
hashes.length === 1
|
|
3044
|
+
? new StringMatch({
|
|
3045
|
+
key: "hash",
|
|
3046
|
+
value: hashes[0]!,
|
|
3047
|
+
caseInsensitive: false,
|
|
3048
|
+
method: StringMatchMethod.exact,
|
|
3049
|
+
})
|
|
3050
|
+
: new Or(
|
|
3051
|
+
hashes.map(
|
|
3052
|
+
(hash) =>
|
|
3053
|
+
new StringMatch({
|
|
3054
|
+
key: "hash",
|
|
3055
|
+
value: hash,
|
|
3056
|
+
caseInsensitive: false,
|
|
3057
|
+
method: StringMatchMethod.exact,
|
|
3058
|
+
}),
|
|
3059
|
+
),
|
|
3060
|
+
);
|
|
3061
|
+
|
|
925
3062
|
const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
|
|
926
3063
|
hash: entry.hash,
|
|
927
3064
|
gid: entry.meta.gid,
|
|
@@ -929,6 +3066,7 @@ const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
|
|
|
929
3066
|
type: entry.meta.type,
|
|
930
3067
|
head: entry.head,
|
|
931
3068
|
payloadSize: entry.payloadSize,
|
|
3069
|
+
data: entry.meta.data,
|
|
932
3070
|
clock: {
|
|
933
3071
|
timestamp: {
|
|
934
3072
|
wallTime: entry.meta.clock.timestamp.wallTime,
|
|
@@ -937,6 +3075,32 @@ const toNativeLogEntry = (entry: ShallowEntry): NativeLogEntry => ({
|
|
|
937
3075
|
},
|
|
938
3076
|
});
|
|
939
3077
|
|
|
3078
|
+
const getResolveShape = (options: MaybeResolveOptions | undefined) =>
|
|
3079
|
+
options && options !== true && options.type === "shape"
|
|
3080
|
+
? options.shape
|
|
3081
|
+
: undefined;
|
|
3082
|
+
|
|
3083
|
+
const isHeadHashOnlyShape = (shape: Shape) => {
|
|
3084
|
+
const keys = Object.keys(shape);
|
|
3085
|
+
return keys.length === 1 && shape.hash === true;
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
const isHeadDataShape = (shape: Shape) => {
|
|
3089
|
+
const keys = Object.keys(shape);
|
|
3090
|
+
if (keys.some((key) => key !== "hash" && key !== "meta")) {
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
if (shape.hash !== undefined && shape.hash !== true) {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
const meta = shape.meta as Shape | undefined;
|
|
3097
|
+
if (!meta || typeof meta !== "object") {
|
|
3098
|
+
return false;
|
|
3099
|
+
}
|
|
3100
|
+
const metaKeys = Object.keys(meta);
|
|
3101
|
+
return metaKeys.length === 1 && meta.data === true;
|
|
3102
|
+
};
|
|
3103
|
+
|
|
940
3104
|
const projectShape = (value: any, shape: Shape): any => {
|
|
941
3105
|
const out: any = {};
|
|
942
3106
|
for (const [key, selector] of Object.entries(shape)) {
|