@duckedup/nidus 0.2.0 → 0.53.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/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A typed attribute value, externally tagged exactly as `nidus` serde-encodes
3
- * `Value` on the wire: `{ Str }`, `{ Int }`, `{ Bool }`, `{ List }`, or the bare
4
- * string `"Null"`.
3
+ * `Value` on the wire: `{ Str }`, `{ Int }`, `{ Bool }`, `{ List }`, `{ Float }`,
4
+ * `{ DateTime }` (epoch milliseconds, UTC), or the bare string `"Null"`.
5
5
  *
6
6
  * `Null` is distinct from an absent key: absence means "not set / not indexed",
7
7
  * `Null` means "set, and empty/none".
@@ -14,15 +14,20 @@ type Value = {
14
14
  Bool: boolean;
15
15
  } | {
16
16
  List: string[];
17
+ } | {
18
+ Float: number;
19
+ } | {
20
+ DateTime: number;
17
21
  } | "Null";
18
22
  /**
19
23
  * What callers may pass anywhere a {@link Value} is expected: either an
20
24
  * explicitly-tagged `Value` (from the `v.*` helpers) or a plain JS scalar that
21
- * the SDK normalizes — `string → Str`, `boolean → Bool`, integer `numberInt`,
22
- * `string[]List`, `null → Null`. A non-integer number throws (the store has no
23
- * float attribute type; floats belong in the vector, not in attrs).
25
+ * the SDK normalizes — `string → Str`, `boolean → Bool`, `string[]List`,
26
+ * `DateDateTime`, `null → Null`, and a `number` to `Int` or `Float` by
27
+ * `Number.isInteger` (JS has no int type, so the value has to decide; use
28
+ * {@link v.float} to pin a whole-numbered field to `Float`).
24
29
  */
25
- type AttrInput = Value | string | number | boolean | string[] | null;
30
+ type AttrInput = Value | string | number | boolean | string[] | Date | null;
26
31
  /** A document: caller-supplied `id`, an optional embedding, and typed metadata. */
