@delali/narsil 0.3.1-next.70 → 0.3.1-next.72

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.
Files changed (57) hide show
  1. package/dist/adapters/filesystem.mjs +1 -1
  2. package/dist/adapters/indexeddb.mjs +1 -1
  3. package/dist/{capabilities-Dt-7B4Wz.d.ts → capabilities-D6gjjQyA.d.ts} +2 -2
  4. package/dist/{chunk-P4DSQ6M4.mjs → chunk-2X2ZQOAB.mjs} +69 -28
  5. package/dist/chunk-3HQPUCCT.mjs +785 -0
  6. package/dist/{chunk-VNMTB3KC.mjs → chunk-7QMTA7AA.mjs} +194 -59
  7. package/dist/{chunk-FVLQ63B7.mjs → chunk-CVPA5Q7R.mjs} +24 -3
  8. package/dist/{chunk-2STVUTNA.mjs → chunk-EDMCZLSJ.mjs} +4 -3
  9. package/dist/{chunk-M76V3OGJ.mjs → chunk-HVJYTKK7.mjs} +940 -647
  10. package/dist/{chunk-HWQLTLNG.mjs → chunk-KZTI25D3.mjs} +1 -1
  11. package/dist/{chunk-YJDQ7NLA.mjs → chunk-LCCL6EEH.mjs} +3 -3
  12. package/dist/{chunk-OI42GWLR.mjs → chunk-LJYTW7GT.mjs} +57 -11
  13. package/dist/{chunk-653BNMAF.mjs → chunk-M6XF5HDE.mjs} +57 -17
  14. package/dist/{chunk-YNVIUGF5.mjs → chunk-P2ZZN32C.mjs} +1 -1
  15. package/dist/{chunk-LMO5TJUS.mjs → chunk-QR4OKRVY.mjs} +637 -196
  16. package/dist/{chunk-USKFU7J5.mjs → chunk-RV77FR2F.mjs} +1 -0
  17. package/dist/{chunk-GIVAGD3Q.mjs → chunk-XZ4PAREY.mjs} +1 -1
  18. package/dist/{chunk-3JGWULDF.mjs → chunk-Y52WZWAU.mjs} +5 -5
  19. package/dist/chunk-ZKRV5M2P.mjs +75 -0
  20. package/dist/{client-COek5GXK.d.ts → client-C2TiW29y.d.ts} +3 -3
  21. package/dist/client.d.ts +8 -8
  22. package/dist/client.mjs +1 -0
  23. package/dist/{config-rBpbgTw8.d.ts → config-BLguB-om.d.ts} +2 -2
  24. package/dist/{config-Dfanyqqt.d.ts → config-C7Ip8Ife.d.ts} +17 -2
  25. package/dist/direct-executor-SVCHXR74.mjs +14 -0
  26. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  27. package/dist/distribution/coordinator/etcd.mjs +2 -2
  28. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  29. package/dist/distribution/coordinator/in-memory.mjs +1 -1
  30. package/dist/distribution/transport/grpc.mjs +1 -1
  31. package/dist/distribution.d.ts +8 -8
  32. package/dist/distribution.mjs +58 -35
  33. package/dist/embeddings/openai.mjs +1 -1
  34. package/dist/{engine-CXKfmK7I.d.ts → engine-Dm4446j1.d.ts} +104 -62
  35. package/dist/index.browser.mjs +2222 -1100
  36. package/dist/index.d.ts +27 -8
  37. package/dist/index.mjs +14 -12
  38. package/dist/invalidation/filesystem.mjs +1 -1
  39. package/dist/{memory-C7UMCMH3.d.ts → memory-CKHTU1CL.d.ts} +66 -25
  40. package/dist/pattern-Cledccbl.d.ts +18 -0
  41. package/dist/pattern.d.ts +22 -0
  42. package/dist/pattern.mjs +7 -0
  43. package/dist/persistence/durability/checkpoint-worker.d.ts +17 -1
  44. package/dist/persistence/durability/checkpoint-worker.mjs +14 -12
  45. package/dist/react.d.ts +277 -182
  46. package/dist/react.mjs +190 -45
  47. package/dist/{schema-Bgzr4g7b.d.ts → schema-BBOCdhND.d.ts} +9 -7
  48. package/dist/server.d.ts +10 -10
  49. package/dist/server.mjs +13 -12
  50. package/dist/{tasks-BZt_zes6.d.ts → tasks-BECd0b__.d.ts} +1 -1
  51. package/dist/{thread-JEVAYETR.mjs → thread-IBSIZZ6W.mjs} +22 -13
  52. package/dist/{types-gOUqhs2F.d.ts → types-BiUmaiWT.d.ts} +1 -1
  53. package/dist/vector/search-worker.d.ts +1 -1
  54. package/dist/vector/search-worker.mjs +4 -4
  55. package/dist/workers/entry.mjs +4 -4
  56. package/package.json +15 -7
  57. package/dist/direct-executor-GYJEKNV5.mjs +0 -12
package/dist/react.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useState, useEffect, createElement, useRef, useEffectEvent, useCallback, useMemo, useContext, useSyncExternalStore } from 'react';
1
+ import { createContext, useState, createElement, useRef, useEffectEvent, useEffect, useCallback, useMemo, useContext, useSyncExternalStore } from 'react';
2
2
 
3
3
  // src/errors.ts
4
4
  var NARSIL_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@delali/narsil.NarsilError");
