@yoch/frozenminisearch 1.0.1 → 1.1.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/es/index.js CHANGED
@@ -1934,7 +1934,7 @@ function postingsTypedBytes(layout) {
1934
1934
  slotCount,
1935
1935
  };
1936
1936
  }
1937
- function validateFrozenPostingsLayout(layout, documentCount, nextId, fail = detail => { throw new Error(detail); }) {
1937
+ function validateFrozenPostingsLayout(layout, documentCount, nextId, fail = (detail) => { throw new Error(detail); }) {
1938
1938
  if (layout.fieldCount <= 0)
1939
1939
  fail('fieldCount must be positive');
1940
1940
  if (layout.nextId !== nextId)
@@ -2016,24 +2016,33 @@ function findSparseSlotByFieldId(fieldIds, start, end, fieldId) {
2016
2016
  }
2017
2017
  return -1;
2018
2018
  }
2019
- /** Resolve one (termIndex, fieldId) posting run in flat buffers; shared by flyweight and docId collect. */
2020
- function frozenPostingSlice(layout, termIndex, fieldId) {
2019
+ /** Reusable scratch for {@link resolvePostingSlice} (scoring is synchronous). */
2020
+ const postingSliceScratch = { offset: 0, length: 0 };
2021
+ /**
2022
+ * Resolve one (termIndex, fieldId) posting run in flat buffers; writes into `out` without allocating.
2023
+ * @returns false when the slot is empty or missing
2024
+ */
2025
+ function resolvePostingSlice(layout, termIndex, fieldId, out) {
2021
2026
  if (layout.layout === 'dense') {
2022
2027
  const base = termIndex * layout.fieldCount + fieldId;
2023
2028
  const len = layout.denseLengths[base];
2024
2029
  if (len === 0)
2025
- return undefined;
2026
- return { offset: layout.denseOffsets[base], length: len };
2030
+ return false;
2031
+ out.offset = layout.denseOffsets[base];
2032
+ out.length = len;
2033
+ return true;
2027
2034
  }
2028
2035
  const start = layout.sparseTermStarts[termIndex];
2029
2036
  const end = layout.sparseTermStarts[termIndex + 1];
2030
2037
  const slot = findSparseSlotByFieldId(layout.sparseFieldIds, start, end, fieldId);
2031
2038
  if (slot < 0)
2032
- return undefined;
2039
+ return false;
2033
2040
  const len = layout.sparseLengths[slot];
2034
2041
  if (len === 0)
2035
- return undefined;
2036
- return { offset: layout.sparseOffsets[slot], length: len };
2042
+ return false;
2043
+ out.offset = layout.sparseOffsets[slot];
2044
+ out.length = len;
2045
+ return true;
2037
2046
  }
2038
2047
  /**
2039
2048
  * One flyweight wrapper for the lifetime of a frozen index. Call {@link bind} before each
@@ -2049,10 +2058,9 @@ function createFrozenFieldTermFlyweight(layout) {
2049
2058
  return flyweight;
2050
2059
  },
2051
2060
  get(fieldId) {
2052
- const slice = frozenPostingSlice(layout, termIndex, fieldId);
2053
- if (slice == null)
2061
+ if (!resolvePostingSlice(layout, termIndex, fieldId, postingSliceScratch))
2054
2062
  return undefined;
2055
- return segment.rebind(slice.offset, slice.length);
2063
+ return segment.rebind(postingSliceScratch.offset, postingSliceScratch.length);
2056
2064
  },
2057
2065
  };
2058
2066
  return flyweight;
@@ -2071,10 +2079,9 @@ function collectDocIdsFromFrozenSegment(allDocIds, offset, length, context, docI
2071
2079
  function collectDocIdsFromFrozenLayout(layout, termIndex, fieldBoosts, context, docIds, allowedDocs) {
2072
2080
  const { fieldIds } = context;
2073
2081
  for (const field of fieldBoosts.names) {
2074
- const slice = frozenPostingSlice(layout, termIndex, fieldIds[field]);
2075
- if (slice == null)
2082
+ if (!resolvePostingSlice(layout, termIndex, fieldIds[field], postingSliceScratch))
2076
2083
  continue;
2077
- collectDocIdsFromFrozenSegment(layout.allDocIds, slice.offset, slice.length, context, docIds, allowedDocs);
2084
+ collectDocIdsFromFrozenSegment(layout.allDocIds, postingSliceScratch.offset, postingSliceScratch.length, context, docIds, allowedDocs);
2078
2085
  }
2079
2086
  }
2080
2087
 
@@ -2198,7 +2205,7 @@ function forEachDefaultToken(text, onToken) {
2198
2205
  /** Default tokenizer into a reusable buffer (avoids `text.split()` array allocation). */
2199
2206
  function tokenizeDefaultInto(out, text) {
2200
2207
  out.length = 0;
2201
- forEachDefaultToken(text, (token) => out.push(token));
2208
+ forEachDefaultToken(text, token => out.push(token));
2202
2209
  }
2203
2210
  /** Tokenize field text into `out` (reused). Fast path when `tokenize` is the library default. */
2204
2211
  function tokenizeFieldInto(out, tokenize, text, fieldName) {
@@ -2228,22 +2235,387 @@ function collectFieldTermFreqsFromFieldInto(localFreqs, tokenScratch, tokenize,
2228
2235
  tokenizeFieldInto(tokenScratch, tokenize, text, fieldName);
2229
2236
  return collectFieldTermFreqsInto(localFreqs, tokenScratch, fieldName, processTerm);
2230
2237
  }
2231
- /** Same running average as {@link MiniSearch} private addFieldLength. */
2232
2238
  function updateAvgFieldLength(avgFieldLength, fieldId, count, length) {
2233
2239
  const averageFieldLength = avgFieldLength[fieldId] || 0;
2234
2240
  const totalFieldLength = (averageFieldLength * count) + length;
2235
2241
  avgFieldLength[fieldId] = totalFieldLength / (count + 1);
2236
2242
  }
2237
- function saveStoredFieldsForDocument(storeFields, extractField, document) {
2243
+
2244
+ function validateTreeShape(shape, termCount) {
2245
+ if (!Array.isArray(shape)) {
2246
+ throw invalidFrozenIndex('treeShape node must be an array');
2247
+ }
2248
+ for (const entry of shape) {
2249
+ if (!Array.isArray(entry) || entry.length !== 2) {
2250
+ throw invalidFrozenIndex('treeShape entry must be a [key, value] pair');
2251
+ }
2252
+ const [key, value] = entry;
2253
+ if (key === LEAF) {
2254
+ const idx = value;
2255
+ if (!Number.isInteger(idx) || idx < 0 || idx >= termCount) {
2256
+ throw invalidFrozenIndex(`treeShape leaf term index out of range: ${idx}`);
2257
+ }
2258
+ }
2259
+ else {
2260
+ validateTreeShape(value, termCount);
2261
+ }
2262
+ }
2263
+ }
2264
+ function termCountOf(snap) {
2265
+ return snap.postings.termCount;
2266
+ }
2267
+ /**
2268
+ * Numeric/structural invariants shared by both the decode path (untrusted binary)
2269
+ * and the build path (trusted internal code).
2270
+ */
2271
+ function validateFrozenSnapshotNumeric(snap) {
2272
+ if (snap.fieldCount <= 0) {
2273
+ throw invalidFrozenIndex('fieldCount must be positive');
2274
+ }
2275
+ if (snap.nextId < 0 || snap.nextId >= 0xffffffff) {
2276
+ throw invalidFrozenIndex('nextId out of range');
2277
+ }
2278
+ if (snap.documentCount < 0 || snap.documentCount > snap.nextId) {
2279
+ throw invalidFrozenIndex('documentCount inconsistent with nextId');
2280
+ }
2281
+ if (snap.fieldLengthMatrix.length !== snap.nextId * snap.fieldCount) {
2282
+ throw invalidFrozenIndex('fieldLengthMatrix size mismatch');
2283
+ }
2284
+ if (snap.avgFieldLength.length !== snap.fieldCount) {
2285
+ throw invalidFrozenIndex('avgFieldLength size mismatch');
2286
+ }
2287
+ validateFrozenPostingsLayout(snap.postings, snap.documentCount, snap.nextId, (detail) => {
2288
+ throw invalidFrozenIndex(detail);
2289
+ });
2290
+ const indexedFields = Object.keys(snap.fieldIds);
2291
+ if (indexedFields.length !== snap.fieldCount) {
2292
+ throw invalidFrozenIndex('fieldIds count mismatch');
2293
+ }
2294
+ for (let f = 0; f < snap.fieldCount; f++) {
2295
+ const found = indexedFields.some(name => snap.fieldIds[name] === f);
2296
+ if (!found) {
2297
+ throw invalidFrozenIndex(`missing field id ${f}`);
2298
+ }
2299
+ }
2300
+ }
2301
+ function readFieldNamesSection(buf, fieldNamesOff, fieldCount, externalIdsOff) {
2302
+ const fieldNames = [];
2303
+ let o = fieldNamesOff;
2304
+ for (let f = 0; f < fieldCount; f++) {
2305
+ const { value, next } = readLengthPrefixedUtf8(buf, o);
2306
+ fieldNames.push(value);
2307
+ o = next;
2308
+ }
2309
+ if (o !== externalIdsOff) {
2310
+ throw invalidFrozenIndex('field names section size mismatch');
2311
+ }
2312
+ return fieldNames;
2313
+ }
2314
+ function readExternalIdsSection(buf, externalIdsOff, nextId, storedOff) {
2315
+ const externalIds = new Array(nextId);
2316
+ let o = externalIdsOff;
2317
+ for (let i = 0; i < nextId; i++) {
2318
+ const { value, next } = readExternalId(buf, o);
2319
+ externalIds[i] = value;
2320
+ o = next;
2321
+ }
2322
+ if (o !== storedOff) {
2323
+ throw invalidFrozenIndex('external ids section size mismatch');
2324
+ }
2325
+ return externalIds;
2326
+ }
2327
+ function readStoredFieldsSection(buf, storedOff, nextId, sectionEnd) {
2328
+ const storedFields = new Array(nextId);
2329
+ const tableEnd = storedOff + nextId * 4;
2330
+ if (tableEnd > sectionEnd) {
2331
+ throw invalidFrozenIndex('stored fields table out of bounds');
2332
+ }
2333
+ for (let i = 0; i < nextId; i++) {
2334
+ const rel = buf.readUInt32LE(storedOff + i * 4);
2335
+ if (rel === 0) {
2336
+ storedFields[i] = undefined;
2337
+ continue;
2338
+ }
2339
+ const entryOff = tableEnd + rel - 1;
2340
+ if (entryOff + 4 > sectionEnd) {
2341
+ throw invalidFrozenIndex('stored fields entry offset out of bounds');
2342
+ }
2343
+ const jsonLen = buf.readUInt32LE(entryOff);
2344
+ const jsonStart = entryOff + 4;
2345
+ const jsonEnd = jsonStart + jsonLen;
2346
+ if (jsonEnd > sectionEnd) {
2347
+ throw invalidFrozenIndex('stored fields JSON out of bounds');
2348
+ }
2349
+ storedFields[i] = JSON.parse(buf.toString('utf8', jsonStart, jsonEnd));
2350
+ }
2351
+ return storedFields;
2352
+ }
2353
+ /** Validate structural invariants of a decoded or assembled frozen snapshot. */
2354
+ function validateFrozenSnapshot(snap) {
2355
+ validateFrozenSnapshotNumeric(snap);
2356
+ const termCount = termCountOf(snap);
2357
+ if (snap.packedTermIndex != null) {
2358
+ validateFrozenTermIndexLeaves(snap.packedTermIndex, termCount);
2359
+ }
2360
+ else if (snap.termTree != null) {
2361
+ validateTermTreeLeaves(snap.termTree, termCount);
2362
+ }
2363
+ else {
2364
+ validateTreeShape(snap.treeShape, termCount);
2365
+ }
2366
+ }
2367
+ function fieldNamesFromFieldIds(fieldIds) {
2368
+ const names = Object.keys(fieldIds);
2369
+ names.sort((a, b) => fieldIds[a] - fieldIds[b]);
2370
+ return names;
2371
+ }
2372
+ /** Core with explicit {@link termCountOf} (no dictionary section). */
2373
+ function buildCoreSectionWithTermCount(snap) {
2374
+ const out = Buffer.alloc(16);
2375
+ out.writeUInt32LE(snap.documentCount, 0);
2376
+ out.writeUInt32LE(snap.nextId, 4);
2377
+ out.writeUInt32LE(snap.fieldCount, 8);
2378
+ out.writeUInt32LE(termCountOf(snap), 12);
2379
+ return out;
2380
+ }
2381
+ function buildFieldNamesSection(fieldNames) {
2382
+ const chunks = [];
2383
+ for (const name of fieldNames) {
2384
+ const body = Buffer.from(name, 'utf8');
2385
+ const header = Buffer.alloc(4);
2386
+ header.writeUInt32LE(body.length, 0);
2387
+ chunks.push(header, body);
2388
+ }
2389
+ return Buffer.concat(chunks);
2390
+ }
2391
+ function buildExternalIdsSection(externalIds, nextId) {
2392
+ const chunks = [];
2393
+ for (let i = 0; i < nextId; i++) {
2394
+ writeExternalId(chunks, externalIds[i]);
2395
+ }
2396
+ return Buffer.concat(chunks);
2397
+ }
2398
+ function buildStoredFieldsSection(storedFields, nextId) {
2399
+ const table = Buffer.alloc(nextId * 4);
2400
+ const heapChunks = [];
2401
+ let heapOff = 0;
2402
+ for (let i = 0; i < nextId; i++) {
2403
+ const row = storedFields[i];
2404
+ if (row == null) {
2405
+ table.writeUInt32LE(0, i * 4);
2406
+ continue;
2407
+ }
2408
+ table.writeUInt32LE(heapOff + 1, i * 4);
2409
+ const json = Buffer.from(JSON.stringify(row), 'utf8');
2410
+ const entry = Buffer.alloc(4 + json.length);
2411
+ entry.writeUInt32LE(json.length, 0);
2412
+ json.copy(entry, 4);
2413
+ heapChunks.push(entry);
2414
+ heapOff += entry.length;
2415
+ }
2416
+ return Buffer.concat([table, ...heapChunks]);
2417
+ }
2418
+ function validateTermTreeLeaves(tree, termCount) {
2419
+ for (const [key, val] of tree) {
2420
+ if (key === LEAF) {
2421
+ const idx = val;
2422
+ if (!Number.isInteger(idx) || idx < 0 || idx >= termCount) {
2423
+ throw invalidFrozenIndex(`term tree leaf index out of range: ${idx}`);
2424
+ }
2425
+ }
2426
+ else {
2427
+ validateTermTreeLeaves(val, termCount);
2428
+ }
2429
+ }
2430
+ }
2431
+ function deserializeTermIndexTree(shape) {
2432
+ const tree = new Map();
2433
+ for (const [key, value] of shape) {
2434
+ if (key === LEAF) {
2435
+ tree.set(LEAF, value);
2436
+ }
2437
+ else {
2438
+ tree.set(key, deserializeTermIndexTree(value));
2439
+ }
2440
+ }
2441
+ return tree;
2442
+ }
2443
+
2444
+ /**
2445
+ * Runtime stored fields. Single store field → one column (no per-doc Record at rest).
2446
+ * Wire format stays row JSON; encode/decode can skip intermediate row arrays when layout is known.
2447
+ */
2448
+ function createStoredFieldsLayout(storeFields, capacity = 0) {
2238
2449
  if (storeFields.length === 0)
2450
+ return { kind: 'none' };
2451
+ if (storeFields.length === 1) {
2452
+ return { kind: 'single', field: storeFields[0], values: new Array(capacity) };
2453
+ }
2454
+ return { kind: 'multi', rows: new Array(capacity) };
2455
+ }
2456
+ function writeStoredField(layout, shortId, storeFields, extractField, document) {
2457
+ if (layout.kind === 'none')
2458
+ return;
2459
+ if (layout.kind === 'single') {
2460
+ layout.values[shortId] = extractField(document, layout.field);
2461
+ return;
2462
+ }
2463
+ const row = {};
2464
+ for (const name of storeFields) {
2465
+ const value = extractField(document, name);
2466
+ if (value !== undefined)
2467
+ row[name] = value;
2468
+ }
2469
+ layout.rows[shortId] = row;
2470
+ }
2471
+ /** Materialize API/wire row for one document. */
2472
+ function readStoredFields(layout, shortId) {
2473
+ if (layout.kind === 'none')
2239
2474
  return undefined;
2240
- const documentFields = {};
2241
- for (const fieldName of storeFields) {
2242
- const fieldValue = extractField(document, fieldName);
2243
- if (fieldValue !== undefined)
2244
- documentFields[fieldName] = fieldValue;
2475
+ if (layout.kind === 'multi')
2476
+ return layout.rows[shortId];
2477
+ const value = layout.values[shortId];
2478
+ if (value === undefined)
2479
+ return {};
2480
+ return { [layout.field]: value };
2481
+ }
2482
+ function resizeStoredFields(layout, length) {
2483
+ if (layout.kind === 'none')
2484
+ return layout;
2485
+ if (layout.kind === 'single') {
2486
+ return layout.values.length <= length
2487
+ ? layout
2488
+ : { kind: 'single', field: layout.field, values: layout.values.slice(0, length) };
2489
+ }
2490
+ return layout.rows.length <= length
2491
+ ? layout
2492
+ : { kind: 'multi', rows: layout.rows.slice(0, length) };
2493
+ }
2494
+ function cloneStoredFields(layout) {
2495
+ if (layout.kind === 'none')
2496
+ return layout;
2497
+ if (layout.kind === 'single') {
2498
+ return { kind: 'single', field: layout.field, values: layout.values.slice() };
2499
+ }
2500
+ return { kind: 'multi', rows: layout.rows.slice() };
2501
+ }
2502
+ /** Import from wire rows or MiniSearch snapshot. Empty storeFields + non-empty rows → multi (binary load without options). */
2503
+ function storedFieldsFromRows(rows, storeFields) {
2504
+ if (storeFields.length === 0) {
2505
+ const hasAny = rows.some(row => row != null && Object.keys(row).length > 0);
2506
+ return hasAny ? { kind: 'multi', rows } : { kind: 'none' };
2507
+ }
2508
+ if (storeFields.length === 1) {
2509
+ const field = storeFields[0];
2510
+ const values = rows.map(row => row === null || row === void 0 ? void 0 : row[field]);
2511
+ return { kind: 'single', field, values };
2512
+ }
2513
+ return { kind: 'multi', rows };
2514
+ }
2515
+ function storedFieldsJsonBytes(layout) {
2516
+ if (layout.kind === 'none')
2517
+ return 0;
2518
+ if (layout.kind === 'multi') {
2519
+ let total = 0;
2520
+ for (const row of layout.rows) {
2521
+ if (row != null)
2522
+ total += JSON.stringify(row).length;
2523
+ }
2524
+ return total;
2525
+ }
2526
+ let total = 0;
2527
+ const { field, values } = layout;
2528
+ for (let i = 0; i < values.length; i++) {
2529
+ const value = values[i];
2530
+ if (value !== undefined)
2531
+ total += JSON.stringify({ [field]: value }).length;
2532
+ }
2533
+ return total;
2534
+ }
2535
+ function storedFieldsSlotCount(layout) {
2536
+ if (layout.kind === 'none')
2537
+ return 0;
2538
+ return layout.kind === 'single' ? layout.values.length : layout.rows.length;
2539
+ }
2540
+ function appendStoredFieldJsonEntry(table, heapChunks, heapOffRef, docIndex, jsonUtf8) {
2541
+ table.writeUInt32LE(heapOffRef.value + 1, docIndex * 4);
2542
+ const entry = Buffer.alloc(4 + jsonUtf8.length);
2543
+ entry.writeUInt32LE(jsonUtf8.length, 0);
2544
+ jsonUtf8.copy(entry, 4);
2545
+ heapChunks.push(entry);
2546
+ heapOffRef.value += entry.length;
2547
+ }
2548
+ /** MSv5 StoredFields section from {@link StoredFieldsLayout} (no intermediate row array). */
2549
+ function buildStoredFieldsWireSection(layout, nextId) {
2550
+ if (layout.kind === 'multi') {
2551
+ const rows = layout.rows.length >= nextId
2552
+ ? layout.rows
2553
+ : layout.rows.concat(new Array(nextId - layout.rows.length));
2554
+ return buildStoredFieldsSection(rows, nextId);
2555
+ }
2556
+ const table = Buffer.alloc(nextId * 4);
2557
+ if (layout.kind === 'none')
2558
+ return table;
2559
+ const heapChunks = [];
2560
+ const heapOffRef = { value: 0 };
2561
+ const { field, values } = layout;
2562
+ for (let i = 0; i < nextId; i++) {
2563
+ const value = values[i];
2564
+ if (value === undefined)
2565
+ continue;
2566
+ const jsonUtf8 = Buffer.from(JSON.stringify({ [field]: value }), 'utf8');
2567
+ appendStoredFieldJsonEntry(table, heapChunks, heapOffRef, i, jsonUtf8);
2245
2568
  }
2246
- return documentFields;
2569
+ return heapChunks.length === 0 ? table : Buffer.concat([table, ...heapChunks]);
2570
+ }
2571
+ function storedFieldsTableEnd(storedOff, nextId, sectionEnd) {
2572
+ const tableEnd = storedOff + nextId * 4;
2573
+ if (tableEnd > sectionEnd) {
2574
+ throw invalidFrozenIndex('stored fields table out of bounds');
2575
+ }
2576
+ return tableEnd;
2577
+ }
2578
+ function readStoredFieldJsonAt(buf, tableEnd, sectionEnd, rel) {
2579
+ const entryOff = tableEnd + rel - 1;
2580
+ if (entryOff + 4 > sectionEnd) {
2581
+ throw invalidFrozenIndex('stored fields entry offset out of bounds');
2582
+ }
2583
+ const jsonLen = buf.readUInt32LE(entryOff);
2584
+ const jsonStart = entryOff + 4;
2585
+ const jsonEnd = jsonStart + jsonLen;
2586
+ if (jsonEnd > sectionEnd) {
2587
+ throw invalidFrozenIndex('stored fields JSON out of bounds');
2588
+ }
2589
+ return JSON.parse(buf.toString('utf8', jsonStart, jsonEnd));
2590
+ }
2591
+ /** MSv5 StoredFields section → layout (skips row materialization when storeFields hint allows). */
2592
+ function readStoredFieldsWireSection(buf, storedOff, nextId, sectionEnd, storeFields) {
2593
+ const tableEnd = storedFieldsTableEnd(storedOff, nextId, sectionEnd);
2594
+ if (storeFields.length === 1) {
2595
+ const field = storeFields[0];
2596
+ const values = new Array(nextId);
2597
+ for (let i = 0; i < nextId; i++) {
2598
+ const rel = buf.readUInt32LE(storedOff + i * 4);
2599
+ if (rel === 0)
2600
+ continue;
2601
+ const row = readStoredFieldJsonAt(buf, tableEnd, sectionEnd, rel);
2602
+ values[i] = row[field];
2603
+ }
2604
+ return { kind: 'single', field, values };
2605
+ }
2606
+ if (storeFields.length === 0) {
2607
+ let hasAny = false;
2608
+ for (let i = 0; i < nextId; i++) {
2609
+ if (buf.readUInt32LE(storedOff + i * 4) !== 0) {
2610
+ hasAny = true;
2611
+ break;
2612
+ }
2613
+ }
2614
+ if (!hasAny)
2615
+ return { kind: 'none' };
2616
+ }
2617
+ const rows = readStoredFieldsSection(buf, storedOff, nextId, sectionEnd);
2618
+ return storedFieldsFromRows(rows, storeFields);
2247
2619
  }
2248
2620
 
2249
2621
  const SUPPORTED_SERIALIZATION_VERSIONS = new Set([1, 2]);
@@ -2311,7 +2683,7 @@ function buildFlatPostingsFromSearchableMap(searchableMap, fieldCount, nextId, s
2311
2683
  });
2312
2684
  return { termCount, index: packedIndex, postings };
2313
2685
  }
2314
- /** Build frozen assemble params from a lucaong MiniSearch JSON snapshot. */
2686
+ /** Build frozen assemble params from a MiniSearch JSON snapshot. */
2315
2687
  function buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options) {
2316
2688
  var _a, _b, _c;
2317
2689
  if (!SUPPORTED_SERIALIZATION_VERSIONS.has(snapshot.serializationVersion)) {
@@ -2329,7 +2701,7 @@ function buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options) {
2329
2701
  let shortIdRemap = null;
2330
2702
  const resolvedNextId = useDense ? documentCount : nextId;
2331
2703
  const externalIds = new Array(resolvedNextId);
2332
- const storedFields = new Array(externalIds.length);
2704
+ const storedFieldRows = new Array(externalIds.length);
2333
2705
  if (useDense) {
2334
2706
  shortIdRemap = new Uint32Array(nextId);
2335
2707
  shortIdRemap.fill(DISCARDED_DOC_ID);
@@ -2341,7 +2713,7 @@ function buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options) {
2341
2713
  const shortIdStr = String(shortId);
2342
2714
  shortIdRemap[shortId] = dense;
2343
2715
  externalIds[dense] = snapshot.documentIds[shortIdStr];
2344
- storedFields[dense] = snapshot.storedFields[shortIdStr];
2716
+ storedFieldRows[dense] = snapshot.storedFields[shortIdStr];
2345
2717
  dense++;
2346
2718
  }
2347
2719
  }
@@ -2349,7 +2721,7 @@ function buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options) {
2349
2721
  for (const [shortIdStr, id] of Object.entries(snapshot.documentIds)) {
2350
2722
  const shortId = parseInt(shortIdStr, 10);
2351
2723
  externalIds[shortId] = id;
2352
- storedFields[shortId] = snapshot.storedFields[shortIdStr];
2724
+ storedFieldRows[shortId] = snapshot.storedFields[shortIdStr];
2353
2725
  }
2354
2726
  }
2355
2727
  const idLookup = createIdToShortIdLookup(externalIds, resolvedNextId);
@@ -2372,6 +2744,7 @@ function buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options) {
2372
2744
  }
2373
2745
  const searchableMap = buildSearchableMapFromSnapshot(snapshot);
2374
2746
  const flat = buildFlatPostingsFromSearchableMap(searchableMap, fieldCount, resolvedNextId, shortIdRemap);
2747
+ const storedFields = storedFieldsFromRows(storedFieldRows, opts.storeFields);
2375
2748
  return {
2376
2749
  options: opts,
2377
2750
  documentCount,
@@ -2790,206 +3163,6 @@ function readMsv5GlobalFlags(buf) {
2790
3163
  return buf.readUInt16LE(6);
2791
3164
  }
2792
3165
 
2793
- function validateTreeShape(shape, termCount) {
2794
- if (!Array.isArray(shape)) {
2795
- throw invalidFrozenIndex('treeShape node must be an array');
2796
- }
2797
- for (const entry of shape) {
2798
- if (!Array.isArray(entry) || entry.length !== 2) {
2799
- throw invalidFrozenIndex('treeShape entry must be a [key, value] pair');
2800
- }
2801
- const [key, value] = entry;
2802
- if (key === LEAF) {
2803
- const idx = value;
2804
- if (!Number.isInteger(idx) || idx < 0 || idx >= termCount) {
2805
- throw invalidFrozenIndex(`treeShape leaf term index out of range: ${idx}`);
2806
- }
2807
- }
2808
- else {
2809
- validateTreeShape(value, termCount);
2810
- }
2811
- }
2812
- }
2813
- function termCountOf(snap) {
2814
- return snap.postings.termCount;
2815
- }
2816
- /**
2817
- * Numeric/structural invariants shared by both the decode path (untrusted binary)
2818
- * and the build path (trusted internal code).
2819
- */
2820
- function validateFrozenSnapshotNumeric(snap) {
2821
- if (snap.fieldCount <= 0) {
2822
- throw invalidFrozenIndex('fieldCount must be positive');
2823
- }
2824
- if (snap.nextId < 0 || snap.nextId >= 0xffffffff) {
2825
- throw invalidFrozenIndex('nextId out of range');
2826
- }
2827
- if (snap.documentCount < 0 || snap.documentCount > snap.nextId) {
2828
- throw invalidFrozenIndex('documentCount inconsistent with nextId');
2829
- }
2830
- if (snap.fieldLengthMatrix.length !== snap.nextId * snap.fieldCount) {
2831
- throw invalidFrozenIndex('fieldLengthMatrix size mismatch');
2832
- }
2833
- if (snap.avgFieldLength.length !== snap.fieldCount) {
2834
- throw invalidFrozenIndex('avgFieldLength size mismatch');
2835
- }
2836
- validateFrozenPostingsLayout(snap.postings, snap.documentCount, snap.nextId, detail => {
2837
- throw invalidFrozenIndex(detail);
2838
- });
2839
- const indexedFields = Object.keys(snap.fieldIds);
2840
- if (indexedFields.length !== snap.fieldCount) {
2841
- throw invalidFrozenIndex('fieldIds count mismatch');
2842
- }
2843
- for (let f = 0; f < snap.fieldCount; f++) {
2844
- const found = indexedFields.some(name => snap.fieldIds[name] === f);
2845
- if (!found) {
2846
- throw invalidFrozenIndex(`missing field id ${f}`);
2847
- }
2848
- }
2849
- }
2850
- function readFieldNamesSection(buf, fieldNamesOff, fieldCount, externalIdsOff) {
2851
- const fieldNames = [];
2852
- let o = fieldNamesOff;
2853
- for (let f = 0; f < fieldCount; f++) {
2854
- const { value, next } = readLengthPrefixedUtf8(buf, o);
2855
- fieldNames.push(value);
2856
- o = next;
2857
- }
2858
- if (o !== externalIdsOff) {
2859
- throw invalidFrozenIndex('field names section size mismatch');
2860
- }
2861
- return fieldNames;
2862
- }
2863
- function readExternalIdsSection(buf, externalIdsOff, nextId, storedOff) {
2864
- const externalIds = new Array(nextId);
2865
- let o = externalIdsOff;
2866
- for (let i = 0; i < nextId; i++) {
2867
- const { value, next } = readExternalId(buf, o);
2868
- externalIds[i] = value;
2869
- o = next;
2870
- }
2871
- if (o !== storedOff) {
2872
- throw invalidFrozenIndex('external ids section size mismatch');
2873
- }
2874
- return externalIds;
2875
- }
2876
- function readStoredFieldsSection(buf, storedOff, nextId, sectionEnd) {
2877
- const storedFields = new Array(nextId);
2878
- const tableEnd = storedOff + nextId * 4;
2879
- if (tableEnd > sectionEnd) {
2880
- throw invalidFrozenIndex('stored fields table out of bounds');
2881
- }
2882
- for (let i = 0; i < nextId; i++) {
2883
- const rel = buf.readUInt32LE(storedOff + i * 4);
2884
- if (rel === 0) {
2885
- storedFields[i] = undefined;
2886
- continue;
2887
- }
2888
- const entryOff = tableEnd + rel - 1;
2889
- if (entryOff + 4 > sectionEnd) {
2890
- throw invalidFrozenIndex('stored fields entry offset out of bounds');
2891
- }
2892
- const jsonLen = buf.readUInt32LE(entryOff);
2893
- const jsonStart = entryOff + 4;
2894
- const jsonEnd = jsonStart + jsonLen;
2895
- if (jsonEnd > sectionEnd) {
2896
- throw invalidFrozenIndex('stored fields JSON out of bounds');
2897
- }
2898
- storedFields[i] = JSON.parse(buf.toString('utf8', jsonStart, jsonEnd));
2899
- }
2900
- return storedFields;
2901
- }
2902
- /** Validate structural invariants of a decoded or assembled frozen snapshot. */
2903
- function validateFrozenSnapshot(snap) {
2904
- validateFrozenSnapshotNumeric(snap);
2905
- const termCount = termCountOf(snap);
2906
- if (snap.packedTermIndex != null) {
2907
- validateFrozenTermIndexLeaves(snap.packedTermIndex, termCount);
2908
- }
2909
- else if (snap.termTree != null) {
2910
- validateTermTreeLeaves(snap.termTree, termCount);
2911
- }
2912
- else {
2913
- validateTreeShape(snap.treeShape, termCount);
2914
- }
2915
- }
2916
- function fieldNamesFromFieldIds(fieldIds) {
2917
- const names = Object.keys(fieldIds);
2918
- names.sort((a, b) => fieldIds[a] - fieldIds[b]);
2919
- return names;
2920
- }
2921
- /** Core with explicit {@link termCountOf} (no dictionary section). */
2922
- function buildCoreSectionWithTermCount(snap) {
2923
- const out = Buffer.alloc(16);
2924
- out.writeUInt32LE(snap.documentCount, 0);
2925
- out.writeUInt32LE(snap.nextId, 4);
2926
- out.writeUInt32LE(snap.fieldCount, 8);
2927
- out.writeUInt32LE(termCountOf(snap), 12);
2928
- return out;
2929
- }
2930
- function buildFieldNamesSection(fieldNames) {
2931
- const chunks = [];
2932
- for (const name of fieldNames) {
2933
- const body = Buffer.from(name, 'utf8');
2934
- const header = Buffer.alloc(4);
2935
- header.writeUInt32LE(body.length, 0);
2936
- chunks.push(header, body);
2937
- }
2938
- return Buffer.concat(chunks);
2939
- }
2940
- function buildExternalIdsSection(externalIds, nextId) {
2941
- const chunks = [];
2942
- for (let i = 0; i < nextId; i++) {
2943
- writeExternalId(chunks, externalIds[i]);
2944
- }
2945
- return Buffer.concat(chunks);
2946
- }
2947
- function buildStoredFieldsSection(storedFields, nextId) {
2948
- const table = Buffer.alloc(nextId * 4);
2949
- const heapChunks = [];
2950
- let heapOff = 0;
2951
- for (let i = 0; i < nextId; i++) {
2952
- const row = storedFields[i];
2953
- if (row == null) {
2954
- table.writeUInt32LE(0, i * 4);
2955
- continue;
2956
- }
2957
- table.writeUInt32LE(heapOff + 1, i * 4);
2958
- const json = Buffer.from(JSON.stringify(row), 'utf8');
2959
- const entry = Buffer.alloc(4 + json.length);
2960
- entry.writeUInt32LE(json.length, 0);
2961
- json.copy(entry, 4);
2962
- heapChunks.push(entry);
2963
- heapOff += entry.length;
2964
- }
2965
- return Buffer.concat([table, ...heapChunks]);
2966
- }
2967
- function validateTermTreeLeaves(tree, termCount) {
2968
- for (const [key, val] of tree) {
2969
- if (key === LEAF) {
2970
- const idx = val;
2971
- if (!Number.isInteger(idx) || idx < 0 || idx >= termCount) {
2972
- throw invalidFrozenIndex(`term tree leaf index out of range: ${idx}`);
2973
- }
2974
- }
2975
- else {
2976
- validateTermTreeLeaves(val, termCount);
2977
- }
2978
- }
2979
- }
2980
- function deserializeTermIndexTree(shape) {
2981
- const tree = new Map();
2982
- for (const [key, value] of shape) {
2983
- if (key === LEAF) {
2984
- tree.set(LEAF, value);
2985
- }
2986
- else {
2987
- tree.set(key, deserializeTermIndexTree(value));
2988
- }
2989
- }
2990
- return tree;
2991
- }
2992
-
2993
3166
  /** Global wire flags for {@link FreqArray} width. */
2994
3167
  function freqWireFlags(freqs) {
2995
3168
  if (freqs instanceof Uint16Array)
@@ -3281,11 +3454,14 @@ function encodeFrozenSnapshotMsv5(snap, termTree, packedTermIndex) {
3281
3454
  const flFlags = fieldLengthMatrixWireFlags(snap.fieldLengthMatrix);
3282
3455
  const freqFlags = freqWireFlags(snap.postings.allFreqs);
3283
3456
  const globalFlags = postingsWire.flags | flFlags | freqFlags;
3457
+ const storedFieldsSection = snap.storedFieldsLayout != null
3458
+ ? buildStoredFieldsWireSection(snap.storedFieldsLayout, snap.nextId)
3459
+ : buildStoredFieldsSection(snap.storedFields, snap.nextId);
3284
3460
  const rawSections = [
3285
3461
  buildCoreSectionWithTermCount(snap),
3286
3462
  buildFieldNamesSection(fieldNames),
3287
3463
  buildExternalIdsSection(snap.externalIds, snap.nextId),
3288
- buildStoredFieldsSection(snap.storedFields, snap.nextId),
3464
+ storedFieldsSection,
3289
3465
  buildTermTreeSectionColumnar(packed),
3290
3466
  bufferFromView(snap.avgFieldLength),
3291
3467
  buildFieldLengthMatrixSection(snap.fieldLengthMatrix),
@@ -3309,11 +3485,14 @@ async function encodeFrozenSnapshotMsv5Async(snap, termTree, packedTermIndex) {
3309
3485
  const flFlags = fieldLengthMatrixWireFlags(snap.fieldLengthMatrix);
3310
3486
  const freqFlags = freqWireFlags(snap.postings.allFreqs);
3311
3487
  const globalFlags = postingsWire.flags | flFlags | freqFlags;
3488
+ const storedFieldsSection = snap.storedFieldsLayout != null
3489
+ ? buildStoredFieldsWireSection(snap.storedFieldsLayout, snap.nextId)
3490
+ : buildStoredFieldsSection(snap.storedFields, snap.nextId);
3312
3491
  const rawSections = [
3313
3492
  buildCoreSectionWithTermCount(snap),
3314
3493
  buildFieldNamesSection(fieldNames),
3315
3494
  buildExternalIdsSection(snap.externalIds, snap.nextId),
3316
- buildStoredFieldsSection(snap.storedFields, snap.nextId),
3495
+ storedFieldsSection,
3317
3496
  buildTermTreeSectionColumnar(packed),
3318
3497
  bufferFromView(snap.avgFieldLength),
3319
3498
  buildFieldLengthMatrixSection(snap.fieldLengthMatrix),
@@ -3343,7 +3522,7 @@ function validateMsv5Container(buf) {
3343
3522
  }
3344
3523
  return { globalFlags, directory };
3345
3524
  }
3346
- function decodeMsv5Sections(globalFlags, sections) {
3525
+ function decodeMsv5Sections(globalFlags, sections, hints) {
3347
3526
  const core = sections[0 /* Msv5SectionId.Core */];
3348
3527
  if (core.length !== 16) {
3349
3528
  throw invalidFrozenIndex('core section size mismatch');
@@ -3358,7 +3537,12 @@ function decodeMsv5Sections(globalFlags, sections) {
3358
3537
  fieldIds[fieldNames[f]] = f;
3359
3538
  }
3360
3539
  const externalIds = readExternalIdsSection(sections[2 /* Msv5SectionId.ExternalIds */], 0, nextId, sections[2 /* Msv5SectionId.ExternalIds */].length);
3361
- const storedFields = readStoredFieldsSection(sections[3 /* Msv5SectionId.StoredFields */], 0, nextId, sections[3 /* Msv5SectionId.StoredFields */].length);
3540
+ const storedFieldsLayout = hints != null
3541
+ ? readStoredFieldsWireSection(sections[3 /* Msv5SectionId.StoredFields */], 0, nextId, sections[3 /* Msv5SectionId.StoredFields */].length, hints.storeFields)
3542
+ : undefined;
3543
+ const storedFields = storedFieldsLayout != null
3544
+ ? new Array(nextId)
3545
+ : readStoredFieldsSection(sections[3 /* Msv5SectionId.StoredFields */], 0, nextId, sections[3 /* Msv5SectionId.StoredFields */].length);
3362
3546
  const packedTermIndex = readPackedTermTreeSectionColumnar(sections[4 /* Msv5SectionId.TermTree */], termCount);
3363
3547
  const avgBuf = sections[5 /* Msv5SectionId.AvgFieldLength */];
3364
3548
  const avgFieldLength = readFloat32Array(avgBuf, 0, avgBuf.length);
@@ -3376,6 +3560,7 @@ function decodeMsv5Sections(globalFlags, sections) {
3376
3560
  avgFieldLength,
3377
3561
  externalIds,
3378
3562
  storedFields,
3563
+ storedFieldsLayout,
3379
3564
  fieldLengthMatrix,
3380
3565
  treeShape: [],
3381
3566
  packedTermIndex,
@@ -3384,13 +3569,13 @@ function decodeMsv5Sections(globalFlags, sections) {
3384
3569
  validateFrozenSnapshot(snap);
3385
3570
  return snap;
3386
3571
  }
3387
- function decodeFrozenSnapshotMsv5(buf) {
3572
+ function decodeFrozenSnapshotMsv5(buf, hints) {
3388
3573
  const { globalFlags, directory } = validateMsv5Container(buf);
3389
- return decodeMsv5Sections(globalFlags, loadMsv5Sections(buf, directory));
3574
+ return decodeMsv5Sections(globalFlags, loadMsv5Sections(buf, directory), hints);
3390
3575
  }
3391
- async function decodeFrozenSnapshotMsv5Async(buf) {
3576
+ async function decodeFrozenSnapshotMsv5Async(buf, hints) {
3392
3577
  const { globalFlags, directory } = validateMsv5Container(buf);
3393
- return decodeMsv5Sections(globalFlags, await loadMsv5SectionsAsync(buf, directory));
3578
+ return decodeMsv5Sections(globalFlags, await loadMsv5SectionsAsync(buf, directory), hints);
3394
3579
  }
3395
3580
 
3396
3581
  /** Encode a frozen snapshot as a binary buffer. */
@@ -3404,26 +3589,26 @@ function encodeFrozenSnapshotAsync(snap, termTree, packedTermIndex) {
3404
3589
 
3405
3590
  const LEGACY_MAGICS = new Set(['MSv1', 'MSv2', 'MSv3', 'MSv4']);
3406
3591
  /** Decode a frozen binary snapshot buffer. */
3407
- function decodeFrozenSnapshot(buf) {
3592
+ function decodeFrozenSnapshot(buf, hints) {
3408
3593
  assertBufferLength(buf, 8);
3409
3594
  const magic = buf.toString('ascii', 0, 4);
3410
3595
  const version = buf.readUInt16LE(4);
3411
3596
  if (isMsv5Buffer(buf) && version === 5) {
3412
- return decodeFrozenSnapshotMsv5(buf);
3597
+ return decodeFrozenSnapshotMsv5(buf, hints);
3413
3598
  }
3414
3599
  if (LEGACY_MAGICS.has(magic)) {
3415
- throw invalidFrozenIndex('Unsupported frozen binary snapshot; re-build with saveBinarySync() or from lucaong JSON');
3600
+ throw invalidFrozenIndex('Unsupported frozen binary snapshot; re-build with saveBinarySync() or from MiniSearch JSON');
3416
3601
  }
3417
3602
  throw invalidFrozenIndex('Unsupported frozen binary snapshot');
3418
3603
  }
3419
3604
  /** Async frozen snapshot decode (streaming zstd). */
3420
- async function decodeFrozenSnapshotAsync(buf) {
3605
+ async function decodeFrozenSnapshotAsync(buf, hints) {
3421
3606
  assertBufferLength(buf, 8);
3422
3607
  const version = buf.readUInt16LE(4);
3423
3608
  if (isMsv5Buffer(buf) && version === 5) {
3424
- return decodeFrozenSnapshotMsv5Async(buf);
3609
+ return decodeFrozenSnapshotMsv5Async(buf, hints);
3425
3610
  }
3426
- return decodeFrozenSnapshot(buf);
3611
+ return decodeFrozenSnapshot(buf, hints);
3427
3612
  }
3428
3613
 
3429
3614
  const DEFAULT_CAPACITY = 16;
@@ -3685,14 +3870,13 @@ class FrozenIndexBuilder {
3685
3870
  this._nextId = 0;
3686
3871
  this._frozen = false;
3687
3872
  const estimated = hints === null || hints === void 0 ? void 0 : hints.estimatedDocumentCount;
3873
+ this._storedFields = createStoredFieldsLayout(this._options.storeFields, estimated !== null && estimated !== void 0 ? estimated : 0);
3688
3874
  if (estimated != null && estimated > 0) {
3689
3875
  this._externalIds = new Array(estimated);
3690
- this._storedFields = new Array(estimated);
3691
3876
  this._fieldLengthData = new Array(estimated * this._fieldCount).fill(0);
3692
3877
  }
3693
3878
  else {
3694
3879
  this._externalIds = [];
3695
- this._storedFields = [];
3696
3880
  this._fieldLengthData = [];
3697
3881
  }
3698
3882
  }
@@ -3715,7 +3899,7 @@ class FrozenIndexBuilder {
3715
3899
  this._seenIds.add(id);
3716
3900
  const shortId = this._nextId++;
3717
3901
  this._externalIds[shortId] = id;
3718
- this._storedFields[shortId] = saveStoredFieldsForDocument(storeFields, extractField, document);
3902
+ writeStoredField(this._storedFields, shortId, storeFields, extractField, document);
3719
3903
  const documentCount = shortId + 1;
3720
3904
  for (const field of fields) {
3721
3905
  const fieldValue = extractField(document, field);
@@ -3801,9 +3985,7 @@ class FrozenIndexBuilder {
3801
3985
  const externalIds = this._externalIds.length > documentCount
3802
3986
  ? this._externalIds.slice(0, documentCount)
3803
3987
  : this._externalIds;
3804
- const storedFields = this._storedFields.length > documentCount
3805
- ? this._storedFields.slice(0, documentCount)
3806
- : this._storedFields;
3988
+ const storedFields = resizeStoredFields(this._storedFields, documentCount);
3807
3989
  const idLookup = createIdToShortIdLookup(externalIds, documentCount);
3808
3990
  return {
3809
3991
  options: this._options,
@@ -4116,7 +4298,7 @@ function executeQueryInternal(query, searchOptions, params, allowedDocs, run) {
4116
4298
  return executeWildcardQuery(searchOptions, params);
4117
4299
  }
4118
4300
  if (isQueryCombination(query)) {
4119
- // Spread inherits parent combineWith into child branches (lucaong 7.2 behavior).
4301
+ // Spread inherits parent combineWith into child branches (MiniSearch 7.2 behavior).
4120
4302
  const options = { ...searchOptions, ...query, queries: undefined };
4121
4303
  const operator = ((_b = (_a = query.combineWith) !== null && _a !== void 0 ? _a : options.combineWith) !== null && _b !== void 0 ? _b : params.globalSearchOptions.combineWith);
4122
4304
  if (useGatedEvaluation(run, query.queries.length, operator, combinationHasWildcard(query))) {
@@ -4166,6 +4348,73 @@ function autoSuggestFromSearch(search, queryString, options = {}) {
4166
4348
  return suggestFromSearchResults(search(queryString, options));
4167
4349
  }
4168
4350
 
4351
+ /** Visit shortIds with a defined external id (holes in `externalIds` are skipped). */
4352
+ function forEachLiveShortId(nextId, externalIds, callback) {
4353
+ for (let shortId = 0; shortId < nextId; shortId++) {
4354
+ const externalId = externalIds[shortId];
4355
+ if (externalId === undefined)
4356
+ continue;
4357
+ callback(shortId, externalId);
4358
+ }
4359
+ }
4360
+
4361
+ /**
4362
+ * Build a MiniSearch `toJSON` wire snapshot (`serializationVersion: 2`) from frozen index parts.
4363
+ * Alloc-heavy (plain objects per term/field) — migration/interop only, not production persistence.
4364
+ * All input parts must belong to the same frozen index instance.
4365
+ */
4366
+ function miniSearchSnapshotFromFrozen(input) {
4367
+ const { documentCount, nextId, fieldIds, fieldCount, externalIds, fieldLengthMatrix, avgFieldLength, storedFields, index, fieldTermFlyweight, } = input;
4368
+ const documentIds = {};
4369
+ const fieldLength = {};
4370
+ const storedFieldsOut = {};
4371
+ const hasStoredFields = storedFields.kind !== 'none';
4372
+ forEachLiveShortId(nextId, externalIds, (shortId, externalId) => {
4373
+ var _a;
4374
+ const shortIdStr = String(shortId);
4375
+ documentIds[shortIdStr] = externalId;
4376
+ const lengths = new Array(fieldCount);
4377
+ const rowBase = shortId * fieldCount;
4378
+ for (let f = 0; f < fieldCount; f++) {
4379
+ lengths[f] = (_a = fieldLengthMatrix[rowBase + f]) !== null && _a !== void 0 ? _a : 0;
4380
+ }
4381
+ fieldLength[shortIdStr] = lengths;
4382
+ if (hasStoredFields) {
4383
+ storedFieldsOut[shortIdStr] = readStoredFields(storedFields, shortId);
4384
+ }
4385
+ });
4386
+ const indexEntries = [];
4387
+ for (const [term, termIndex] of index.entries()) {
4388
+ fieldTermFlyweight.bind(termIndex);
4389
+ const fieldData = {};
4390
+ for (let f = 0; f < fieldCount; f++) {
4391
+ const segment = fieldTermFlyweight.get(f);
4392
+ if (segment == null || segment.size === 0)
4393
+ continue;
4394
+ const entry = {};
4395
+ segment.forEachDoc((docId, freq) => {
4396
+ entry[String(docId)] = freq;
4397
+ });
4398
+ fieldData[String(f)] = entry;
4399
+ }
4400
+ if (Object.keys(fieldData).length > 0) {
4401
+ indexEntries.push([term, fieldData]);
4402
+ }
4403
+ }
4404
+ return {
4405
+ documentCount,
4406
+ nextId,
4407
+ documentIds,
4408
+ fieldIds,
4409
+ fieldLength,
4410
+ averageFieldLength: Array.from(avgFieldLength),
4411
+ storedFields: storedFieldsOut,
4412
+ dirtCount: 0,
4413
+ index: indexEntries,
4414
+ serializationVersion: 2,
4415
+ };
4416
+ }
4417
+
4169
4418
  function ownedIndexArray(arr) {
4170
4419
  if (arr instanceof Uint8Array)
4171
4420
  return new Uint8Array(arr);
@@ -4231,7 +4480,7 @@ function shallowCopyJsSnapshotFields(params) {
4231
4480
  return {
4232
4481
  fieldIds: { ...params.fieldIds },
4233
4482
  options: shallowCopyOptions(params.options),
4234
- storedFields: params.storedFields.slice(),
4483
+ storedFields: cloneStoredFields(params.storedFields),
4235
4484
  };
4236
4485
  }
4237
4486
  /**
@@ -4316,7 +4565,7 @@ class FrozenMiniSearch {
4316
4565
  fieldIds: this._fieldIds,
4317
4566
  getFieldLength: (docId, fieldId) => this.getFieldLength(docId, fieldId),
4318
4567
  getExternalId: docId => this._externalIds[docId],
4319
- getStoredFields: docId => this._storedFields[docId],
4568
+ getStoredFields: docId => readStoredFields(this._storedFields, docId),
4320
4569
  };
4321
4570
  this._queryEngineParams = {
4322
4571
  fields: this._options.fields,
@@ -4324,12 +4573,9 @@ class FrozenMiniSearch {
4324
4573
  tokenize: this._options.tokenize,
4325
4574
  processTerm: this._options.processTerm,
4326
4575
  indexView: createFrozenQueryIndexView(this._index, this._postings, this._fieldTermFlyweight, (callback) => {
4327
- for (let shortId = 0; shortId < this._nextId; shortId++) {
4328
- const id = this._externalIds[shortId];
4329
- if (id === undefined)
4330
- continue;
4331
- callback(shortId, id, this._storedFields[shortId]);
4332
- }
4576
+ forEachLiveShortId(this._nextId, this._externalIds, (shortId, id) => {
4577
+ callback(shortId, id, readStoredFields(this._storedFields, shortId));
4578
+ });
4333
4579
  }),
4334
4580
  aggregateContext: this._aggregateContext,
4335
4581
  };
@@ -4339,11 +4585,7 @@ class FrozenMiniSearch {
4339
4585
  memoryBreakdown() {
4340
4586
  const termCount = this.termCount;
4341
4587
  const postingsStats = postingsTypedBytes(this._postings);
4342
- let storedJson = 0;
4343
- for (const row of this._storedFields) {
4344
- if (row != null)
4345
- storedJson += JSON.stringify(row).length;
4346
- }
4588
+ const storedJson = storedFieldsJsonBytes(this._storedFields);
4347
4589
  const radixEst = this._index.packedByteLength();
4348
4590
  const idMapBytes = this._idLookup.mode === 'lazy-map' ? this._idLookup.mapEntryCount * 32 : 0;
4349
4591
  const estimatedStructuredBytes = postingsStats.totalTypedBytes
@@ -4373,7 +4615,7 @@ class FrozenMiniSearch {
4373
4615
  },
4374
4616
  documents: {
4375
4617
  externalIdsSlots: this._externalIds.length,
4376
- storedFieldsSlots: this._storedFields.length,
4618
+ storedFieldsSlots: storedFieldsSlotCount(this._storedFields),
4377
4619
  idLookupMode: this._idLookup.mode,
4378
4620
  idToShortIdEntries: this._idLookup.mapEntryCount,
4379
4621
  fieldLengthMatrixBytes: this._fieldLengthMatrix.byteLength,
@@ -4388,10 +4630,10 @@ class FrozenMiniSearch {
4388
4630
  }
4389
4631
  getStoredFields(id) {
4390
4632
  const shortId = this._idLookup.get(id);
4391
- return shortId == null ? undefined : this._storedFields[shortId];
4633
+ return shortId == null ? undefined : readStoredFields(this._storedFields, shortId);
4392
4634
  }
4393
4635
  search(query, searchOptions = {}) {
4394
- return finalizeRawSearchResults(this.executeQuery(query, searchOptions), query, searchOptions, this._options.searchOptions, docId => this._externalIds[docId], docId => this._storedFields[docId]);
4636
+ return finalizeRawSearchResults(this.executeQuery(query, searchOptions), query, searchOptions, this._options.searchOptions, docId => this._externalIds[docId], docId => readStoredFields(this._storedFields, docId));
4395
4637
  }
4396
4638
  autoSuggest(queryString, options = {}) {
4397
4639
  const merged = { ...this._options.autoSuggestOptions, ...options };
@@ -4407,7 +4649,8 @@ class FrozenMiniSearch {
4407
4649
  fieldNames: fieldNamesFromFieldIds(this._fieldIds),
4408
4650
  avgFieldLength: this._avgFieldLength,
4409
4651
  externalIds: this._externalIds,
4410
- storedFields: this._storedFields,
4652
+ storedFields: new Array(this._nextId),
4653
+ storedFieldsLayout: this._storedFields,
4411
4654
  fieldLengthMatrix: fieldLengthMatrixForWire(this._fieldLengthMatrix),
4412
4655
  treeShape: [],
4413
4656
  postings: this._postings,
@@ -4423,7 +4666,8 @@ class FrozenMiniSearch {
4423
4666
  fieldNames: fieldNamesFromFieldIds(this._fieldIds),
4424
4667
  avgFieldLength: this._avgFieldLength,
4425
4668
  externalIds: this._externalIds,
4426
- storedFields: this._storedFields,
4669
+ storedFields: new Array(this._nextId),
4670
+ storedFieldsLayout: this._storedFields,
4427
4671
  fieldLengthMatrix: fieldLengthMatrixForWire(this._fieldLengthMatrix),
4428
4672
  treeShape: [],
4429
4673
  postings: this._postings,
@@ -4431,16 +4675,20 @@ class FrozenMiniSearch {
4431
4675
  }
4432
4676
  /** Load a frozen binary snapshot. */
4433
4677
  static loadBinarySync(buffer, options = {}) {
4434
- const snap = decodeFrozenSnapshot(buffer);
4678
+ var _a;
4679
+ const storeFields = (_a = options.storeFields) !== null && _a !== void 0 ? _a : defaultFrozenLoadOptions.storeFields;
4680
+ const snap = decodeFrozenSnapshot(buffer, { storeFields });
4435
4681
  return FrozenMiniSearch.fromBinarySnapshot(snap, options);
4436
4682
  }
4437
4683
  /** Load a frozen binary snapshot with streaming zstd decompression (bounded memory). */
4438
4684
  static async loadBinaryAsync(buffer, options = {}) {
4439
- const snap = await decodeFrozenSnapshotAsync(buffer);
4685
+ var _a;
4686
+ const storeFields = (_a = options.storeFields) !== null && _a !== void 0 ? _a : defaultFrozenLoadOptions.storeFields;
4687
+ const snap = await decodeFrozenSnapshotAsync(buffer, { storeFields });
4440
4688
  return FrozenMiniSearch.fromBinarySnapshot(snap, options);
4441
4689
  }
4442
4690
  static fromBinarySnapshot(snap, options) {
4443
- var _a, _b;
4691
+ var _a, _b, _c;
4444
4692
  const snapshotFields = (_a = snap.fieldNames) !== null && _a !== void 0 ? _a : fieldNamesFromFieldIds(snap.fieldIds);
4445
4693
  if (options.fields != null) {
4446
4694
  assertFieldsMatchSnapshot(options.fields, snap.fieldIds);
@@ -4468,7 +4716,7 @@ class FrozenMiniSearch {
4468
4716
  fieldCount: snap.fieldCount,
4469
4717
  externalIds: snap.externalIds,
4470
4718
  idLookup,
4471
- storedFields: snap.storedFields,
4719
+ storedFields: (_c = snap.storedFieldsLayout) !== null && _c !== void 0 ? _c : storedFieldsFromRows(snap.storedFields, opts.storeFields),
4472
4720
  fieldLengthMatrix: snap.fieldLengthMatrix,
4473
4721
  avgFieldLength: snap.avgFieldLength,
4474
4722
  index,
@@ -4481,21 +4729,43 @@ class FrozenMiniSearch {
4481
4729
  return buildFrozenFromDocuments(documents, options);
4482
4730
  }
4483
4731
  /**
4484
- * Convert a lucaong MiniSearch JSON snapshot (`toJSON` / `loadJSON` wire format) into a
4485
- * frozen index. No runtime dependency on the `minisearch` package.
4732
+ * Export this index as a MiniSearch wire snapshot (`serializationVersion: 2`).
4733
+ * Use for migration or interchange with the `minisearch` package (`JSON.stringify` works via this method).
4734
+ * Not the primary persistence format — prefer {@link saveBinarySync} for production (size and load time).
4735
+ * Term order in `index` may differ from MiniSearch native `toJSON`; search scores stay equivalent.
4736
+ */
4737
+ toJSON() {
4738
+ return miniSearchSnapshotFromFrozen({
4739
+ documentCount: this._documentCount,
4740
+ nextId: this._nextId,
4741
+ fieldIds: this._fieldIds,
4742
+ fieldCount: this._fieldCount,
4743
+ externalIds: this._externalIds,
4744
+ fieldLengthMatrix: this._fieldLengthMatrix,
4745
+ avgFieldLength: this._avgFieldLength,
4746
+ storedFields: this._storedFields,
4747
+ index: this._index,
4748
+ fieldTermFlyweight: this._fieldTermFlyweight,
4749
+ });
4750
+ }
4751
+ /**
4752
+ * Build a new frozen index **from** a MiniSearch JSON snapshot string (import / migration).
4753
+ * Accepts the wire format produced by MiniSearch `toJSON` or by {@link toJSON} on this class.
4754
+ * Distinct from {@link loadBinarySync}: JSON is MiniSearch interchange, not the native frozen binary.
4755
+ * No runtime dependency on the `minisearch` package.
4486
4756
  */
4487
- static fromMiniSearchJson(json, options = {}) {
4757
+ static fromJson(json, options = {}) {
4488
4758
  return FrozenMiniSearch.fromMiniSearchSnapshot(JSON.parse(json), options);
4489
4759
  }
4490
4760
  /**
4491
- * Same as {@link fromMiniSearchJson} with a pre-parsed snapshot object.
4761
+ * Same as {@link fromJson} with a pre-parsed snapshot object.
4492
4762
  * `storedFields` are shallow-copied; callers must not mutate nested values
4493
4763
  * after load if they intend to keep the index immutable.
4494
4764
  */
4495
4765
  static fromMiniSearchSnapshot(snapshot, options = {}) {
4496
4766
  return assembleFrozenTrusted(buildFrozenAssembleParamsFromMiniSearchSnapshot(snapshot, options), 'minisearch-json');
4497
4767
  }
4498
- /** Accepts any object exposing `toJSON()` in lucaong MiniSearch snapshot shape. */
4768
+ /** Accepts any object exposing `toJSON()` in MiniSearch snapshot shape. */
4499
4769
  static fromMiniSearch(source, options = {}) {
4500
4770
  return FrozenMiniSearch.fromMiniSearchSnapshot(source.toJSON(), options);
4501
4771
  }