@peerbit/log 5.0.10 → 6.0.0-b712c6b

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/src/log.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { deserialize, field, fixedArray, variant } from "@dao-xyz/borsh";
2
2
  import { type AnyStore } from "@peerbit/any-store";
3
- import { type Blocks, cidifyString } from "@peerbit/blocks-interface";
3
+ import { type Blocks, type GetOptions, cidifyString } from "@peerbit/blocks-interface";
4
4
  import {
5
5
  type Identity,
6
6
  SignatureWithKey,
@@ -9,7 +9,6 @@ import {
9
9
  sha256Base64Sync,
10
10
  } from "@peerbit/crypto";
11
11
  import { type Indices } from "@peerbit/indexer-interface";
12
- import { create } from "@peerbit/indexer-sqlite3";
13
12
  import { type CryptoKeychain } from "@peerbit/keychain";
14
13
  import { type Change } from "./change.js";
15
14
  import {
@@ -38,6 +37,16 @@ import { Trim, type TrimOptions } from "./trim.js";
38
37
 
39
38
  const { LastWriteWins } = Sorting;
40
39
 
40
+ type CreateSqliteIndexer = typeof import("@peerbit/indexer-sqlite3").create;
41
+ let sqliteCreate: CreateSqliteIndexer | undefined;
42
+ const createDefaultIndexer = async (): Promise<Indices> => {
43
+ if (!sqliteCreate) {
44
+ const mod = await import("@peerbit/indexer-sqlite3");
45
+ sqliteCreate = mod.create;
46
+ }
47
+ return sqliteCreate();
48
+ };
49
+
41
50
  export type LogEvents<T> = {
42
51
  onChange?: (change: Change<T> /* , reference?: R */) => void;
43
52
  onGidRemoved?: (gids: string[]) => Promise<void> | void;
@@ -55,6 +64,10 @@ export type LogProperties<T> = {
55
64
  sortFn?: Sorting.SortFn;
56
65
  trim?: TrimOptions;
57
66
  canAppend?: CanAppend<T>;
67
+ resolveRemotePeers?: (
68
+ hash: string,
69
+ options?: { signal?: AbortSignal },
70
+ ) => Promise<string[] | undefined> | string[] | undefined;
58
71
  };
59
72
 
60
73
  export type LogOptions<T> = LogProperties<T> & LogEvents<T> & MemoryProperties;
@@ -159,13 +172,21 @@ export class Log<T> {
159
172
 
160
173
  this._closeController = new AbortController();
161
174
 
162
- const { encoding, trim, keychain, indexer, onGidRemoved, sortFn } = options;
175
+ const {
176
+ encoding,
177
+ trim,
178
+ keychain,
179
+ indexer,
180
+ onGidRemoved,
181
+ sortFn,
182
+ resolveRemotePeers,
183
+ } = options;
163
184
 
164
185
  // TODO do correctly with tie breaks
165
186
  this._sortFn = sortFn || LastWriteWins;
166
187
 
167
188
  this._storage = store;
168
- this._indexer = indexer || (await create());
189
+ this._indexer = indexer || (await createDefaultIndexer());
169
190
  await this._indexer.start?.();
170
191
 
171
192
  this._encoding = encoding || NO_ENCODING;
@@ -194,6 +215,7 @@ export class Log<T> {
194
215
  ).init({ schema: ShallowEntry }),
195
216
  publicKey: this._identity.publicKey,
196
217
  sort: this._sortFn,
218
+ resolveRemotePeers,
197
219
  });
198
220
  await this._entryIndex.init();
199
221
  /* this._values = new Values(this._entryIndex, this._sortFn); */
@@ -357,23 +379,15 @@ export class Log<T> {
357
379
  * Get an entry.
358
380
  * @param {string} [hash] The hashes of the entry
359
381
  */
360
- get(
361
- hash: string,
362
- options?: { remote?: { timeout?: number } | boolean },
363
- ): Promise<Entry<T> | undefined> {
382
+ get(hash: string, options?: GetOptions): Promise<Entry<T> | undefined> {
364
383
  return this._entryIndex.get(
365
384
  hash,
366
385
  options
367
386
  ? {
368
387
  type: "full",
369
- remote: options?.remote && {
370
- timeout:
371
- typeof options?.remote !== "boolean"
372
- ? options.remote.timeout
373
- : undefined,
374
- },
388
+ remote: options.remote,
375
389
  ignoreMissing: true, // always return undefined instead of throwing errors on missing entries
376
- }
390
+ }
377
391
  : { type: "full", ignoreMissing: true },
378
392
  );
379
393
  }
@@ -629,103 +643,128 @@ export class Log<T> {
629
643
  },
630
644
  ): Promise<void> {
631
645
  let entries: Entry<T>[];
632
- let references: Map<string, Entry<T>> = new Map();
646
+ const references: Map<string, Entry<T>> = new Map();
647
+
648
+ const fromCache = new Map<string, string[] | null>();
649
+ const resolveRemoteFrom = async (hash: string, signal?: AbortSignal) => {
650
+ const cached = fromCache.get(hash);
651
+ if (cached !== undefined) return cached === null ? undefined : cached;
652
+
653
+ let from: string[] | undefined;
654
+ try {
655
+ from = await this.entryIndex.properties.resolveRemotePeers?.(hash, { signal });
656
+ } catch {
657
+ from = undefined;
658
+ }
659
+ const normalized = from && from.length > 0 ? from : undefined;
660
+ fromCache.set(hash, normalized ?? null);
661
+ return normalized;
662
+ };
663
+
664
+ const remote: NonNullable<Exclude<GetOptions["remote"], boolean>> = {
665
+ timeout: options?.timeout,
666
+ signal: this._closeController.signal,
667
+ };
633
668
 
634
669
  if (entriesOrLog instanceof Log) {
635
670
  if (entriesOrLog.entryIndex.length === 0) return;
636
671
  entries = await entriesOrLog.toArray();
637
- for (const element of entries) {
638
- references.set(element.hash, element);
639
- }
672
+ for (const element of entries) references.set(element.hash, element);
640
673
  } else if (Array.isArray(entriesOrLog)) {
641
- if (entriesOrLog.length === 0) {
642
- return;
643
- }
674
+ if (entriesOrLog.length === 0) return;
644
675
 
645
676
  entries = [];
646
677
  for (const element of entriesOrLog) {
647
678
  if (element instanceof Entry) {
648
679
  entries.push(element);
649
680
  references.set(element.hash, element);
650
- } else if (typeof element === "string") {
651
- if ((await this.has(element)) && !options?.reset) {
681
+ continue;
682
+ }
683
+
684
+ if (typeof element === "string") {
685
+ if ((await this.entryIndex.getShallow(element)) != null && !options?.reset) {
652
686
  continue; // already in log
653
687
  }
654
688
 
655
- let entry = await Entry.fromMultihash<T>(this._storage, element, {
689
+ const from = await resolveRemoteFrom(element, this._closeController.signal);
690
+ const entry = await Entry.fromMultihash<T>(this._storage, element, {
656
691
  remote: {
657
- timeout: options?.timeout,
692
+ timeout: remote.timeout,
693
+ signal: remote.signal,
694
+ ...(from && from.length > 0 ? { from } : {}),
658
695
  },
659
696
  });
660
- if (!entry) {
661
- throw new Error("Missing entry in join by hash: " + element);
662
- }
663
697
  entries.push(entry);
664
- } else if (element instanceof ShallowEntry) {
665
- if ((await this.has(element.hash)) && !options?.reset) {
698
+ references.set(entry.hash, entry);
699
+ continue;
700
+ }
701
+
702
+ if (element instanceof ShallowEntry) {
703
+ if (
704
+ (await this.entryIndex.getShallow(element.hash)) != null &&
705
+ !options?.reset
706
+ ) {
666
707
  continue; // already in log
667
708
  }
668
709
 
669
- let entry = await Entry.fromMultihash<T>(
670
- this._storage,
710
+ const from = await resolveRemoteFrom(
671
711
  element.hash,
672
- {
673
- remote: {
674
- timeout: options?.timeout,
675
- },
676
- },
712
+ this._closeController.signal,
677
713
  );
678
- if (!entry) {
679
- throw new Error("Missing entry in join by hash: " + element.hash);
680
- }
714
+ const entry = await Entry.fromMultihash<T>(this._storage, element.hash, {
715
+ remote: {
716
+ timeout: remote.timeout,
717
+ signal: remote.signal,
718
+ ...(from && from.length > 0 ? { from } : {}),
719
+ },
720
+ });
681
721
  entries.push(entry);
682
- } else {
683
- entries.push(element.entry);
684
- references.set(element.entry.hash, element.entry);
722
+ references.set(entry.hash, entry);
723
+ continue;
724
+ }
685
725
 
686
- for (const ref of element.references) {
687
- references.set(ref.hash, ref);
688
- }
726
+ entries.push(element.entry);
727
+ references.set(element.entry.hash, element.entry);
728
+ for (const ref of element.references) {
729
+ references.set(ref.hash, ref);
689
730
  }
690
731
  }
691
732
  } else {
692
- let all = await entriesOrLog.all(); // TODO dont load all at once
693
- if (all.length === 0) {
694
- return;
695
- }
696
-
733
+ const all = await entriesOrLog.all(); // TODO dont load all at once
734
+ if (all.length === 0) return;
697
735
  entries = all;
698
736
  }
699
737
 
700
- let heads: Map<string, boolean> = new Map();
738
+ const heads: Map<string, boolean> = new Map();
701
739
  for (const entry of entries) {
702
- if (heads.has(entry.hash)) {
703
- continue;
704
- }
740
+ if (heads.has(entry.hash)) continue;
705
741
  heads.set(entry.hash, true);
706
- for (const next of await entry.getNext()) {
707
- heads.set(next, false);
708
- }
742
+ for (const next of await entry.getNext()) heads.set(next, false);
709
743
  }
710
744
 
711
745
  for (const entry of entries) {
712
- let isHead = heads.get(entry.hash)!;
713
- let prev = this._joining.get(entry.hash);
746
+ const isHead = heads.get(entry.hash)!;
747
+ const prev = this._joining.get(entry.hash);
714
748
  if (prev) {
715
749
  await prev;
716
- } else {
717
- const p = this.joinRecursively(entry, {
718
- references,
719
- isHead,
720
- ...options,
721
- });
722
-
723
- this._joining.set(entry.hash, p);
724
- p.finally(() => {
725
- this._joining.delete(entry.hash);
726
- });
727
- await p;
750
+ continue;
728
751
  }
752
+
753
+ const p = this.joinRecursively(entry, {
754
+ references,
755
+ isHead,
756
+ reset: options?.reset,
757
+ verifySignatures: options?.verifySignatures,
758
+ trim: options?.trim,
759
+ onChange: options?.onChange,
760
+ remote,
761
+ resolveRemoteFrom,
762
+ });
763
+ this._joining.set(entry.hash, p);
764
+ p.finally(() => {
765
+ this._joining.delete(entry.hash);
766
+ });
767
+ await p;
729
768
  }
730
769
  }
731
770
 
@@ -746,12 +785,14 @@ export class Log<T> {
746
785
  isHead: boolean;
747
786
  reset?: boolean;
748
787
  onChange?: OnChange<T>;
749
- remote?: {
750
- timeout?: number;
751
- };
788
+ remote?: GetOptions["remote"];
789
+ resolveRemoteFrom?: (
790
+ hash: string,
791
+ signal?: AbortSignal,
792
+ ) => Promise<string[] | undefined>;
752
793
  },
753
794
  ): Promise<boolean> {
754
- if (this.entryIndex.length > (options?.length ?? Number.MAX_SAFE_INTEGER)) {
795
+ if (this.entryIndex.length > (options.length ?? Number.MAX_SAFE_INTEGER)) {
755
796
  return false;
756
797
  }
757
798
 
@@ -759,17 +800,15 @@ export class Log<T> {
759
800
  throw new Error("Unexpected");
760
801
  }
761
802
 
762
- if ((await this.has(entry.hash)) && !options.reset) {
803
+ if ((await this.entryIndex.getShallow(entry.hash)) != null && !options.reset) {
763
804
  return false;
764
805
  }
765
806
 
766
807
  entry.init(this);
767
808
 
768
- if (options?.verifySignatures) {
809
+ if (options.verifySignatures) {
769
810
  if (!(await entry.verifySignatures())) {
770
- throw new Error(
771
- 'Invalid signature entry with hash "' + entry.hash + '"',
772
- );
811
+ throw new Error(`Invalid signature entry with hash "${entry.hash}"`);
773
812
  }
774
813
  }
775
814
 
@@ -791,34 +830,45 @@ export class Log<T> {
791
830
  }
792
831
 
793
832
  if (entry.meta.type !== EntryType.CUT) {
833
+ const remote =
834
+ options.remote && typeof options.remote === "object"
835
+ ? options.remote
836
+ : undefined;
837
+
794
838
  for (const a of entry.meta.next) {
795
839
  const prev = this._joining.get(a);
796
840
  if (prev) {
797
841
  await prev;
798
- } else if (!(await this.has(a)) || options.reset) {
799
- const nested =
800
- options.references?.get(a) ||
801
- (await Entry.fromMultihash<T>(this._storage, a, {
802
- remote: { timeout: options?.remote?.timeout },
803
- }));
804
- if (!nested) {
805
- throw new Error("Missing entry in joinRecursively: " + a);
806
- }
807
-
808
- const p = this.joinRecursively(
809
- nested,
810
- options.isHead ? { ...options, isHead: false } : options,
811
- );
812
- this._joining.set(nested.hash, p);
813
- p.finally(() => {
814
- this._joining.delete(nested.hash);
815
- });
816
- await p;
842
+ continue;
817
843
  }
844
+ if ((await this.entryIndex.getShallow(a)) != null && !options.reset) {
845
+ continue;
846
+ }
847
+
848
+ const from = await options.resolveRemoteFrom?.(a, remote?.signal);
849
+ const nested =
850
+ options.references?.get(a) ??
851
+ (await Entry.fromMultihash<T>(this._storage, a, {
852
+ remote: {
853
+ timeout: remote?.timeout,
854
+ signal: remote?.signal,
855
+ ...(from && from.length > 0 ? { from } : {}),
856
+ },
857
+ }));
858
+
859
+ const p = this.joinRecursively(
860
+ nested,
861
+ options.isHead ? { ...options, isHead: false } : options,
862
+ );
863
+ this._joining.set(nested.hash, p);
864
+ p.finally(() => {
865
+ this._joining.delete(nested.hash);
866
+ });
867
+ await p;
818
868
  }
819
869
  }
820
870
 
821
- if (this?._canAppend && !(await this?._canAppend(entry))) {
871
+ if (this._canAppend && !(await this._canAppend(entry))) {
822
872
  return false;
823
873
  }
824
874
 
@@ -835,23 +885,23 @@ export class Log<T> {
835
885
  | PendingDelete<T>
836
886
  | { entry: Entry<T>; fn: undefined }
837
887
  )[] = await this.processEntry(entry);
838
- const trimmed = await this.trim(options?.trim);
888
+ const trimmed = await this.trim(options.trim);
839
889
 
840
890
  if (trimmed) {
841
- for (const entry of trimmed) {
842
- pendingDeletes.push({ entry, fn: undefined });
891
+ for (const removedEntry of trimmed) {
892
+ pendingDeletes.push({ entry: removedEntry, fn: undefined });
843
893
  }
844
894
  }
845
895
 
846
896
  const removed = pendingDeletes.map((x) => x.entry);
847
897
 
848
- await options?.onChange?.({
898
+ await options.onChange?.({
849
899
  added: [{ head: options.isHead, entry }],
850
- removed: removed,
900
+ removed,
851
901
  });
852
902
  await this._onChange?.({
853
903
  added: [{ head: options.isHead, entry }],
854
- removed: removed,
904
+ removed,
855
905
  });
856
906
 
857
907
  await Promise.all(pendingDeletes.map((x) => x.fn?.()));