@@ -33,6 +33,7 @@ var ErrorCodes = {
33
33
  SEARCH_INVALID_MODE: "SEARCH_INVALID_MODE",
34
34
  SEARCH_INVALID_CURSOR: "SEARCH_INVALID_CURSOR",
35
35
  SEARCH_RESULT_WINDOW_EXCEEDED: "SEARCH_RESULT_WINDOW_EXCEEDED",
36
+ SEARCH_WORK_CAP_EXCEEDED: "SEARCH_WORK_CAP_EXCEEDED",
36
37
  LANGUAGE_NOT_SUPPORTED: "LANGUAGE_NOT_SUPPORTED",
37
38
  ENVELOPE_VERSION_MISMATCH: "ENVELOPE_VERSION_MISMATCH",
38
39
  ENVELOPE_INVALID_MAGIC: "ENVELOPE_INVALID_MAGIC",
@@ -147,8 +148,53 @@ function isNarsilError(value) {
147
148
  return typeof value === "object" && value !== null && NARSIL_ERROR_MARKER in value;
148
149
  }
149
150
 
151
+ // src/react/reader.ts
152
+ function plainNumbersIn(value) {
153
+ if (value === null || typeof value !== "object") return value;
154
+ if (ArrayBuffer.isView(value)) {
155
+ return value instanceof DataView ? value : Array.from(value);
156
+ }
157
+ if (Array.isArray(value)) {
158
+ let copy2 = null;
159
+ for (let at = 0; at < value.length; at++) {
160
+ const converted = plainNumbersIn(value[at]);
161
+ if (converted === value[at]) continue;
162
+ copy2 ??= value.slice();
163
+ copy2[at] = converted;
164
+ }
165
+ return copy2 ?? value;
166
+ }
167
+ let copy = null;
168
+ for (const [key, held] of Object.entries(value)) {
169
+ const converted = plainNumbersIn(held);
170
+ if (converted === held) continue;
171
+ copy ??= { ...value };
172
+ copy[key] = converted;
173
+ }
174
+ return copy ?? value;
175
+ }
176
+ function withPlainNumbers(answer) {
177
+ return answer.then((value) => plainNumbersIn(value));
178
+ }
179
+ function engineReader(engine) {
180
+ return {
181
+ query(indexName, params) {
182
+ return withPlainNumbers(engine.query(indexName, params));
183
+ },
184
+ preflight: (indexName, params) => engine.preflight(indexName, params),
185
+ suggest: (indexName, params) => engine.suggest(indexName, params),
186
+ get: (indexName, docId) => withPlainNumbers(engine.get(indexName, docId)),
187
+ listDocuments(indexName, params) {
188
+ return withPlainNumbers(engine.listDocuments(indexName, params));
189
+ },
190
+ listIndexes: async () => engine.listIndexes(),
191
+ getStats: async (indexName) => engine.getStats(indexName)
192
+ };
193
+ }
194
+
150
195
  // src/react/constants.ts
151
196
  var DEFAULT_KEEP_ALIVE_MS = 2e3;
197
+ var DEFAULT_REFRESH_AFTER_WRITE_MS = 200;
152
198
  var DEFAULT_TASK_POLL_INTERVAL_MS = 250;
153
199
  var ERROR_RETRY_INTERVAL_MS = 5e3;
154
200
  var MAX_QUERY_KEY_DEPTH = 32;
@@ -173,10 +219,13 @@ var IDLE_SNAPSHOT = Object.freeze({
173
219
  isLoading: false,
174
220
  isFetching: false
175
221
  });
176
- function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
222
+ var ANY_INDEX = /* @__PURE__ */ Symbol("any index");
223
+ var NO_INDEX = /* @__PURE__ */ Symbol("no index");
224
+ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS, refreshAfterWriteMs = DEFAULT_REFRESH_AFTER_WRITE_MS) {
177
225
  const entries = /* @__PURE__ */ new Map();
178
226
  let holders = 0;
179
227
  let closing = null;
228
+ const staleRefreshes = /* @__PURE__ */ new Map();
180
229
  function publish(entry, next) {
181
230
  entry.snapshot = next;
182
231
  for (const listener of entry.listeners) listener();
@@ -185,6 +234,7 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
185
234
  if (entry.controller !== null) return;
186
235
  const controller = new AbortController();
187
236
  entry.controller = controller;
237
+ entry.stale = false;
188
238
  entry.version++;
189
239
  const version = entry.version;
190
240
  if (!entry.snapshot.isFetching) publish(entry, { ...entry.snapshot, isFetching: true });
@@ -209,9 +259,32 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
209
259
  );
210
260
  }
211
261
  function disposeAll() {
262
+ for (const timer of staleRefreshes.values()) clearTimeout(timer);
263
+ staleRefreshes.clear();
212
264
  for (const [key, entry] of [...entries]) drop(key, entry);
213
265
  entries.clear();
214
266
  }
267
+ function refreshStale(scope) {
268
+ staleRefreshes.delete(scope);
269
+ for (const [key, entry] of entries) {
270
+ if (entry.writeScope === scope && entry.stale && entry.listeners.size > 0) load(key, entry);
271
+ }
272
+ }
273
+ function scheduleRefresh(scope) {
274
+ const pending = staleRefreshes.get(scope);
275
+ if (pending !== void 0) clearTimeout(pending);
276
+ staleRefreshes.set(
277
+ scope,
278
+ setTimeout(() => refreshStale(scope), refreshAfterWriteMs)
279
+ );
280
+ }
281
+ function markStale(entry) {
282
+ entry.stale = true;
283
+ if (entry.controller === null) return;
284
+ entry.controller.abort();
285
+ entry.controller = null;
286
+ entry.version++;
287
+ }
215
288
  function drop(key, entry) {
216
289
  if (entries.get(key) === entry) entries.delete(key);
217
290
  if (entry.disposal !== null) clearTimeout(entry.disposal);
@@ -220,7 +293,7 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
220
293
  entry.controller = null;
221
294
  }
