@littlebigbrain/client 0.7.0 → 0.8.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/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { ImportLine, LbbClientOptions, ListResponse, RawLbbResponse, RdfExportOptions, RdfImportOptions, Schemas, SparqlResults } from "./types.js";
1
+ import type { ImportLine, LbbClientOptions, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfExportOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults } from "./types.js";
2
2
  import { type CallOptions, type RequestOptions } from "./transport.js";
3
3
  import { ContextNamespace, EntityNamespace, GraphNamespace, IndexNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace } from "./namespaces.js";
4
4
  export { parseSparqlResults } from "./types.js";
5
- export type { AttributeFilter, AttributeFilterOp, AttributeFilterValue, EntityAttributeFilterOptions, EntityPropertiesLine, FetchLike, FlatProperties, ImportLine, LbbClientOptions, LbbRequestEvent, LbbResponseEvent, LbbRetryEvent, LbbErrorPayload, ListResponse, RawLbbResponse, RdfExportOptions, RdfImportOptions, Schemas, SparqlResults, SparqlResultsJson, SparqlTerm, AskRequest, AskResponse, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./types.js";
5
+ export type { AttributeFilter, AttributeFilterOp, AttributeFilterValue, EntityAttributeFilterOptions, EntityPropertiesLine, FetchLike, FlatProperties, ImportLine, LbbClientOptions, LbbRequestEvent, LbbResponseEvent, LbbRetryEvent, LbbErrorPayload, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfExportOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults, SparqlResultsJson, SparqlTerm, AskRequest, AskResponse, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./types.js";
6
6
  export { LbbError } from "./transport.js";
7
7
  export type { CallOptions, Query, QueryValue, RequestOptions, } from "./transport.js";
8
8
  export type { EntityListOptions, HybridSearchOptions } from "./namespaces.js";
@@ -36,6 +36,8 @@ export declare class LbbClient {
36
36
  private readonly onRequest?;
37
37
  private readonly onResponse?;
38
38
  private readonly onRetry?;
39
+ /** A5 default read consistency applied when a read omits its own value. */
40
+ readonly defaultConsistency?: SearchConsistency;
39
41
  readonly context: ContextNamespace;
40
42
  readonly search: SearchNamespace;
41
43
  readonly indexes: IndexNamespace;
@@ -58,6 +60,17 @@ export declare class LbbClient {
58
60
  branch?: string;
59
61
  stack?: string;
60
62
  }): LbbClient;
63
+ /**
64
+ * A5: fold read-consistency options into a request body's own `consistency` /
65
+ * `min_indexed_seq` fields (the shape used by full-text, embedding, and
66
+ * structured-SPARQL bodies). A per-call value wins over the client
67
+ * `defaultConsistency`; an explicit body field wins over both.
68
+ */
69
+ resolveConsistency(opts?: ReadConsistencyOptions): SearchConsistency | undefined;
70
+ private mergeReadConsistency;
71
+ /** A5: read-consistency options rendered as URL query params, for the routes
72
+ * that carry consistency on the URL (SPARQL-text, graph summary). */
73
+ private readConsistencyQuery;
61
74
  private buildUrl;
62
75
  rawRequest<T>(method: string, path: string, opts?: RequestOptions): Promise<RawLbbResponse<T>>;
63
76
  request<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
@@ -66,7 +79,9 @@ export declare class LbbClient {
66
79
  /** Commit triplets and optional entity embeddings. Prefer `client.graph("main").facts.create(...)`. */
67
80
  commit(body: Schemas["TripletCommitFile"], opts?: {
68
81
  idempotencyKey?: string;
69
- }): Promise<Schemas["GraphCommitResponse"]>;
82
+ }): Promise<Schemas["GraphCommitResponse"] & {
83
+ commitSeq: number;
84
+ }>;
70
85
  /**
71
86
  * Validate-only preflight: run the same ontology/schema validation a real
72
87
  * commit would and report the would-be effect (`op_count`, `written_properties`,
@@ -95,7 +110,9 @@ export declare class LbbClient {
95
110
  observedAt?: string;
96
111
  index?: boolean;
97
112
  idempotencyKey?: string;
98
- }): Promise<Schemas["GraphImportResponse"]>;
113
+ }): Promise<Schemas["GraphImportResponse"] & {
114
+ commitSeq: number | null;
115
+ }>;
99
116
  /**
100
117
  * Bulk-ingest N-Triples, Turtle, N-Quads, or TriG without client-side conversion. Resource-object
101
118
  * triples become keyed Resource edges; literal-object triples become text
@@ -360,7 +377,7 @@ export declare class LbbClient {
360
377
  allowRegression?: boolean;
361
378
  }): Promise<unknown>;
362
379
  /** Full semantic hybrid search from a request body (`POST /v1/graph/search`). */
363
- graphSearch(body: Schemas["SemanticGraphSearchRequest"]): Promise<Schemas["SemanticGraphSearchResponse"]>;
380
+ graphSearch(body: Schemas["SemanticGraphSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["SemanticGraphSearchResponse"]>;
364
381
  /** Reciprocal-rank-fusion across sub-queries. */
365
382
  multiSearch(body: Schemas["HybridMultiSearchRequest"]): Promise<Schemas["HybridMultiSearchResponse"]>;
366
383
  /**
@@ -407,9 +424,9 @@ export declare class LbbClient {
407
424
  /** Export the stored relevance labels as qrels-style rows for training. */
408
425
  searchFeedbackExport(): Promise<Schemas["SearchFeedbackExportResponse"]>;
409
426
  /** BM25 search. */
410
- fullTextSearch(body: Schemas["FullTextSearchRequest"]): Promise<Schemas["FullTextSearchResponse"]>;
427
+ fullTextSearch(body: Schemas["FullTextSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["FullTextSearchResponse"]>;
411
428
  /** ANN/vector search. */
412
- embeddingSearch(body: Schemas["EmbeddingSearchRequest"]): Promise<Schemas["EmbeddingSearchResponse"]>;
429
+ embeddingSearch(body: Schemas["EmbeddingSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["EmbeddingSearchResponse"]>;
413
430
  /** Bounded k-hop graph traversal. */
414
431
  traverse(body: Schemas["TraverseRequest"]): Promise<Schemas["TraverseResponse"]>;
415
432
  /** Resolve a query to seed entities, then return bounded paths. */
@@ -502,9 +519,9 @@ export declare class LbbClient {
502
519
  * keys come back per group in `groups[].value_keys[<as>]`, entity keys in
503
520
  * `groups[].keys`.
504
521
  */
505
- sparql(body: Schemas["SparqlSelectRequest"]): Promise<Schemas["SparqlSelectResponse"]>;
506
- /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. */
507
- sparqlText(body: Schemas["SparqlTextRequest"]): Promise<Schemas["SparqlTextResponse"]>;
522
+ sparql(body: Schemas["SparqlSelectRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["SparqlSelectResponse"]>;
523
+ /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. The text dialect carries `consistency`/`min_indexed_seq` on the URL. */
524
+ sparqlText(body: Schemas["SparqlTextRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["SparqlTextResponse"]>;
508
525
  /**
509
526
  * Run a SPARQL 1.1 text query and return parsed results — the ergonomic
510
527
  * complement to {@link sparqlText} (which hands back the raw results string).
@@ -512,7 +529,7 @@ export declare class LbbClient {
512
529
  * `rows` is the bindings flattened to `{ variable: lexicalValue }`, `boolean`
513
530
  * is the ASK answer (or `null` for a SELECT).
514
531
  */
515
- sparqlRows(body: Schemas["SparqlTextRequest"]): Promise<SparqlResults>;
532
+ sparqlRows(body: Schemas["SparqlTextRequest"], opts?: ReadConsistencyOptions): Promise<SparqlResults>;
516
533
  /**
517
534
  * Basic-graph-pattern query with group-graph-pattern combinators
518
535
  * (UNION / OPTIONAL / MINUS / EXISTS / NOT EXISTS) folded over the base
@@ -634,8 +651,8 @@ export declare class LbbClient {
634
651
  timeoutMs?: number;
635
652
  pollIntervalMs?: number;
636
653
  }): Promise<IndexLineageObservation>;
637
- /** Graph counts and type/relation buckets. */
638
- summary(): Promise<Schemas["GraphSummaryResponse"]>;
654
+ /** Graph counts and type/relation buckets. Carries `consistency`/`min_indexed_seq` on the URL. */
655
+ summary(opts?: ReadConsistencyOptions): Promise<Schemas["GraphSummaryResponse"]>;
639
656
  /** List the graphs (and branches) under the scoped tenant. */
640
657
  listGraphs(): Promise<Schemas["GraphListResponse"]>;
641
658
  }
package/dist/client.js CHANGED
@@ -24,6 +24,8 @@ export class LbbClient {
24
24
  onRequest;
25
25
  onResponse;
26
26
  onRetry;
27
+ /** A5 default read consistency applied when a read omits its own value. */
28
+ defaultConsistency;
27
29
  context;
28
30
  search;
29
31
  indexes;
@@ -49,6 +51,7 @@ export class LbbClient {
49
51
  this.onRequest = options.onRequest;
50
52
  this.onResponse = options.onResponse;
51
53
  this.onRetry = options.onRetry;
54
+ this.defaultConsistency = options.defaultConsistency;
52
55
  if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) {
53
56
  throw new RangeError("maxRetries must be a non-negative integer");
54
57
  }
@@ -103,8 +106,38 @@ export class LbbClient {
103
106
  onRequest: this.onRequest,
104
107
  onResponse: this.onResponse,
105
108
  onRetry: this.onRetry,
109
+ defaultConsistency: this.defaultConsistency,
106
110
  });
107
111
  }
112
+ /**
113
+ * A5: fold read-consistency options into a request body's own `consistency` /
114
+ * `min_indexed_seq` fields (the shape used by full-text, embedding, and
115
+ * structured-SPARQL bodies). A per-call value wins over the client
116
+ * `defaultConsistency`; an explicit body field wins over both.
117
+ */
118
+ resolveConsistency(opts) {
119
+ return opts?.consistency ?? this.defaultConsistency;
120
+ }
121
+ mergeReadConsistency(body, opts) {
122
+ const consistency = this.resolveConsistency(opts);
123
+ const merged = { ...body };
124
+ if (consistency !== undefined && merged.consistency === undefined) {
125
+ merged.consistency = consistency;
126
+ }
127
+ if (opts?.minIndexedSeq !== undefined &&
128
+ merged.min_indexed_seq === undefined) {
129
+ merged.min_indexed_seq = opts.minIndexedSeq;
130
+ }
131
+ return merged;
132
+ }
133
+ /** A5: read-consistency options rendered as URL query params, for the routes
134
+ * that carry consistency on the URL (SPARQL-text, graph summary). */
135
+ readConsistencyQuery(opts) {
136
+ return {
137
+ consistency: this.resolveConsistency(opts),
138
+ min_indexed_seq: opts?.minIndexedSeq,
139
+ };
140
+ }
108
141
  buildUrl(path, query) {
109
142
  const params = [];
110
143
  const push = (key, value) => params.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
@@ -292,11 +325,14 @@ export class LbbClient {
292
325
  }
293
326
  // --- writes ---
294
327
  /** Commit triplets and optional entity embeddings. Prefer `client.graph("main").facts.create(...)`. */
295
- commit(body, opts = {}) {
296
- return this.request("POST", "/v1/graph/commit", {
328
+ async commit(body, opts = {}) {
329
+ const response = await this.request("POST", "/v1/graph/commit", {
297
330
  body,
298
331
  idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("facts.create"),
299
332
  });
333
+ // A5: surface the committed sequence as `commitSeq` so the write→floor→read
334
+ // loop reads naturally: `const { commitSeq } = await client.commit(…)`.
335
+ return { ...response, commitSeq: response.commit_seq };
300
336
  }
301
337
  /**
302
338
  * Validate-only preflight: run the same ontology/schema validation a real
@@ -325,11 +361,11 @@ export class LbbClient {
325
361
  * the throttle): import the whole dataset, index once. The response's `index`
326
362
  * object reports whether the build ran or was skipped.
327
363
  */
328
- import(lines, opts = {}) {
364
+ async import(lines, opts = {}) {
329
365
  const ndjson = typeof lines === "string"
330
366
  ? lines
331
367
  : lines.map((line) => JSON.stringify(line)).join("\n");
332
- return this.request("POST", "/v1/graph/import", {
368
+ const response = await this.request("POST", "/v1/graph/import", {
333
369
  rawBody: ndjson,
334
370
  contentType: "application/x-ndjson",
335
371
  query: {
@@ -340,6 +376,9 @@ export class LbbClient {
340
376
  },
341
377
  idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("import"),
342
378
  });
379
+ // A5: surface the last committed sequence (null for an empty import) so the
380
+ // write→floor→read loop reads naturally after a bulk load.
381
+ return { ...response, commitSeq: response.committed_commit_seq ?? null };
343
382
  }
344
383
  /**
345
384
  * Bulk-ingest N-Triples, Turtle, N-Quads, or TriG without client-side conversion. Resource-object
@@ -739,8 +778,15 @@ export class LbbClient {
739
778
  }
740
779
  // --- search ---
741
780
  /** Full semantic hybrid search from a request body (`POST /v1/graph/search`). */
742
- graphSearch(body) {
743
- return this.request("POST", "/v1/graph/search", { body });
781
+ graphSearch(body, opts) {
782
+ // Consistency for hybrid graph search lives on the nested `search` options.
783
+ const consistency = this.resolveConsistency(opts);
784
+ const search = consistency !== undefined || opts?.minIndexedSeq !== undefined
785
+ ? this.mergeReadConsistency(body.search ?? {}, opts)
786
+ : body.search;
787
+ return this.request("POST", "/v1/graph/search", {
788
+ body: { ...body, search },
789
+ });
744
790
  }
745
791
  /** Reciprocal-rank-fusion across sub-queries. */
746
792
  multiSearch(body) {
@@ -805,12 +851,16 @@ export class LbbClient {
805
851
  return this.request("GET", "/v1/search/feedback/export");
806
852
  }
807
853
  /** BM25 search. */
808
- fullTextSearch(body) {
809
- return this.request("POST", "/v1/search/full-text", { body });
854
+ fullTextSearch(body, opts) {
855
+ return this.request("POST", "/v1/search/full-text", {
856
+ body: this.mergeReadConsistency(body, opts),
857
+ });
810
858
  }
811
859
  /** ANN/vector search. */
812
- embeddingSearch(body) {
813
- return this.request("POST", "/v1/search/embedding", { body });
860
+ embeddingSearch(body, opts) {
861
+ return this.request("POST", "/v1/search/embedding", {
862
+ body: this.mergeReadConsistency(body, opts),
863
+ });
814
864
  }
815
865
  // --- traversal ---
816
866
  /** Bounded k-hop graph traversal. */
@@ -946,12 +996,17 @@ export class LbbClient {
946
996
  * keys come back per group in `groups[].value_keys[<as>]`, entity keys in
947
997
  * `groups[].keys`.
948
998
  */
949
- sparql(body) {
950
- return this.request("POST", "/v1/query/sparql", { body });
999
+ sparql(body, opts) {
1000
+ return this.request("POST", "/v1/query/sparql", {
1001
+ body: this.mergeReadConsistency(body, opts),
1002
+ });
951
1003
  }
952
- /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. */
953
- sparqlText(body) {
954
- return this.request("POST", "/v1/query/sparql-text", { body });
1004
+ /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. The text dialect carries `consistency`/`min_indexed_seq` on the URL. */
1005
+ sparqlText(body, opts) {
1006
+ return this.request("POST", "/v1/query/sparql-text", {
1007
+ body,
1008
+ query: this.readConsistencyQuery(opts),
1009
+ });
955
1010
  }
956
1011
  /**
957
1012
  * Run a SPARQL 1.1 text query and return parsed results — the ergonomic
@@ -960,8 +1015,8 @@ export class LbbClient {
960
1015
  * `rows` is the bindings flattened to `{ variable: lexicalValue }`, `boolean`
961
1016
  * is the ASK answer (or `null` for a SELECT).
962
1017
  */
963
- async sparqlRows(body) {
964
- return parseSparqlResults(await this.sparqlText(body));
1018
+ async sparqlRows(body, opts) {
1019
+ return parseSparqlResults(await this.sparqlText(body, opts));
965
1020
  }
966
1021
  /**
967
1022
  * Basic-graph-pattern query with group-graph-pattern combinators
@@ -1175,9 +1230,11 @@ export class LbbClient {
1175
1230
  await sleep(opts.pollIntervalMs ?? 250);
1176
1231
  }
1177
1232
  }
1178
- /** Graph counts and type/relation buckets. */
1179
- summary() {
1180
- return this.request("GET", "/v1/graph/summary");
1233
+ /** Graph counts and type/relation buckets. Carries `consistency`/`min_indexed_seq` on the URL. */
1234
+ summary(opts) {
1235
+ return this.request("GET", "/v1/graph/summary", {
1236
+ query: this.readConsistencyQuery(opts),
1237
+ });
1181
1238
  }
1182
1239
  /** List the graphs (and branches) under the scoped tenant. */
1183
1240
  listGraphs() {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { LbbClient, LbbError, parseSparqlResults } from "./client.js";
2
- export type { LbbClientOptions, CallOptions, RequestOptions, EntityListOptions, HybridSearchOptions, LbbRequestEvent, LbbResponseEvent, FetchLike, Schemas, SparqlResults, SparqlResultsJson, SparqlTerm, AskRequest, AskResponse, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./client.js";
2
+ export type { LbbClientOptions, CallOptions, RequestOptions, EntityListOptions, HybridSearchOptions, LbbRequestEvent, LbbResponseEvent, FetchLike, ReadConsistencyOptions, SearchConsistency, Schemas, SparqlResults, SparqlResultsJson, SparqlTerm, AskRequest, AskResponse, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./client.js";
3
3
  export type { components, paths, operations } from "./schema.js";
@@ -1,10 +1,14 @@
1
1
  import type { LbbClient } from "./client.js";
2
2
  import type { CallOptions } from "./transport.js";
3
- import { type EntityAttributeFilterOptions, type ImportLine, type ListResponse, type RdfExportOptions, type RdfImportOptions, type Schemas } from "./types.js";
3
+ import { type EntityAttributeFilterOptions, type ImportLine, type ListResponse, type ReadConsistencyOptions, type RdfExportOptions, type RdfImportOptions, type Schemas } from "./types.js";
4
4
  export interface HybridSearchOptions extends CallOptions {
5
5
  topK?: number;
6
6
  source?: string;
7
7
  consistency?: string;
8
+ /** A5 read-your-writes floor (`min_indexed_seq`): the committed sequence a
9
+ * write returned; under eventual, an uncovered floor yields a retryable
10
+ * `read_your_writes_pending` 429. */
11
+ minIndexedSeq?: number;
8
12
  lexical?: boolean;
9
13
  bm25?: boolean;
10
14
  vector?: boolean;
@@ -96,8 +100,8 @@ export declare class SearchNamespace {
96
100
  feedback(body: Schemas["SearchFeedbackRequest"], opts?: CallOptions): Promise<Schemas["SearchFeedbackResponse"]>;
97
101
  feedbackExport(opts?: CallOptions): Promise<Schemas["SearchFeedbackExportResponse"]>;
98
102
  feedbackSummary(opts?: CallOptions): Promise<Schemas["SearchFeedbackSummaryResponse"]>;
99
- fullText(body: Schemas["FullTextSearchRequest"], opts?: CallOptions): Promise<Schemas["FullTextSearchResponse"]>;
100
- vector(body: Schemas["EmbeddingSearchRequest"], opts?: CallOptions): Promise<Schemas["EmbeddingSearchResponse"]>;
103
+ fullText(body: Schemas["FullTextSearchRequest"], opts?: CallOptions & ReadConsistencyOptions): Promise<Schemas["FullTextSearchResponse"]>;
104
+ vector(body: Schemas["EmbeddingSearchRequest"], opts?: CallOptions & ReadConsistencyOptions): Promise<Schemas["EmbeddingSearchResponse"]>;
101
105
  }
102
106
  export declare class SchemaNamespace {
103
107
  private readonly client;
@@ -225,9 +229,9 @@ export declare class OntologyNamespace {
225
229
  export declare class QueryNamespace {
226
230
  private readonly client;
227
231
  constructor(client: LbbClient);
228
- structured(body: Schemas["SparqlSelectRequest"], opts?: CallOptions): Promise<Schemas["SparqlSelectResponse"]>;
229
- sparql(body: Schemas["SparqlTextRequest"], opts?: CallOptions): Promise<import("./types.js").SparqlResults>;
230
- sparqlRaw(body: Schemas["SparqlTextRequest"], opts?: CallOptions): Promise<Schemas["SparqlTextResponse"]>;
232
+ structured(body: Schemas["SparqlSelectRequest"], opts?: CallOptions & ReadConsistencyOptions): Promise<Schemas["SparqlSelectResponse"]>;
233
+ sparql(body: Schemas["SparqlTextRequest"], opts?: CallOptions & ReadConsistencyOptions): Promise<import("./types.js").SparqlResults>;
234
+ sparqlRaw(body: Schemas["SparqlTextRequest"], opts?: CallOptions & ReadConsistencyOptions): Promise<Schemas["SparqlTextResponse"]>;
231
235
  analytics(body: Schemas["AnalyticQueryRequest"], opts?: CallOptions): Promise<Schemas["AnalyticQueryResponse"]>;
232
236
  shacl(body: Schemas["ShaclQueryRequest"], opts?: CallOptions): Promise<Schemas["ShaclQueryResponse"]>;
233
237
  infer(body: Schemas["InferenceRunRequest"], opts?: CallOptions): Promise<Schemas["InferenceRunResponse"]>;
@@ -1,4 +1,18 @@
1
1
  import { attributeFilter, firstPatternVariable, parseSparqlResults, } from "./types.js";
2
+ /** A5: fold read-consistency options into a request body's `consistency` /
3
+ * `min_indexed_seq` fields; a per-call value wins over the client default. */
4
+ function withReadConsistency(client, body, opts) {
5
+ const consistency = opts.consistency ?? client.defaultConsistency;
6
+ const merged = { ...body };
7
+ if (consistency !== undefined && merged.consistency === undefined) {
8
+ merged.consistency = consistency;
9
+ }
10
+ if (opts.minIndexedSeq !== undefined &&
11
+ merged.min_indexed_seq === undefined) {
12
+ merged.min_indexed_seq = opts.minIndexedSeq;
13
+ }
14
+ return merged;
15
+ }
2
16
  function transportOptions(options) {
3
17
  return {
4
18
  idempotencyKey: options.idempotencyKey,
@@ -186,10 +200,14 @@ export class SearchNamespace {
186
200
  }
187
201
  hybrid(input, opts = {}) {
188
202
  if (typeof input !== "string") {
203
+ const search = withReadConsistency(this.client, input.search ?? {}, {
204
+ consistency: opts.consistency,
205
+ minIndexedSeq: opts.minIndexedSeq,
206
+ });
189
207
  return this.client.request("POST", "/v1/graph/search", {
190
208
  ...transportOptions(opts),
191
209
  retry: opts.retry ?? true,
192
- body: input,
210
+ body: { ...input, search },
193
211
  });
194
212
  }
195
213
  return this.client.request("GET", "/v1/search", {
@@ -198,7 +216,8 @@ export class SearchNamespace {
198
216
  query: input,
199
217
  top_k: opts.topK,
200
218
  source: opts.source,
201
- consistency: opts.consistency,
219
+ consistency: opts.consistency ?? this.client.defaultConsistency,
220
+ min_indexed_seq: opts.minIndexedSeq,
202
221
  lexical: opts.lexical,
203
222
  bm25: opts.bm25,
204
223
  vector: opts.vector,
@@ -233,14 +252,14 @@ export class SearchNamespace {
233
252
  return this.client.request("POST", "/v1/search/full-text", {
234
253
  ...opts,
235
254
  retry: opts.retry ?? true,
236
- body,
255
+ body: withReadConsistency(this.client, body, opts),
237
256
  });
238
257
  }
239
258
  vector(body, opts = {}) {
240
259
  return this.client.request("POST", "/v1/search/embedding", {
241
260
  ...opts,
242
261
  retry: opts.retry ?? true,
243
- body,
262
+ body: withReadConsistency(this.client, body, opts),
244
263
  });
245
264
  }
246
265
  }
@@ -537,11 +556,20 @@ export class QueryNamespace {
537
556
  return this.client.request("POST", "/v1/query/sparql", {
538
557
  ...opts,
539
558
  retry: opts.retry ?? true,
540
- body,
559
+ body: withReadConsistency(this.client, body, opts),
541
560
  });
542
561
  }
543
562
  async sparql(body, opts = {}) {
544
- const response = await this.client.request("POST", "/v1/query/sparql-text", { ...opts, retry: opts.retry ?? true, body });
563
+ // The text dialect carries consistency/floor on the URL, not the body.
564
+ const response = await this.client.request("POST", "/v1/query/sparql-text", {
565
+ ...opts,
566
+ retry: opts.retry ?? true,
567
+ body,
568
+ query: {
569
+ consistency: opts.consistency ?? this.client.defaultConsistency,
570
+ min_indexed_seq: opts.minIndexedSeq,
571
+ },
572
+ });
545
573
  return parseSparqlResults(response);
546
574
  }
547
575
  sparqlRaw(body, opts = {}) {
@@ -549,6 +577,10 @@ export class QueryNamespace {
549
577
  ...opts,
550
578
  retry: opts.retry ?? true,
551
579
  body,
580
+ query: {
581
+ consistency: opts.consistency ?? this.client.defaultConsistency,
582
+ min_indexed_seq: opts.minIndexedSeq,
583
+ },
552
584
  });
553
585
  }
554
586
  analytics(body, opts = {}) {
package/dist/schema.d.ts CHANGED
@@ -2969,6 +2969,7 @@ export interface components {
2969
2969
  facets?: components["schemas"]["FacetRequest"][] | null;
2970
2970
  filters?: null | components["schemas"]["SearchFilterExpr"];
2971
2971
  max_clusters?: number | null;
2972
+ min_indexed_seq?: null | components["schemas"]["CommitSeq"];
2972
2973
  probe_count?: number | null;
2973
2974
  provider?: null | components["schemas"]["EmbeddingProviderConfig"];
2974
2975
  query: string;
@@ -3474,6 +3475,7 @@ export interface components {
3474
3475
  explain: boolean;
3475
3476
  facets?: components["schemas"]["FacetRequest"][] | null;
3476
3477
  filters?: null | components["schemas"]["SearchFilterExpr"];
3478
+ min_indexed_seq?: null | components["schemas"]["CommitSeq"];
3477
3479
  /**
3478
3480
  * @description Number of leading results to skip after ordering, for stateless paging:
3479
3481
  * `offset = page * top_k` walks pages deterministically. Defaults to 0.
@@ -6347,7 +6349,19 @@ export interface components {
6347
6349
  /** Format: float */
6348
6350
  weighted_score: number;
6349
6351
  };
6350
- /** @enum {string} */
6352
+ /**
6353
+ * @description Read consistency for a query surface (search, graph summary, SPARQL).
6354
+ *
6355
+ * A5 (2026-07-21 product decision): `Eventual` is the **default**. It serves the
6356
+ * last published index/dataset state at its watermark with no in-memory fold of
6357
+ * the un-indexed tail — the lowest-latency mode and the availability floor
6358
+ * during any indexing backlog. The served watermark rides back on
6359
+ * `SnapshotView::served_at_seq`. `Strong` (fold the un-indexed tail up to the
6360
+ * query head) is now explicit opt-in; the precise read-your-writes contract
6361
+ * (`min_indexed_seq`) replaces most former uses of strong. See
6362
+ * `docs/architecture/segment-native-indexing.md` §6.
6363
+ * @enum {string}
6364
+ */
6351
6365
  SearchConsistency: "strong" | "eventual";
6352
6366
  SearchEngineOptions: {
6353
6367
  bm25?: boolean;
@@ -6360,6 +6374,7 @@ export interface components {
6360
6374
  graph_anchor?: null | components["schemas"]["GraphAnchor"];
6361
6375
  lexical?: boolean;
6362
6376
  max_clusters?: number | null;
6377
+ min_indexed_seq?: null | components["schemas"]["CommitSeq"];
6363
6378
  probe_count?: number | null;
6364
6379
  profile?: null | components["schemas"]["RetrievalProfileId"];
6365
6380
  provider?: null | components["schemas"]["EmbeddingProviderConfig"];
@@ -7688,6 +7703,7 @@ export interface components {
7688
7703
  * `GET /v1/graph/metadata` and on the entity read's metadata.
7689
7704
  */
7690
7705
  compacted_seq: components["schemas"]["CommitSeq"];
7706
+ served_at_seq?: null | components["schemas"]["CommitSeq"];
7691
7707
  /**
7692
7708
  * @description F2 (roadmap 18.3): true when this snapshot was served from the in-memory
7693
7709
  * cache while storage was degraded and the latest head could not be
@@ -7697,8 +7713,9 @@ export interface components {
7697
7713
  */
7698
7714
  stale?: boolean;
7699
7715
  /**
7700
- * @description The reason a read is `stale` (F2). `"storage_degraded"` today; omitted
7701
- * when not stale.
7716
+ * @description The reason a read is `stale`. `"storage_degraded"` (F2) or
7717
+ * `"eventual_consistency"` (A3: served from the last published index/dataset
7718
+ * state at [`Self::served_at_seq`], not head); omitted when not stale.
7702
7719
  */
7703
7720
  stale_reason?: string | null;
7704
7721
  };
@@ -7875,6 +7892,7 @@ export interface components {
7875
7892
  * Projection, DISTINCT, and limit/offset are ignored.
7876
7893
  */
7877
7894
  ask?: boolean;
7895
+ consistency?: null | components["schemas"]["SearchConsistency"];
7878
7896
  /** @description `SELECT DISTINCT`: drop duplicate projected rows. */
7879
7897
  distinct?: boolean;
7880
7898
  /**
@@ -7919,6 +7937,7 @@ export interface components {
7919
7937
  * (see `AnalyticQueryRequest::max_solutions`).
7920
7938
  */
7921
7939
  max_solutions?: number | null;
7940
+ min_indexed_seq?: null | components["schemas"]["CommitSeq"];
7922
7941
  /** @description Skip this many rows before `limit`. */
7923
7942
  offset?: number | null;
7924
7943
  /**
@@ -8035,6 +8054,7 @@ export interface components {
8035
8054
  /** @description SPARQL 1.1 Query Results JSON, serialized. */
8036
8055
  results: string;
8037
8056
  row_page: components["schemas"]["RowPage"];
8057
+ snapshot?: null | components["schemas"]["SnapshotView"];
8038
8058
  };
8039
8059
  /**
8040
8060
  * @description A literal constant operand in a FILTER. Scalars compare by typed value;
package/dist/types.d.ts CHANGED
@@ -192,6 +192,32 @@ export interface LbbClientOptions {
192
192
  onResponse?: (event: LbbResponseEvent) => void;
193
193
  /** Called before each backoff sleep, so absorbed retries are observable. Bodies and credentials are never included. */
194
194
  onRetry?: (event: LbbRetryEvent) => void;
195
+ /**
196
+ * A5 default read consistency, applied when a read call omits its own
197
+ * `consistency`. Since A5 the server default is `eventual`; set `"strong"`
198
+ * here to keep every read head-exact by default (carried across `withScope`).
199
+ * A per-call `consistency` always wins.
200
+ */
201
+ defaultConsistency?: SearchConsistency;
202
+ }
203
+ /** A5 read consistency level. Since A5 the server default is `eventual`. */
204
+ export type SearchConsistency = "strong" | "eventual";
205
+ /**
206
+ * A5 read-consistency options shared by the query / search / summary surfaces.
207
+ * Merged into the request as `consistency` / `min_indexed_seq`; a per-call value
208
+ * wins over the client's `defaultConsistency`.
209
+ */
210
+ export interface ReadConsistencyOptions {
211
+ /** Read consistency. Omit to use the client's `defaultConsistency`, else the
212
+ * server default (`eventual`). */
213
+ consistency?: SearchConsistency;
214
+ /**
215
+ * Read-your-writes floor: require the read to be served from state whose
216
+ * watermark ≥ this `commit_seq` (the committed sequence a write returned).
217
+ * Under eventual, a floor not yet covered yields a retryable
218
+ * `read_your_writes_pending` `429` — poll until the write lands.
219
+ */
220
+ minIndexedSeq?: number;
195
221
  }
196
222
  export interface LbbRequestEvent {
197
223
  method: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlebigbrain/client",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "TypeScript client for the little big brain graph + hybrid search HTTP API",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {