PageSourceSearch

https://pressthink.org/j/rosen-archive/frontend/services/searchIndexLoader.js?v=3.8.36

js pressthink.org collected 2026-10-02 04:27:04 UTC 3,735 bytes, 116 lines download raw bytes

1/**
2 * Load independent full-text indexes without letting one unavailable artifact
3 * discard siblings that loaded successfully.
4 */
5export function loadSearchIndexArtifact(serialized, options, loadJS) {
6  const { phrasePostings, ...artifact } = JSON.parse(serialized);
7  const index = loadJS(artifact, options);
8  if (phrasePostings && typeof phrasePostings === 'object') {
9    index.phrasePostings = new Map(
10      Object.entries(phrasePostings).map(([key, documentIds]) => {
11        const recordIds = documentIds.map((documentId) => {
12          if (!Object.hasOwn(artifact.documentIds, documentId)) {
13            throw new Error(`exact phrase posting ${key} references missing document ${documentId}`);
14          }
15          return artifact.documentIds[documentId];
16        });
17        return [key, new Set(recordIds)];
18      }),
19    );
20  }
21  return index;
22}
23
24export async function loadAvailableSearchIndexes(
25  specs,
26  { fetchImpl = globalThis.fetch, loadJSON } = {},
27) {
28  const settled = await Promise.allSettled(specs.map(async ({ url, options }) => {
29    const response = await fetchImpl(url);
30    if (!response.ok) {
31      throw new Error(`search index fetch failed (${url}): HTTP ${response.status}`);
32    }
33    return loadJSON(await response.text(), options);
34  }));
35
36  const loaded = [];
37  const failures = [];
38  settled.forEach((result, index) => {
39    if (result.status === 'fulfilled') {
40      loaded.push({ url: specs[index].url, index: result.value });
41      return;
42    }
43    failures.push({
44      url: specs[index].url,
45      error: result.reason instanceof Error ? result.reason : new Error(String(result.reason)),
46    });
47  });
48
49  if (loaded.length === 0) {
50    throw new AggregateError(
51      failures.map(failure => failure.error),
52      'all full-text search indexes failed to load',
53    );
54  }
55
56  return { indexes: loaded.map(entry => entry.index), loaded, failures };
57}
58
59/**
60 * Cache each successfully loaded index independently while retrying only the
61 * artifacts that remain unavailable. Concurrent callers share one attempt,
62 * and results always follow the original spec order even when recovery happens
63 * across multiple attempts.
64 */
65export function createResilientSearchIndexLoader(
66  specs,
67  { fetchImpl = globalThis.fetch, loadJSON } = {},
68) {
69  const loadedByUrl = new Map();
70  let inFlight = null;
71
72  const orderedIndexes = () => specs
73    .filter(spec => loadedByUrl.has(spec.url))
74    .map(spec => loadedByUrl.get(spec.url));
75
76  const load = () => {
77    if (inFlight) return inFlight;
78
79    const missingSpecs = specs.filter(spec => !loadedByUrl.has(spec.url));
80    if (missingSpecs.length === 0) {
81      return Promise.resolve({ indexes: orderedIndexes(), failures: [], complete: true });
82    }
83
84    inFlight = loadAvailableSearchIndexes(missingSpecs, { fetchImpl, loadJSON })
85      .then(({ loaded, failures }) => {
86        for (const entry of loaded) loadedByUrl.set(entry.url, entry.index);
87        const result = {
88          indexes: orderedIndexes(),
89          failures,
90          complete: loadedByUrl.size === specs.length,
91        };
92        if (!result.complete) inFlight = null;
93        return result;
94      })
95      .catch((error) => {
96        inFlight = null;
97        if (loadedByUrl.size > 0 && error instanceof AggregateError) {
98          return {
99            indexes: orderedIndexes(),
100            failures: missingSpecs.map((spec, index) => ({
101              url: spec.url,
102              error: error.errors[index] instanceof Error
103                ? error.errors[index]
104                : new Error(String(error.errors[index])),
105            })),
106            complete: false,
107          };
108        }
109        throw error;
110      });
111
112    return inFlight;
113  };
114
115  return { load };
116}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.