222
295
  return {
223
- subscribe(key, loader, onChange) {
296
+ subscribe(key, loader, onChange, writeScope) {
224
297
  let entry = entries.get(key);
225
298
  if (entry === void 0) {
226
299
  entry = {
@@ -229,7 +302,9 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
229
302
  loader,
230
303
  controller: null,
231
304
  version: 0,
232
- disposal: null
305
+ disposal: null,
306
+ writeScope,
307
+ stale: false
233
308
  };
234
309
  entries.set(key, entry);
235
310
  } else {
@@ -241,7 +316,7 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
241
316
  }
242
317
  const held = entry;
243
318
  held.listeners.add(onChange);
244
- const retryable = held.snapshot.isLoading || held.snapshot.error !== void 0;
319
+ const retryable = held.stale || held.snapshot.isLoading || held.snapshot.error !== void 0;
245
320
  if (retryable && held.controller === null) load(key, held);
246
321
  return () => {
247
322
  held.listeners.delete(onChange);
@@ -259,6 +334,15 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
259
334
  const entry = entries.get(key);
260
335
  if (entry !== void 0) load(key, entry);
261
336
  },
337
+ invalidate(indexName) {
338
+ const marked = /* @__PURE__ */ new Set();
339
+ for (const entry of entries.values()) {
340
+ if (entry.writeScope !== ANY_INDEX && entry.writeScope !== indexName) continue;
341
+ markStale(entry);
342
+ marked.add(entry.writeScope);
343
+ }
344
+ for (const scope of marked) scheduleRefresh(scope);
345
+ },
262
346
  retain() {
263
347
  holders++;
264
348
  if (closing !== null) {
@@ -283,30 +367,72 @@ function createResourceStore(keepAliveMs = DEFAULT_KEEP_ALIVE_MS) {
283
367
 
284
368
  // src/react/context.ts
285
369
  var NarsilContext = createContext(null);
286
- function build(client, keepAliveMs) {
287
- return { client, store: createResourceStore(keepAliveMs), keepAliveMs };
370
+ function buildContextValue(props) {
371
+ const { client, engine, keepAliveMs, refreshAfterWriteMs } = props;
372
+ const shared = { store: createResourceStore(keepAliveMs, refreshAfterWriteMs), keepAliveMs, refreshAfterWriteMs };
373
+ if (client !== void 0 && engine === void 0) return { ...shared, reader: client, client, engine: null };
374
+ if (engine !== void 0 && client === void 0) {
375
+ return { ...shared, reader: engineReader(engine), client: null, engine };
376
+ }
377
+ throw new NarsilError(
378
+ ErrorCodes.CONFIG_INVALID,
379
+ "A NarsilProvider requires exactly one of client and engine, but its props set both or neither"
380
+ );
381
+ }
382
+ function providerPropsChanged(value, props) {
383
+ return value.client !== (props.client ?? null) || value.engine !== (props.engine ?? null) || value.keepAliveMs !== props.keepAliveMs || value.refreshAfterWriteMs !== props.refreshAfterWriteMs;
384
+ }
385
+ function useStoreRetention(store) {
386
+ useEffect(() => store.retain(), [store]);
387
+ }
388
+ function useRefreshAfterWrites(engine, store) {
389
+ useEffect(() => {
390
+ if (engine === null) return;
391
+ const onWrite = (payload) => {
392
+ store.invalidate(payload.indexName);
393
+ };
394
+ engine.on("write", onWrite);
395
+ return () => {
396
+ engine.off("write", onWrite);
397
+ };
398
+ }, [engine, store]);
288
399
  }
289
400
  function NarsilProvider(props) {
290
- const { client, keepAliveMs, children } = props;
291
- const [value, setValue] = useState(() => build(client, keepAliveMs));
292
- if (value.client !== client || value.keepAliveMs !== keepAliveMs) {
293
- setValue(build(client, keepAliveMs));
401
+ const [value, setValue] = useState(() => buildContextValue(props));
402
+ if (providerPropsChanged(value, props)) {
403
+ setValue(buildContextValue(props));
294
404
  }
295
- useEffect(() => value.store.retain(), [value]);
296
- return createElement(NarsilContext.Provider, { value }, children);
405
+ useStoreRetention(value.store);
406
+ useRefreshAfterWrites(value.engine, value.store);
407
+ return createElement(NarsilContext.Provider, { value }, props.children);
297
408
  }
298
409
  function useNarsilContext() {
299
410
  const value = useContext(NarsilContext);
300
411
  if (value === null) {
301
412
  throw new NarsilError(
302
413
  ErrorCodes.CONFIG_INVALID,
303
- "A Narsil hook ran outside a NarsilProvider, so it found no client to send through"
414
+ "A Narsil hook was called outside a NarsilProvider, so no client or engine is available to it"
304
415
  );
305
416
  }
306
417
  return value;
307
418
  }
419
+ function useServerClient(hookName) {
420
+ const { client } = useNarsilContext();
421
+ if (client !== null) return client;
422
+ throw new NarsilError(
423
+ ErrorCodes.CONFIG_INVALID,
424
+ `${hookName} works only under a client of a server, because only a server handles tasks and imports, while the NarsilProvider above it holds an engine`,
425
+ { hook: hookName }
426
+ );
427
+ }
308
428
  function useNarsilClient() {
309
- return useNarsilContext().client;
429
+ const { client } = useNarsilContext();
430
+ if (client !== null) return client;
431
+ throw new NarsilError(
432
+ ErrorCodes.CONFIG_INVALID,
433
+ "useNarsilClient returns a client, while the NarsilProvider above it holds an engine, so import that engine directly",
434
+ { hook: "useNarsilClient" }
435
+ );
310
436
  }
311
437
 
312
438
  // src/react/key.ts
@@ -457,8 +583,8 @@ function useKey(parts) {
457
583
  cached.current = { parts, key };
458
584
  return key;
459
585
  }
460
- function useRead(parts, run, options) {
461
- const { client, store } = useNarsilContext();
586
+ function useRead(source, parts, run, options, writeScope) {
587
+ const { store } = useNarsilContext();
462
588
  const enabled = options?.enabled ?? true;
463
589
  const headers = options?.headers;
464
590
  const timeoutMs = options?.timeoutMs;
@@ -472,11 +598,11 @@ function useRead(parts, run, options) {
472
598
  if (!enabled) return NO_SUBSCRIPTION;
473
599
  const loader = (signal) => {
474
600
  const held = call.current;
475
- return held.run(client, { ...held.request, signal });
601
+ return held.run(source, { ...held.request, signal });
476
602
  };
477
- return store.subscribe(key, loader, onChange);
603
+ return store.subscribe(key, loader, onChange, writeScope);
478
604
  },
479
- [enabled, store, client, key]
605
+ [enabled, store, source, key, writeScope]
480
606
  );
481
607
  const readSnapshot = useCallback(
482
608
  () => enabled ? store.snapshot(key) : IDLE_SNAPSHOT,
@@ -508,21 +634,28 @@ function useRead(parts, run, options) {
508
634
  [data, snapshot, refresh]
509
635
  );
510
636
  }
637
+ function useProviderRead(parts, run, options, writeScope) {
638
+ const { reader } = useNarsilContext();
639
+ return useRead(reader, parts, run, options, writeScope);
640
+ }
511
641
 
512
642
  // src/react/documents.ts
513
643
  function useDocument(indexName, docId, options) {
514
644
  const id = docId ?? "";
515
645
  const enabled = (options?.enabled ?? true) && id.length > 0;
516
- return useRead(["get", indexName, id], (client, request) => client.get(indexName, id, request), {
517
- ...options,
518
- enabled
519
- });
646
+ return useProviderRead(
647
+ ["get", indexName, id],
648
+ (reader, request) => reader.get(indexName, id, request),
649
+ { ...options, enabled },
650
+ indexName
651
+ );
520
652
  }
521
653
  function useDocuments(indexName, params, options) {
522
- return useRead(
654
+ return useProviderRead(
523
655
  ["listDocuments", indexName, params],
524
- (client, request) => client.listDocuments(indexName, params, request),
525
- options
656
+ (reader, request) => reader.listDocuments(indexName, params, request),
657
+ options,
658
+ indexName
526
659
  );
527
660
  }
528
661
 
@@ -544,7 +677,7 @@ function isTerminalTask(record) {
544
677
  // src/react/import.ts
545
678
  var NOTHING = { task: void 0, error: void 0, starting: false };
546
679
  function useImport(indexName, options) {
547
- const client = useNarsilClient();
680
+ const client = useServerClient("useImport");
548
681
  const [progress, setProgress] = useState(NOTHING);
549
682
  const polling = useRef(null);
550
683
  const sending = useRef(null);
@@ -652,51 +785,63 @@ function useImport(indexName, options) {
652
785
 
653
786
  // src/react/indexes.ts
654
787
  function useIndexes(options) {
655
- return useRead(["listIndexes"], (client, request) => client.listIndexes(request), options);
788
+ return useProviderRead(["listIndexes"], (reader, request) => reader.listIndexes(request), options, ANY_INDEX);
656
789
  }
657
790
  function useStats(indexName, options) {
658
- return useRead(["getStats", indexName], (client, request) => client.getStats(indexName, request), options);
791
+ return useProviderRead(
792
+ ["getStats", indexName],
793
+ (reader, request) => reader.getStats(indexName, request),
794
+ options,
795
+ indexName
796
+ );
659
797
  }
660
798
 
661
799
  // src/react/search.ts
662
800
  function useQuery(indexName, params, options) {
663
- return useRead(
801
+ return useProviderRead(
664
802
  ["query", indexName, params],
665
- (client, request) => client.query(indexName, params, request),
666
- options
803
+ (reader, request) => reader.query(indexName, params, request),
804
+ options,
805
+ indexName
667
806
  );
668
807
  }
669
808
  function usePreflight(indexName, params, options) {
670
- return useRead(
809
+ return useProviderRead(
671
810
  ["preflight", indexName, params],
672
- (client, request) => client.preflight(indexName, params, request),
673
- options
811
+ (reader, request) => reader.preflight(indexName, params, request),
812
+ options,
813
+ indexName
674
814
  );
675
815
  }
676
816
  function useSuggest(indexName, params, options) {
677
- return useRead(
817
+ return useProviderRead(
678
818
  ["suggest", indexName, params],
679
- (client, request) => client.suggest(indexName, params, request),
680
- options
819
+ (reader, request) => reader.suggest(indexName, params, request),
820
+ options,
821
+ indexName
681
822
  );
682
823
  }
683
824
 
684
825
  // src/react/tasks.ts
685
826
  function useTask(taskId, options) {
827
+ const client = useServerClient("useTask");
686
828
  const id = taskId ?? "";
687
829
  const enabled = (options?.enabled ?? true) && id.length > 0;
688
- const state = useRead(["getTask", id], (client, request) => client.getTask(id, request), {
689
- ...options,
690
- enabled,
691
- refreshIntervalMs: 0
692
- });
830
+ const state = useRead(
831
+ client,
832
+ ["getTask", id],
833
+ (server, request) => server.getTask(id, request),
834
+ { ...options, enabled, refreshIntervalMs: 0 },
835
+ NO_INDEX
836
+ );
693
837
  const running = state.data !== null && !isTerminalTask(state.data);
694
838
  const interval = pollInterval(options?.pollIntervalMs ?? DEFAULT_TASK_POLL_INTERVAL_MS, state.error !== void 0);
695
839
  usePolling(state.refresh, interval, enabled && running);
696
840
  return state;
697
841
  }
698
842
  function useTasks(query, options) {
699
- return useRead(["listTasks", query], (client, request) => client.listTasks(query, request), options);
843
+ const client = useServerClient("useTasks");
844
+ return useRead(client, ["listTasks", query], (server, request) => server.listTasks(query, request), options, NO_INDEX);
700
845
  }
701
846
 
702
847
  export { ClientErrorCodes, ErrorCodes, NarsilError, NarsilProvider, ServerErrorCodes, isNarsilError, useDocument, useDocuments, useImport, useIndexes, useNarsilClient, usePreflight, useQuery, useStats, useSuggest, useTask, useTasks };
@@ -18,12 +18,13 @@ type AnyDocument = Record<string, unknown> & {
18
18
  *
19
19
  * The engine can sort by a field whose type includes `sortable`. On a single
20
20
  * `string` field with that option, the engine can also test a text range with
21
- * `gt`, `gte`, `lt`, `lte`, or `between`. The engine raises
22
- * `DOC_VALIDATION_FAILED` for a value longer than
23
- * {@link IndexConfig.patternValueLimit} in a field whose type includes
24
- * `pattern`. A later release will add pattern search and partial-word search,
25
- * which will test the fields whose types include `pattern` and `partial`
26
- * respectively.
21
+ * `gt`, `gte`, `lt`, `lte`, or `between`. The engine keeps a pattern index for
22
+ * a field whose type includes `pattern`. It answers `contains`, `eq`, `ne`,
23
+ * `in`, `nin`, `startsWith`, and `endsWith` on that field from the index,
24
+ * comparing case folds for any of those tests that sets `caseFold`. The engine
25
+ * raises `DOC_VALIDATION_FAILED` for a value longer than
26
+ * {@link IndexConfig.patternValueLimit} in such a field. A later release will
27
+ * add partial-word search for the fields whose types include `partial`.
27
28
  *
28
29
  * @public
29
30
  */
@@ -51,7 +52,8 @@ type FieldOptionChain<Remaining extends string> = {
51
52
  * The engine stores and compares a `verbatim` value exactly as written, as one
52
53
  * piece of text. Because the engine keeps that value whole, it leaves the
53
54
  * field out of keyword search, although it can test the whole value in a
54
- * filter.
55
+ * filter. The engine keeps a pattern index for every `verbatim` field, so it
56
+ * can also test such a field with `contains` and `caseFold`.
55
57
  *
56
58
  * Add the {@link TextFieldOption} options to a `string` or `string[]` type, or
57
59
  * `sortable` alone to a `verbatim` or `verbatim[]` type, as in
package/dist/server.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- export { C as ClientErrorCode, a as ClientErrorCodes, E as ErrorCode, b as ErrorCodes, N as NarsilError, c as NarsilErrorCode, S as ServerErrorCode, d as ServerErrorCodes, i as isNarsilError } from './memory-C7UMCMH3.js';
2
- import { b as ServerOptions, N as NarsilServer } from './capabilities-Dt-7B4Wz.js';
3
- export { A as ASYNC_IMPORT_CAPABILITY, C as CorsOptions, E as ErrorEnvelope, I as INDEX_LIFECYCLE_CAPABILITY, O as OnRequestHook, R as REBUILD_ANALYSIS_CAPABILITY, c as RequestContext, d as RequestDenial, S as SERVER_CAPABILITIES, e as ServerLimits, T as TASK_CANCEL_CAPABILITY, a as TASK_FILTER_CAPABILITY } from './capabilities-Dt-7B4Wz.js';
4
- import { a as Narsil } from './engine-CXKfmK7I.js';
5
- import { d as TaskProgress, b as ImportResult, h as TaskStore, e as TaskRecord } from './tasks-BZt_zes6.js';
6
- export { C as CapabilitiesResponse, a as CreateIndexEmbedding, i as CreateIndexRequest, H as HttpIndexConfig, I as ImportError, M as MemoryStatsResponse, T as TaskListPage, c as TaskListQuery, f as TaskStatus, g as TaskType } from './tasks-BZt_zes6.js';
7
- import './schema-Bgzr4g7b.js';
1
+ export { C as ClientErrorCode, a as ClientErrorCodes, E as ErrorCode, b as ErrorCodes, N as NarsilError, c as NarsilErrorCode, S as ServerErrorCode, d as ServerErrorCodes, i as isNarsilError } from './memory-CKHTU1CL.js';
2
+ import { b as ServerOptions, N as NarsilServer } from './capabilities-D6gjjQyA.js';
3
+ export { A as ASYNC_IMPORT_CAPABILITY, C as CorsOptions, E as ErrorEnvelope, I as INDEX_LIFECYCLE_CAPABILITY, O as OnRequestHook, R as REBUILD_ANALYSIS_CAPABILITY, c as RequestContext, d as RequestDenial, S as SERVER_CAPABILITIES, e as ServerLimits, T as TASK_CANCEL_CAPABILITY, a as TASK_FILTER_CAPABILITY } from './capabilities-D6gjjQyA.js';
4
+ import { a as Narsil } from './engine-Dm4446j1.js';
5
+ import { d as TaskProgress, b as ImportResult, h as TaskStore, e as TaskRecord } from './tasks-BECd0b__.js';
6
+ export { C as CapabilitiesResponse, a as CreateIndexEmbedding, i as CreateIndexRequest, H as HttpIndexConfig, I as ImportError, M as MemoryStatsResponse, T as TaskListPage, c as TaskListQuery, f as TaskStatus, g as TaskType } from './tasks-BECd0b__.js';
7
+ import './schema-BBOCdhND.js';
8
8
  import './adapters-79OIa4xm.js';
9
- import './config-rBpbgTw8.js';
10
- import './config-Dfanyqqt.js';
11
- import './types-gOUqhs2F.js';
9
+ import './config-BLguB-om.js';
10
+ import './config-C7Ip8Ife.js';
11
+ import './types-BiUmaiWT.js';
12
12
  import './types-D5OwhtsQ.js';
13
13
 
14
14
  /**
package/dist/server.mjs CHANGED
@@ -1,17 +1,17 @@
1
- import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsHeaderReader, createSharedRequestGate, MAIN_THREAD_GATE_SLOT, registerServerRoutes, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_BODY_BYTES, RUNNING_TASK_TTL_MS, DEFAULT_TASK_PAGE_SIZE, TERMINAL_TASK_TTL_MS, TASK_PROGRESS_WRITE_INTERVAL_MS, serializeNarsilError, createVersionHandler, acceptorOf, createDocumentReadHandlers, sendError, respondError, respondJson, MAX_INDEX_TOUCH_INTERVAL_MS, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, clearGateSlot, parseJson, validateCreateIndex, rejectInvalid, validateBatch, serializeBatchResult, validateDocumentBody, validateSuggest, validateQuery, parseTaskListQuery, parseJsonOptional, validateRebalance, sendBinary, IMPORT_FLUSHES_IN_FLIGHT, badRequest, SERVER_CAPABILITIES, toHttpError, MIN_UNBOUNDED_SERVER_LIMIT, MIN_COUNTED_SERVER_LIMIT } from './chunk-2STVUTNA.mjs';
2
- export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-2STVUTNA.mjs';
3
- import { validateIndexName } from './chunk-3JGWULDF.mjs';
4
- import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-FVLQ63B7.mjs';
5
- import { detectRuntime } from './chunk-HWQLTLNG.mjs';
6
- import './chunk-GIVAGD3Q.mjs';
7
- import './chunk-653BNMAF.mjs';
1
+ import { DEFAULT_MAX_RETAINED_TASKS, loadUWebSockets, resolveCors, authorizerFor, createSharedGateBuffer, gateSlotOfWorker, createRouteRunner, corsHeaderReader, createSharedRequestGate, MAIN_THREAD_GATE_SLOT, registerServerRoutes, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_MAX_TASK_PAGE_SIZE, DEFAULT_MAX_IMPORT_ERRORS, DEFAULT_MAX_FETCH_DOCUMENTS, DEFAULT_MAX_RESULT_WINDOW, DEFAULT_MAX_CONCURRENT_REQUESTS, DEFAULT_IMPORT_BATCH_SIZE, DEFAULT_MAX_LINE_BYTES, DEFAULT_MAX_IMPORT_BYTES, DEFAULT_MAX_BODY_BYTES, RUNNING_TASK_TTL_MS, DEFAULT_TASK_PAGE_SIZE, TERMINAL_TASK_TTL_MS, TASK_PROGRESS_WRITE_INTERVAL_MS, serializeNarsilError, createVersionHandler, acceptorOf, createDocumentReadHandlers, sendError, respondError, respondJson, MAX_INDEX_TOUCH_INTERVAL_MS, INDEX_TOUCH_INTERVALS_PER_IDLE_TIMEOUT, clearGateSlot, parseJson, validateCreateIndex, rejectInvalid, validateBatch, serializeBatchResult, validateDocumentBody, validateSuggest, validateQuery, parseTaskListQuery, parseJsonOptional, validateRebalance, sendBinary, IMPORT_FLUSHES_IN_FLIGHT, badRequest, SERVER_CAPABILITIES, toHttpError, MIN_UNBOUNDED_SERVER_LIMIT, MIN_COUNTED_SERVER_LIMIT } from './chunk-EDMCZLSJ.mjs';
2
+ export { ASYNC_IMPORT_CAPABILITY, INDEX_LIFECYCLE_CAPABILITY, REBUILD_ANALYSIS_CAPABILITY, SERVER_CAPABILITIES, TASK_CANCEL_CAPABILITY, TASK_FILTER_CAPABILITY, httpStatusForNarsilError } from './chunk-EDMCZLSJ.mjs';
3
+ import { validateIndexName } from './chunk-Y52WZWAU.mjs';
4
+ import { engineCoreOf, providedDocId, resolveVectorText } from './chunk-CVPA5Q7R.mjs';
5
+ import './chunk-XZ4PAREY.mjs';
6
+ import { detectRuntime } from './chunk-KZTI25D3.mjs';
7
+ import './chunk-M6XF5HDE.mjs';
8
8
  import './chunk-TGP5M5R7.mjs';
9
- import './chunk-YNVIUGF5.mjs';
9
+ import './chunk-P2ZZN32C.mjs';
10
10
  import './chunk-U2BKYEC7.mjs';
11
- import './chunk-OI42GWLR.mjs';
11
+ import './chunk-LJYTW7GT.mjs';
12
12
  import { startIdleHeapCleanup } from './chunk-Y3L5MDRV.mjs';
13
- import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-USKFU7J5.mjs';
14
- export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-USKFU7J5.mjs';
13
+ import { NarsilError, ErrorCodes, ServerErrorCodes, isNarsilError } from './chunk-RV77FR2F.mjs';
14
+ export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-RV77FR2F.mjs';
15
15
  import { randomUUID } from 'crypto';
16
16
  import { MessageChannel } from 'worker_threads';
17
17
 
@@ -982,7 +982,8 @@ async function startRequestThreads(options) {
982
982
  limits: options.limits,
983
983
  build: options.build,
984
984
  authorizes: authorize !== void 0,
985
- searchHooks
985
+ searchHooks,
986
+ patternWorkCap: options.core.patternWorkCap
986
987
  }
987
988
  },
988
989
  [channel.port1]
@@ -1,4 +1,4 @@
1
- import { M as MemoryStats } from './memory-C7UMCMH3.js';
1
+ import { M as MemoryStats } from './memory-CKHTU1CL.js';
2
2
 
3
3
  /** Declarative index configuration accepted over HTTP. Function-valued engine
4
4
  * options (custom tokenizer, stopWords-as-function, group reducer, embedding
@@ -1,12 +1,13 @@
1
- import { loadUWebSockets, childAppOf, createRouteRunner, corsHeaderReader, createSharedRequestGate, registerServerRoutes, sendRelayed, createDocumentReadHandlers, respondJson, SERVER_CAPABILITIES, versionReport, sendError, serverRoutes, parseJson, validateSuggest, rejectInvalid, respondError, validateQuery } from './chunk-2STVUTNA.mjs';
2
- import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-P4DSQ6M4.mjs';
3
- import './chunk-GIVAGD3Q.mjs';
4
- import { resolveProjection, projectionKeepsField, setNestedValue } from './chunk-LMO5TJUS.mjs';
5
- import { extractVectorFieldsFromSchema } from './chunk-653BNMAF.mjs';
1
+ import { loadUWebSockets, childAppOf, createRouteRunner, corsHeaderReader, createSharedRequestGate, registerServerRoutes, sendRelayed, createDocumentReadHandlers, respondJson, SERVER_CAPABILITIES, versionReport, sendError, serverRoutes, parseJson, validateSuggest, rejectInvalid, respondError, validateQuery } from './chunk-EDMCZLSJ.mjs';
2
+ import { executeListDocuments, executeSuggest, executePreflight, queryBindingOf, executeQuery } from './chunk-2X2ZQOAB.mjs';
3
+ import './chunk-XZ4PAREY.mjs';
4
+ import { resolveProjection, projectionKeepsField, setNestedValue } from './chunk-QR4OKRVY.mjs';
5
+ import { extractVectorFieldsFromSchema } from './chunk-M6XF5HDE.mjs';
6
6
  import './chunk-ZJ4CSAB2.mjs';
7
- import './chunk-YNVIUGF5.mjs';
8
- import './chunk-OI42GWLR.mjs';
9
- import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-USKFU7J5.mjs';
7
+ import './chunk-P2ZZN32C.mjs';
8
+ import './chunk-ZKRV5M2P.mjs';
9
+ import './chunk-LJYTW7GT.mjs';
10
+ import { ServerErrorCodes, NarsilError, ErrorCodes } from './chunk-RV77FR2F.mjs';
10
11
 
11
12
  // src/server/request-threads/held-vectors.ts
12
13
  function managerWithHeldVectors(manager, fieldPaths, vectorOf) {
@@ -28,7 +29,7 @@ function managerWithHeldVectors(manager, fieldPaths, vectorOf) {
28
29
 
29
30
  // src/server/request-threads/read-engine.ts
30
31
  function createThreadReadEngine(options) {
31
- const { executor, relay, searchHooks } = options;
32
+ const { executor, relay, searchHooks, patternWorkCap } = options;
32
33
  function vectorFieldsOf(context) {
33
34
  return [...extractVectorFieldsFromSchema(context.config.schema).keys()];
34
35
  }
@@ -110,7 +111,8 @@ function createThreadReadEngine(options) {
110
111
  config: context.config,
111
112
  indexName,
112
113
  cursorBinding: queryBindingOf(params),
113
- vectorSearchers: context.vectorSearchers
114
+ vectorSearchers: context.vectorSearchers,
115
+ patternWorkCap
114
116
  });
115
117
  if (context.analysisStale) result.analysisStale = true;
116
118
  return result;
@@ -123,7 +125,8 @@ function createThreadReadEngine(options) {
123
125
  config: context.config,
124
126
  indexName,
125
127
  cursorBinding: queryBindingOf(params),
126
- vectorSearchers: context.vectorSearchers
128
+ vectorSearchers: context.vectorSearchers,
129
+ patternWorkCap
127
130
  });
128
131
  if (context.analysisStale) result.analysisStale = true;
129
132
  return result;
@@ -157,7 +160,8 @@ function createThreadReadEngine(options) {
157
160
  return executeListDocuments(params ?? {}, {
158
161
  manager,
159
162
  schema: context.config.schema,
160
- strict: context.config.strict
163
+ strict: context.config.strict,
164
+ patternWorkCap
161
165
  });
162
166
  }
163
167
  };
@@ -381,7 +385,12 @@ async function serveRequests(executor, settings) {
381
385
  const child = childAppOf(app);
382
386
  if (child === null) throw new Error("This uWebSockets.js build offers no child apps");
383
387
  const relay = createRelayClient(settings.port, settings.touchIntervalMs);
384
- const local = createThreadReadEngine({ executor, relay, searchHooks: settings.searchHooks });
388
+ const local = createThreadReadEngine({
389
+ executor,
390
+ relay,
391
+ searchHooks: settings.searchHooks,
392
+ patternWorkCap: settings.patternWorkCap
393
+ });
385
394
  const run = createRouteRunner({
386
395
  authorize: settings.authorizes ? (context) => relay.authorize(context) : void 0,
387
396
  gate: createSharedRequestGate(settings.gate, settings.maxConcurrentRequests, settings.gateSlot),
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './schema-Bgzr4g7b.js';
1
+ import { S as SchemaDefinition } from './schema-BBOCdhND.js';
2
2
 
3
3
  /**
4
4
  * What a node does in a cluster. A node may hold several roles at once.
@@ -1,4 +1,4 @@
1
- import { e as VectorQuantizationMode } from '../schema-Bgzr4g7b.js';
1
+ import { e as VectorQuantizationMode } from '../schema-BBOCdhND.js';
2
2
  import { A as ArenaSimd } from '../simd-DW5AtJ6B.js';
3
3
  import '../adapters-79OIa4xm.js';
4
4
 
@@ -1,8 +1,8 @@
1
- import { openSharedVectorField, MAX_WORKER_COPIES, createVectorStore, osqBitsOf, createHNSWIndex, createOsqQuantizer, osqRecordBytes } from '../chunk-YJDQ7NLA.mjs';
1
+ import { openSharedVectorField, MAX_WORKER_COPIES, createVectorStore, osqBitsOf, createHNSWIndex, createOsqQuantizer, osqRecordBytes } from '../chunk-LCCL6EEH.mjs';
2
2
  import '../chunk-ZJ4CSAB2.mjs';
3
- import '../chunk-YNVIUGF5.mjs';
4
- import '../chunk-OI42GWLR.mjs';
5
- import '../chunk-USKFU7J5.mjs';
3
+ import '../chunk-P2ZZN32C.mjs';
4
+ import '../chunk-LJYTW7GT.mjs';
5
+ import '../chunk-RV77FR2F.mjs';
6
6
 
7
7
  // src/vector/worker-copy.ts
8
8
  function restoreQuantizer(snapshot, store) {
@@ -1,6 +1,6 @@
1
1
  import { startIdleHeapCleanup } from '../chunk-Y3L5MDRV.mjs';
2
2
  import { threadSlotOfWorker, isValidWorkerAction } from '../chunk-POWOTUKJ.mjs';
3
- import { ErrorCodes, NarsilError, isNarsilError } from '../chunk-USKFU7J5.mjs';
3
+ import { ErrorCodes, NarsilError, isNarsilError } from '../chunk-RV77FR2F.mjs';
4
4
 
5
5
  // src/workers/action-handler.ts
6
6
  function buildErrorResponse(requestId, code, message, details) {
@@ -18,7 +18,7 @@ async function importBootstrapModule(moduleUrl) {
18
18
  await import(moduleUrl);
19
19
  }
20
20
  async function stopServingRequests() {
21
- const { closeRequestThread } = await import('../thread-JEVAYETR.mjs');
21
+ const { closeRequestThread } = await import('../thread-IBSIZZ6W.mjs');
22
22
  closeRequestThread();
23
23
  }
24
24
  function holdsCopies(executor) {
@@ -49,7 +49,7 @@ function createActionHandler(executor) {
49
49
  if (!holdsCopies(executor)) {
50
50
  throw new NarsilError(ErrorCodes.CONFIG_INVALID, "This executor holds no copies to answer requests from");
51
51
  }
52
- const starting = import('../thread-JEVAYETR.mjs').then(
52
+ const starting = import('../thread-IBSIZZ6W.mjs').then(
53
53
  ({ serveRequests }) => serveRequests(executor, action.settings)
54
54
  );
55
55
  serving.start = starting.then(
@@ -102,7 +102,7 @@ async function setup() {
102
102
  } catch {
103
103
  parentPort = null;
104
104
  }
105
- const { createDirectExecutor } = await import('../direct-executor-GYJEKNV5.mjs');
105
+ const { createDirectExecutor } = await import('../direct-executor-SVCHXR74.mjs');
106
106
  const handleAction = createActionHandler(createDirectExecutor({ threadSlot }));
107
107
  if (parentPort) {
108
108
  const port = parentPort;