27
32
  interface NidusRecord {
28
33
  id: string;
@@ -49,6 +54,8 @@ type Predicate = {
49
54
  Ne: [string, Value];
50
55
  } | {
51
56
  Glob: [string, string];
57
+ } | {
58
+ IGlob: [string, string];
52
59
  } | {
53
60
  In: [string, Value[]];
54
61
  } | {
@@ -61,6 +68,30 @@ type Predicate = {
61
68
  Gt: [string, Value];
62
69
  } | {
63
70
  Ge: [string, Value];
71
+ } | {
72
+ Contains: [string, Value];
73
+ } | {
74
+ NotContains: [string, Value];
75
+ } | {
76
+ ContainsAny: [string, Value[]];
77
+ } | {
78
+ All: Predicate[];
79
+ } | {
80
+ Any: Predicate[];
81
+ } | {
82
+ Not: Predicate;
83
+ }
84
+ /** The one three-element leaf: key, text, and the edit budget. */
85
+ | {
86
+ Fuzzy: [string, string, number];
87
+ } | {
88
+ ContainsAllTokens: [string, string];
89
+ } | {
90
+ ContainsAnyToken: [string, string];
91
+ } | {
92
+ ContainsTokenSequence: [string, string];
93
+ } | {
94
+ Regex: [string, string];
64
95
  };
65
96
  /**
66
97
  * A conjunction (AND) of predicates. On the wire `Filter` is a newtype over
@@ -74,9 +105,52 @@ interface Hit {
74
105
  id: string;
75
106
  score: number;
76
107
  attrs: Record<string, DecodedValue>;
108
+ /** Why this hit matched — present only when the query asked to `explain` or highlight. */
109
+ annotations?: Annotations;
110
+ }
111
+ /** One fusion leg's own view of a hit: its rank in that leg (0-based) and that leg's score. */
112
+ interface LegScore {
113
+ rank: number;
114
+ score: number;
115
+ }
116
+ /** One BM25 clause's contribution to a hit's text score. Only matched clauses appear. */
117
+ interface ClauseScore {
118
+ field: string;
119
+ score: number;
120
+ }
121
+ /**
122
+ * An excerpt of a field's stored text plus the ranges a query term matched. The server
123
+ * reports UTF-8 **byte** offsets; the SDK converts them to JS string indices (UTF-16 code
124
+ * units), so `text.slice(...span)` is the matched term even when the excerpt is not ASCII.
125
+ */
126
+ interface Fragment {
127
+ text: string;
128
+ spans: [number, number][];
77
129
  }
78
- /** A {@link Value} decoded back to a plain JS value. */
79
- type DecodedValue = string | number | boolean | string[] | null;
130
+ /** The fragments found in one full-text field. */
131
+ interface Highlight {
132
+ field: string;
133
+ fragments: Fragment[];
134
+ }
135
+ /**
136
+ * Why a hit matched. Every part is opt-in and absent when it carries nothing, so a hit
137
+ * annotated by `explain` alone has no `highlights` key.
138
+ */
139
+ interface Annotations {
140
+ /** The vector leg's rank and score, on a hybrid hit that leg returned. */
141
+ vector?: LegScore;
142
+ /** The BM25 leg's rank and combined text score, on a hybrid hit that leg returned. */
143
+ text?: LegScore;
144
+ /** Each matched clause's own BM25 score, in query order. */
145
+ clauses?: ClauseScore[];
146
+ /** Highlighted fragments, one entry per clause field that had a match. */
147
+ highlights?: Highlight[];
148
+ }
149
+ /**
150
+ * A {@link Value} decoded back to a plain JS value. A `DateTime` comes back as a
151
+ * `Date`, not a number, so a decoded `attrs` map re-encodes to what it came from.
152
+ */
153
+ type DecodedValue = string | number | boolean | string[] | Date | null;
80
154
  /** On-disk footprint, mirroring `FootprintDto`. */
81
155
  interface Footprint {
82
156
  rows: number;
@@ -104,41 +178,242 @@ interface Stats {
104
178
  collections: string[];
105
179
  footprint: Footprint;
106
180
  }
181
+ /**
182
+ * Which attrs the returned hits carry. Omit both for every attr (the default).
183
+ * Sending both is a `400` — the server refuses rather than picking one.
184
+ */
185
+ interface ProjectionOptions {
186
+ /** Return only these attrs. A named attr the record lacks is simply absent. */
187
+ includeAttributes?: string[];
188
+ /** Return every attr but these. */
189
+ excludeAttributes?: string[];
190
+ }
191
+ /**
192
+ * Recency decay over a timestamp attribute. The penalty is *subtracted* from the base
193
+ * score — `score = base - lambda * (1 - decay ^ (age / scale))` — so it stays meaningful
194
+ * for a metric whose scores are negative or unbounded (Euclidean, dot product, BM25).
195
+ */
196
+ interface Decay {
197
+ /** The timestamp attribute: a `DateTime`, or an `Int` of epoch milliseconds. */
198
+ field: string;
199
+ /**
200
+ * "Now". Ages are measured back from here rather than from the wall clock, so the same
201
+ * query against an unchanged store ranks the same way twice. A `Date` or epoch ms.
202
+ */
203
+ origin: Date | number;
204
+ /** Age in milliseconds at which the factor equals `decay` (default: 7 days). */
205
+ scale?: number;
206
+ /** Factor reached at exactly `scale` old, in `(0, 1)`; the default `0.5` makes it a half-life. */
207
+ decay?: number;
208
+ /** Score a fully-decayed hit gives up (default `1`). */
209
+ lambda?: number;
210
+ /** Factor for a record whose `field` is missing or not a timestamp. Defaults to `1` — no penalty. */
211
+ missing?: number;
212
+ }
213
+ /**
214
+ * A ranking expression layered over the store's distance metric. Omitting it is the bare
215
+ * metric — the ranking nidus has always returned.
216
+ */
217
+ type RankBy = {
218
+ decay: Decay;
219
+ };
220
+ /**
221
+ * Cap how many hits may carry any one value of an attribute — "at most 2 hits per file".
222
+ * Records *missing* the attribute form one shared group, so an absent value cannot evade
223
+ * the cap. Approximate: it thins the ranking rather than searching deeper to refill it.
224
+ */
225
+ interface LimitPer {
226
+ field: string;
227
+ max: number;
228
+ }
229
+ /**
230
+ * Sort a {@link NidusClient.list} by an attribute instead of storage order. Values of
231
+ * another type, unorderable ones (`null`/lists/`NaN`), and records missing the attribute
232
+ * sort into one trailing bucket, which stays trailing in both directions.
233
+ */
234
+ interface OrderBy {
235
+ field: string;
236
+ descending?: boolean;
237
+ }
238
+ /** Ranking knobs shared by {@link NidusClient.search} and {@link NidusClient.textSearch}. */
239
+ interface RankingOptions {
240
+ rankBy?: RankBy;
241
+ limitPer?: LimitPer;
242
+ }
243
+ /** How several {@link TextClause}s fold into one text score. */
244
+ type FtsCombine = "Sum" | "Max";
245
+ /** One clause of a multi-field text query: an indexed field and the query text for it. */
246
+ interface TextClause {
247
+ field: string;
248
+ query: string;
249
+ }
250
+ /** How much text a highlight carries. `fragmentChars` is a character budget, not bytes. */
251
+ interface HighlightOptions {
252
+ /** Most fragments returned per field (default `1`). */
253
+ maxFragments?: number;
254
+ /** Characters per fragment (default `160`): leading context, then the match and its tail. */
255
+ fragmentChars?: number;
256
+ }
257
+ /** Annotation knobs shared by {@link NidusClient.textSearch} and {@link NidusClient.hybridSearch}. */
258
+ interface AnnotationOptions {
259
+ /** Report each leg's and each matched clause's own score in a hit's `annotations`. */
260
+ explain?: boolean;
261
+ /**
262
+ * Return highlighted fragments; `true` takes the defaults. Highlighting reads the
263
+ * stored text, so it still works on a field the projection dropped.
264
+ */
265
+ highlight?: boolean | HighlightOptions;
266
+ }
107
267
  /** Options for {@link NidusClient.search}. An empty/omitted `scope` searches every collection. */
108
- interface SearchOptions {
268
+ interface SearchOptions extends ProjectionOptions, RankingOptions {
109
269
  query: number[];
110
270
  scope?: string[];
111
271
  topK?: number;
272
+ /** Skip this many top-ranked hits, for pagination. `offset + topK` may not exceed 10000. */
273
+ offset?: number;
112
274
  minScore?: number;
113
275
  filter?: Filter;
276
+ /**
277
+ * Force the exact scan for this query, bypassing any ANN index and the
278
+ * quantized first pass. The index stays in place for every other query.
279
+ */
280
+ exact?: boolean;
114
281
  }
115
- /** Options for {@link NidusClient.textSearch} (BM25). */
116
- interface TextSearchOptions {
282
+ /**
283
+ * The two accepted spellings of a text query: one `field` plus its `query`, or a list of
284
+ * `clauses` each carrying its own text. Sending both, or an empty list, is a `400` — an
285
+ * empty result would otherwise read as "no matches" rather than "no query".
286
+ */
287
+ type TextQuerySpelling = {
117
288
  field: string;
118
289
  query: string;
290
+ clauses?: never;
291
+ combine?: never;
292
+ } | {
293
+ clauses: TextClause[];
294
+ combine?: FtsCombine;
295
+ field?: never;
296
+ query?: never;
297
+ };
298
+ /** The knobs of {@link TextSearchOptions} that do not name what to search. */
299
+ interface TextSearchBase extends ProjectionOptions, RankingOptions, AnnotationOptions {
119
300
  scope?: string[];
120
301
  topK?: number;
302
+ /** Skip this many top-ranked hits, for pagination. */
303
+ offset?: number;
121
304
  /** A raw BM25 score floor (not cosine). */
122
305
  minScore?: number;
123
306
  filter?: Filter;
124
307
  }
125
- /** Options for {@link NidusClient.hybridSearch} (vector + BM25 fused via RRF). */
126
- interface HybridSearchOptions {
127
- vector: number[];
308
+ /** Options for {@link NidusClient.textSearch} (BM25). */
309
+ type TextSearchOptions = TextSearchBase & TextQuerySpelling;
310
+ /**
311
+ * One entry of {@link NidusClient.setFtsSchema}'s `fields`: the attribute to index
312
+ * plus any BM25/analyzer knobs to override. Every knob is optional — omit them all
313
+ * (or pass the bare field name instead) for the server's defaults, `k1 = 1.2`,
314
+ * `b = 0.75`, US English, no folding, no token-length cap.
315
+ */
316
+ interface FtsField {
317
+ /** The attribute to full-text index. */
318
+ field: string;
319
+ /** BM25 term-frequency saturation (default `1.2`). */
320
+ k1?: number;
321
+ /** BM25 length normalization, `0`–`1` (default `0.75`). */
322
+ b?: number;
323
+ /** Analyzer language; `"english"` is the only one today. */
324
+ language?: string;
325
+ /** Fold Latin diacritics to ASCII, so `café` and `cafe` share a term. */
326
+ asciiFolding?: boolean;
327
+ /** Drop tokens longer than this many characters (default: no cap). */
328
+ maxTokenLen?: number;
329
+ }
330
+ /** {@link TextQuerySpelling} for hybrid search, whose single form spells the text `text`. */
331
+ type HybridQuerySpelling = {
128
332
  field: string;
129
333
  text: string;
334
+ clauses?: never;
335
+ combine?: never;
336
+ } | {
337
+ clauses: TextClause[];
338
+ combine?: FtsCombine;
339
+ field?: never;
340
+ text?: never;
341
+ };
342
+ /** The knobs of {@link HybridSearchOptions} that do not name what the text leg searches. */
343
+ interface HybridSearchBase extends AnnotationOptions {
344
+ vector: number[];
130
345
  scope?: string[];
131
346
  topK?: number;
347
+ /** Skip this many hits of the *fused* ranking, for pagination. */
348
+ offset?: number;
132
349
  filter?: Filter;
133
350
  rrfK?: number;
134
351
  candidates?: number;
352
+ /** Weight on the vector leg's RRF contribution. Both weights at `1` is plain fusion. */
353
+ vectorWeight?: number;
354
+ /** Weight on the BM25 leg's RRF contribution (default `1`). */
355
+ textWeight?: number;
135
356
  }
357
+ /** Options for {@link NidusClient.hybridSearch} (vector + BM25 fused via RRF). */
358
+ type HybridSearchOptions = HybridSearchBase & HybridQuerySpelling;
136
359
  /** Options for {@link NidusClient.list} (metadata-only, paginated). */
137
- interface ListOptions {
360
+ interface ListOptions extends ProjectionOptions {
138
361
  scope?: string[];
139
362
  offset?: number;
140
363
  limit?: number;
141
364
  filter?: Filter;
365
+ /** Sort by an attribute instead of storage order. */
366
+ orderBy?: OrderBy;
367
+ }
368
+ /** Options for {@link NidusClient.aggregate}. An empty/omitted `scope` covers every collection. */
369
+ interface AggregateOptions {
370
+ scope?: string[];
371
+ filter?: Filter;
372
+ /** Attributes to sum. A missing or non-numeric value is skipped, not counted as zero. */
373
+ sum?: string[];
374
+ /**
375
+ * Report one {@link Group} per distinct value of this attribute, alongside the
376
+ * whole-scope totals. An empty string is a `400`, not "no grouping" — omit it instead.
377
+ */
378
+ groupBy?: string;
379
+ }
380
+ /** What {@link NidusClient.aggregate} answers: the match count plus one sum per named field. */
381
+ interface Aggregation {
382
+ count: number;
383
+ /** One entry per requested `sum` field, decoded from its tagged `Int`/`Float`. */
384
+ sums: Record<string, number>;
385
+ /** One row per distinct `groupBy` value, largest first. Absent when none was asked for. */
386
+ groups?: Group[];
387
+ /** Distinct values outran the server's cap and later ones were dropped. */
388
+ groupsTruncated?: boolean;
389
+ }
390
+ /**
391
+ * One distinct `groupBy` value with the aggregates over just its records. `value` is `null`
392
+ * for the records missing the attribute — a different group from those holding a `null`.
393
+ */
394
+ interface Group {
395
+ value: DecodedValue | null;
396
+ count: number;
397
+ sums: Record<string, number>;
398
+ }
399
+ /**
400
+ * Options for {@link NidusClient.batchSearch}: several vector queries answered in one
401
+ * round-trip, capped at 16 by the server. Each entry is an ordinary {@link SearchOptions}.
402
+ */
403
+ interface BatchSearchOptions {
404
+ queries: SearchOptions[];
405
+ /** Merge the per-query rankings into ONE list instead of returning them side by side. */
406
+ fuse?: BatchFuse;
407
+ }
408
+ /**
409
+ * Cross-query Reciprocal Rank Fusion — the same fusion `hybridSearch` runs, over N query
410
+ * legs. `weights` must be empty or exactly as long as `queries`; the server refuses a short
411
+ * list rather than silently re-weighting the wrong leg.
412
+ */
413
+ interface BatchFuse {
414
+ rrfK?: number;
415
+ weights?: number[];
416
+ topK?: number;
142
417
  }
143
418
  /**
144
419
  * Options for {@link NidusClient.remember} (text-native ingest). The server
@@ -212,16 +487,40 @@ declare class NidusClient {
212
487
  deleteWhere(name: string, filter: Filter): Promise<number>;
213
488
  /** Fetch every record in a collection (attrs decoded to plain JS values). */
214
489
  records(name: string): Promise<DecodedRecord[]>;
215
- /** Declare the full-text-indexed attribute fields for a collection. */
216
- setFtsSchema(name: string, fields: string[]): Promise<void>;
490
+ /**
491
+ * Declare the full-text-indexed attribute fields for a collection. A bare string
492
+ * takes the server's BM25/analyzer defaults; an {@link FtsField} object tunes `k1`,
493
+ * `b`, and the analyzer for that field alone.
494
+ */
495
+ setFtsSchema(name: string, fields: (string | FtsField)[]): Promise<void>;
217
496
  /** Vector (cosine) nearest-neighbour search. Empty `scope` searches all collections. */
218
497
  search(opts: SearchOptions): Promise<Hit[]>;
219
- /** BM25 full-text search over one indexed field. */
498
+ /**
499
+ * BM25 full-text search over one indexed field, or over a `clauses` list folded by
500
+ * `combine` (`"Sum"` unless said otherwise). Naming the fields both ways is a `400`.
501
+ */
220
502
  textSearch(opts: TextSearchOptions): Promise<Hit[]>;
221
- /** Hybrid search: fuse a vector query and a BM25 text query via RRF. */
503
+ /**
504
+ * Hybrid search: fuse a vector query and a BM25 text query via RRF. The text leg takes
505
+ * the same single-field / `clauses` choice as {@link NidusClient.textSearch}.
506
+ */
222
507
  hybridSearch(opts: HybridSearchOptions): Promise<Hit[]>;
223
508
  /** Metadata-only listing (no vector), paginated by `offset`/`limit`. */
224
509
  list(opts?: ListOptions): Promise<Hit[]>;
510
+ /**
511
+ * Count the records matching a filter and sum the named attributes. Answered from the
512
+ * in-RAM index alone — no record is built and no vector is read.
513
+ */
514
+ aggregate(opts?: AggregateOptions): Promise<Aggregation>;
515
+ /**
516
+ * Answer several vector queries in one round-trip (16 max). Returns one ranking per
517
+ * query in request order, or — with `opts.fuse` — a single array holding the one fused
518
+ * ranking, so the return shape is uniform either way.
519
+ *
520
+ * The server validates the whole batch before running any leg, so a malformed query
521
+ * fails the call rather than returning a partial answer that cannot be told apart.
522
+ */
523
+ batchSearch(opts: BatchSearchOptions): Promise<Hit[][]>;
225
524
  /**
226
525
  * Embed `text` and upsert it under `id` in `collection` (idempotent on `id`).
227
526
  * With `opts.mode === "summarize"` the server summarizes first, embeds the
@@ -242,6 +541,8 @@ declare class NidusClient {
242
541
  compact(): Promise<void>;
243
542
  /** Run a search-family request and decode the resulting hits' attrs. */
244
543
  private searchRequest;
544
+ /** One wire hit into a {@link Hit}. Shared so every search surface decodes identically. */
545
+ private decodeHit;
245
546
  /** Issue a request and parse a JSON body, mapping a non-2xx to {@link NidusError}. */
246
547
  private request;
247
548
  /** The bare transport: headers, auth, timeout, and transport-error mapping. */
@@ -275,6 +576,11 @@ declare const f: {
275
576
  readonly ne: (key: string, value: AttrInput) => Predicate;
276
577
  /** `attrs[key]` is a `Str` matching the glob pattern (`*`, `?`, `[..]`). */
277
578
  readonly glob: (key: string, pattern: string) => Predicate;
579
+ /**
580
+ * {@link f.glob}, ignoring **ASCII** case on both sides — `"Src/*"` matches
581
+ * `"src/main.rs"`. Non-ASCII is not folded (`É` does not match `é`).
582
+ */
583
+ readonly iglob: (key: string, pattern: string) => Predicate;
278
584
  /** `attrs[key]` equals one of `values`. */
279
585
  readonly in: (key: string, values: AttrInput[]) => Predicate;
280
586
  /** `attrs[key]` is present and equals none of `values`. */
@@ -287,27 +593,69 @@ declare const f: {
287
593
  readonly gt: (key: string, value: AttrInput) => Predicate;
288
594
  /** `attrs[key] >= value` (same-type, orderable). */
289
595
  readonly ge: (key: string, value: AttrInput) => Predicate;
596
+ /** `attrs[key]` is a `List` containing `value` (whole-element, not substring). */
597
+ readonly contains: (key: string, value: AttrInput) => Predicate;
598
+ /** `attrs[key]` is a present `List` not containing `value`. */
599
+ readonly notContains: (key: string, value: AttrInput) => Predicate;
600
+ /** `attrs[key]` is a `List` sharing at least one element with `values`. */
601
+ readonly containsAny: (key: string, values: AttrInput[]) => Predicate;
602
+ /** Every sub-predicate holds. `all()` is `true`. */
603
+ readonly all: (...preds: Predicate[]) => Predicate;
604
+ /** At least one sub-predicate holds. `any()` is `false`. */
605
+ readonly any: (...preds: Predicate[]) => Predicate;
606
+ /**
607
+ * The sub-predicate does not hold. Differs from {@link f.ne} on an absent key:
608
+ * `not(eq(k, v))` matches a record with no `k`, `ne(k, v)` does not.
609
+ */
610
+ readonly not: (pred: Predicate) => Predicate;
611
+ /**
612
+ * `attrs[key]` is within `maxEdits` Levenshtein edits of `text`, ASCII-case-folded on
613
+ * both sides; a `List` matches if any element does. The only three-element predicate.
614
+ * A `maxEdits` above 8 is refused by the server, not clamped.
615
+ */
616
+ readonly fuzzy: (key: string, text: string, maxEdits: number) => Predicate;
617
+ /**
618
+ * Every token of `text` appears among `attrs[key]`'s tokens, in any order. Tokens are
619
+ * ASCII-case-folded runs of alphanumerics; a `List` matches if any single element does.
620
+ */
621
+ readonly containsAllTokens: (key: string, text: string) => Predicate;
622
+ /** At least one token of `text` appears among `attrs[key]`'s tokens. Empty never matches. */
623
+ readonly containsAnyToken: (key: string, text: string) => Predicate;
624
+ /** `text`'s tokens appear consecutively and in order — a phrase match. */
625
+ readonly containsTokenSequence: (key: string, text: string) => Predicate;
626
+ /**
627
+ * `attrs[key]` matches the regular expression, **anchored at both ends** like
628
+ * {@link f.glob} — `.*` opts back into a substring search, and `(?i)` into case folding.
629
+ * The syntax is Rust's `regex`, not JS's: no backreferences and no lookaround.
630
+ */
631
+ readonly regex: (key: string, pattern: string) => Predicate;
290
632
  /** Collect predicates into a {@link Filter} (purely sugar — they already AND). */
291
633
  readonly and: (...preds: Predicate[]) => Filter;
292
634
  };
293
635
 
294
636
  /**
295
637
  * Value constructors mirroring the `Value` variants. `v.int` requires a safe
296
- * integer (the store's attribute integer is an `i64`; a non-integer would be a
297
- * silent type error since there is no float attribute).
638
+ * integer and `v.float` a finite number `NaN`/`Infinity` have no JSON spelling,
639
+ * and `JSON.stringify` would quietly write `null`.
298
640
  */
299
641
  declare const v: {
300
642
  readonly str: (s: string) => Value;
301
643
  readonly int: (n: number) => Value;
644
+ readonly float: (n: number) => Value;
302
645
  readonly bool: (b: boolean) => Value;
303
646
  readonly list: (items: string[]) => Value;
647
+ /**
648
+ * A UTC instant, from a `Date` or a raw epoch-millisecond count. Milliseconds is
649
+ * the wire type, so there is no sub-millisecond precision and no timezone.
650
+ */
651
+ readonly datetime: (when: Date | number) => Value;
304
652
  /** The explicit `Null` value — set-but-empty, distinct from an absent key. */
305
653
  readonly nil: () => Value;
306
654
  };
307
655
  /**
308
656
  * Normalize a caller-supplied {@link AttrInput} into the wire {@link Value} shape.
309
657
  * Plain scalars map by type; an already-tagged `Value` passes through unchanged.
310
- * Throws on a non-integer number or a non-string list element.
658
+ * Throws on a non-finite number, an invalid `Date`, or a non-string list element.
311
659
  */
312
660
  declare function encodeValue(input: AttrInput): Value;
313
661
  /** Normalize a whole `attrs` map of {@link AttrInput} into wire {@link Value}s. */
@@ -317,4 +665,4 @@ declare function decodeValue(value: Value): DecodedValue;
317
665
  /** Decode a whole wire `attrs` map back to plain JS values. */
318
666
  declare function decodeAttrs(attrs: Record<string, Value>): Record<string, DecodedValue>;
319
667
 
320
- export { type AnnInfo, type AttrInput, type DecodedRecord, type DecodedValue, type FetchLike, type Filter, type Footprint, type Hit, type HybridSearchOptions, type ListOptions, NidusClient, type NidusClientOptions, NidusError, type NidusRecord, type Predicate, type RecallOptions, type RecordInput, type RememberOptions, type SearchOptions, type Stats, type TextSearchOptions, type Value, decodeAttrs, decodeValue, encodeAttrs, encodeValue, f, v };
668
+ export { type AggregateOptions, type Aggregation, type AnnInfo, type AnnotationOptions, type Annotations, type AttrInput, type ClauseScore, type Decay, type DecodedRecord, type DecodedValue, type FetchLike, type Filter, type Footprint, type Fragment, type FtsCombine, type FtsField, type Highlight, type HighlightOptions, type Hit, type HybridQuerySpelling, type HybridSearchBase, type HybridSearchOptions, type LegScore, type LimitPer, type ListOptions, NidusClient, type NidusClientOptions, NidusError, type NidusRecord, type OrderBy, type Predicate, type ProjectionOptions, type RankBy, type RankingOptions, type RecallOptions, type RecordInput, type RememberOptions, type SearchOptions, type Stats, type TextClause, type TextQuerySpelling, type TextSearchBase, type TextSearchOptions, type Value, decodeAttrs, decodeValue, encodeAttrs, encodeValue, f, v };