@littlebigbrain/client 0.6.1 → 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, LbbStackActivityResponse, LbbStackActivityWindow, ListResponse, RawLbbResponse, RdfExportOptions, RdfImportOptions, Schemas, SparqlResults } from "./types.js";
2
- import { type RequestOptions } from "./transport.js";
1
+ import type { ImportLine, LbbClientOptions, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfExportOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults } from "./types.js";
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, LbbStackActivityResponse, LbbStackActivityWindow, 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
@@ -115,6 +132,42 @@ export declare class LbbClient {
115
132
  }): Promise<Schemas["GraphRetractResponse"]>;
116
133
  /** Create the scoped graph/branch. Construct the client with the desired graph/branch first. */
117
134
  createGraph(): Promise<Schemas["CreateGraphResponse"]>;
135
+ /**
136
+ * Fork a whole graph into a brand-new destination graph in the same tenant.
137
+ * The copy runs as a durable background job (`confirm` is fixed to `dst`, which
138
+ * the route requires to authorize the fork); the destination must not already
139
+ * exist, so the create-only CAS on the server side makes the call safe to
140
+ * retry. The response only acknowledges the enqueue — poll the destination
141
+ * graph's metadata (see `response.poll`) to observe the fork completing: the
142
+ * destination becomes readable once its head is published.
143
+ */
144
+ forkGraph(opts: {
145
+ src: string;
146
+ dst: string;
147
+ }): Promise<Schemas["GraphForkResponse"]>;
148
+ /**
149
+ * Declarative full-state replace: reconcile the scoped graph so its current
150
+ * state matches exactly the NDJSON payload (same line grammar as
151
+ * {@link import} — triplets or `{type,name,properties}` entity records; pass an
152
+ * array, serialized here, or a pre-built NDJSON string). The whole
153
+ * reconciliation lands as one atomic cutover: payload records are upserted, and
154
+ * entities present at the pre-reload head but absent from the payload leave
155
+ * current state (retraction semantics — history is preserved, so an `as_of`
156
+ * read pinned before the cutover still sees the old state). `confirm` must
157
+ * equal the target graph id (reload is semi-destructive). `dryRun` previews the
158
+ * full delta with zero durable changes. The response carries
159
+ * `prior_commit_seq` / `prior_snapshot_token` as the rollback anchor — read
160
+ * them back with `?as_of_commit_seq=<prior_commit_seq>` to see the pre-reload
161
+ * state. An Idempotency-Key scopes the single cutover commit, so a retry
162
+ * replays rather than re-applying.
163
+ */
164
+ reload(lines: ImportLine[] | string, opts: {
165
+ confirm: string;
166
+ dryRun?: boolean;
167
+ strict?: boolean;
168
+ observedAt?: string;
169
+ idempotencyKey?: string;
170
+ }): Promise<Schemas["GraphReloadResponse"]>;
118
171
  /** Fork the scoped branch from an existing branch in the same graph. */
119
172
  createBranch(body: Schemas["GraphBranchCreateRequest"]): Promise<Schemas["GraphBranchCreateResponse"]>;
120
173
  /**
@@ -324,7 +377,7 @@ export declare class LbbClient {
324
377
  allowRegression?: boolean;
325
378
  }): Promise<unknown>;
326
379
  /** Full semantic hybrid search from a request body (`POST /v1/graph/search`). */
327
- graphSearch(body: Schemas["SemanticGraphSearchRequest"]): Promise<Schemas["SemanticGraphSearchResponse"]>;
380
+ graphSearch(body: Schemas["SemanticGraphSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["SemanticGraphSearchResponse"]>;
328
381
  /** Reciprocal-rank-fusion across sub-queries. */
329
382
  multiSearch(body: Schemas["HybridMultiSearchRequest"]): Promise<Schemas["HybridMultiSearchResponse"]>;
330
383
  /**
@@ -371,9 +424,9 @@ export declare class LbbClient {
371
424
  /** Export the stored relevance labels as qrels-style rows for training. */
372
425
  searchFeedbackExport(): Promise<Schemas["SearchFeedbackExportResponse"]>;
373
426
  /** BM25 search. */
374
- fullTextSearch(body: Schemas["FullTextSearchRequest"]): Promise<Schemas["FullTextSearchResponse"]>;
427
+ fullTextSearch(body: Schemas["FullTextSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["FullTextSearchResponse"]>;
375
428
  /** ANN/vector search. */
376
- embeddingSearch(body: Schemas["EmbeddingSearchRequest"]): Promise<Schemas["EmbeddingSearchResponse"]>;
429
+ embeddingSearch(body: Schemas["EmbeddingSearchRequest"], opts?: ReadConsistencyOptions): Promise<Schemas["EmbeddingSearchResponse"]>;
377
430
  /** Bounded k-hop graph traversal. */
378
431
  traverse(body: Schemas["TraverseRequest"]): Promise<Schemas["TraverseResponse"]>;
379
432
  /** Resolve a query to seed entities, then return bounded paths. */
@@ -385,7 +438,14 @@ export declare class LbbClient {
385
438
  name?: string;
386
439
  relations?: string[];
387
440
  asOf?: string;
441
+ /** Require the bounded ranged-adjacency path; returns `index_busy` while unavailable. */
442
+ indexed?: boolean;
388
443
  }): Promise<Schemas["EntityNeighborhoodResponse"]>;
444
+ /** Exact type cardinality plus a bounded deterministic sample from ranged adjacency. */
445
+ entityTypeSample(opts: {
446
+ type: string;
447
+ limit?: number;
448
+ } & CallOptions): Promise<Schemas["EntityTypeSampleResponse"]>;
389
449
  /** Stored entity object-ref status and index-coverage metadata (no
390
450
  * attributes — read those from `entityDetail`'s top-level `attributes`). */
391
451
  entityMetadata(opts: {
@@ -459,9 +519,9 @@ export declare class LbbClient {
459
519
  * keys come back per group in `groups[].value_keys[<as>]`, entity keys in
460
520
  * `groups[].keys`.
461
521
  */
462
- sparql(body: Schemas["SparqlSelectRequest"]): Promise<Schemas["SparqlSelectResponse"]>;
463
- /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. */
464
- 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"]>;
465
525
  /**
466
526
  * Run a SPARQL 1.1 text query and return parsed results — the ergonomic
467
527
  * complement to {@link sparqlText} (which hands back the raw results string).
@@ -469,7 +529,7 @@ export declare class LbbClient {
469
529
  * `rows` is the bindings flattened to `{ variable: lexicalValue }`, `boolean`
470
530
  * is the ASK answer (or `null` for a SELECT).
471
531
  */
472
- sparqlRows(body: Schemas["SparqlTextRequest"]): Promise<SparqlResults>;
532
+ sparqlRows(body: Schemas["SparqlTextRequest"], opts?: ReadConsistencyOptions): Promise<SparqlResults>;
473
533
  /**
474
534
  * Basic-graph-pattern query with group-graph-pattern combinators
475
535
  * (UNION / OPTIONAL / MINUS / EXISTS / NOT EXISTS) folded over the base
@@ -581,16 +641,18 @@ export declare class LbbClient {
581
641
  }): Promise<Schemas["WalCompactResponse"]>;
582
642
  /** Server, graph, and persisted-index status. */
583
643
  status(): Promise<unknown>;
584
- /** Graph footprint, WAL tail, and index coverage. */
585
- metadata(): Promise<Schemas["GraphMetadataResponse"]>;
644
+ /** Graph footprint, WAL tail, and index coverage. Exact object inventory is opt-in. */
645
+ metadata(opts?: {
646
+ includeObjects?: boolean;
647
+ includeIndexes?: boolean;
648
+ includeTemporalCoverage?: boolean;
649
+ }): Promise<Schemas["GraphMetadataResponse"]>;
586
650
  waitForIndexLineage(targetSeq: number, opts?: {
587
651
  timeoutMs?: number;
588
652
  pollIntervalMs?: number;
589
653
  }): Promise<IndexLineageObservation>;
590
- /** Graph counts and type/relation buckets. */
591
- 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"]>;
592
656
  /** List the graphs (and branches) under the scoped tenant. */
593
657
  listGraphs(): Promise<Schemas["GraphListResponse"]>;
594
- /** Activity for the stack selected by the bearer stack key or session. */
595
- stackActivity(window?: LbbStackActivityWindow): Promise<LbbStackActivityResponse>;
596
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
@@ -400,6 +439,53 @@ export class LbbClient {
400
439
  createGraph() {
401
440
  return this.request("POST", "/v1/graph/create");
402
441
  }
442
+ /**
443
+ * Fork a whole graph into a brand-new destination graph in the same tenant.
444
+ * The copy runs as a durable background job (`confirm` is fixed to `dst`, which
445
+ * the route requires to authorize the fork); the destination must not already
446
+ * exist, so the create-only CAS on the server side makes the call safe to
447
+ * retry. The response only acknowledges the enqueue — poll the destination
448
+ * graph's metadata (see `response.poll`) to observe the fork completing: the
449
+ * destination becomes readable once its head is published.
450
+ */
451
+ forkGraph(opts) {
452
+ return this.request("POST", "/v1/graph/fork", {
453
+ query: { src: opts.src, dst: opts.dst, confirm: opts.dst },
454
+ retry: true,
455
+ });
456
+ }
457
+ /**
458
+ * Declarative full-state replace: reconcile the scoped graph so its current
459
+ * state matches exactly the NDJSON payload (same line grammar as
460
+ * {@link import} — triplets or `{type,name,properties}` entity records; pass an
461
+ * array, serialized here, or a pre-built NDJSON string). The whole
462
+ * reconciliation lands as one atomic cutover: payload records are upserted, and
463
+ * entities present at the pre-reload head but absent from the payload leave
464
+ * current state (retraction semantics — history is preserved, so an `as_of`
465
+ * read pinned before the cutover still sees the old state). `confirm` must
466
+ * equal the target graph id (reload is semi-destructive). `dryRun` previews the
467
+ * full delta with zero durable changes. The response carries
468
+ * `prior_commit_seq` / `prior_snapshot_token` as the rollback anchor — read
469
+ * them back with `?as_of_commit_seq=<prior_commit_seq>` to see the pre-reload
470
+ * state. An Idempotency-Key scopes the single cutover commit, so a retry
471
+ * replays rather than re-applying.
472
+ */
473
+ reload(lines, opts) {
474
+ const ndjson = typeof lines === "string"
475
+ ? lines
476
+ : lines.map((line) => JSON.stringify(line)).join("\n");
477
+ return this.request("POST", "/v1/graph/reload", {
478
+ rawBody: ndjson,
479
+ contentType: "application/x-ndjson",
480
+ query: {
481
+ confirm: opts.confirm,
482
+ dry_run: opts.dryRun,
483
+ strict: opts.strict,
484
+ observed_at: opts.observedAt,
485
+ },
486
+ idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("reload"),
487
+ });
488
+ }
403
489
  /** Fork the scoped branch from an existing branch in the same graph. */
404
490
  createBranch(body) {
405
491
  return this.request("POST", "/v1/graph/branch", { body });
@@ -692,8 +778,15 @@ export class LbbClient {
692
778
  }
693
779
  // --- search ---
694
780
  /** Full semantic hybrid search from a request body (`POST /v1/graph/search`). */
695
- graphSearch(body) {
696
- 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
+ });
697
790
  }
698
791
  /** Reciprocal-rank-fusion across sub-queries. */
699
792
  multiSearch(body) {
@@ -758,12 +851,16 @@ export class LbbClient {
758
851
  return this.request("GET", "/v1/search/feedback/export");
759
852
  }
760
853
  /** BM25 search. */
761
- fullTextSearch(body) {
762
- 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
+ });
763
858
  }
764
859
  /** ANN/vector search. */
765
- embeddingSearch(body) {
766
- 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
+ });
767
864
  }
768
865
  // --- traversal ---
769
866
  /** Bounded k-hop graph traversal. */
@@ -783,9 +880,18 @@ export class LbbClient {
783
880
  name: opts.name,
784
881
  relations: opts.relations?.join(","),
785
882
  as_of: opts.asOf,
883
+ indexed: opts.indexed,
786
884
  },
787
885
  });
788
886
  }
887
+ /** Exact type cardinality plus a bounded deterministic sample from ranged adjacency. */
888
+ entityTypeSample(opts) {
889
+ const { type, limit, ...request } = opts;
890
+ return this.request("GET", "/v1/graph/entities/sample", {
891
+ ...request,
892
+ query: { type, limit },
893
+ });
894
+ }
789
895
  /** Stored entity object-ref status and index-coverage metadata (no
790
896
  * attributes — read those from `entityDetail`'s top-level `attributes`). */
791
897
  entityMetadata(opts) {
@@ -890,12 +996,17 @@ export class LbbClient {
890
996
  * keys come back per group in `groups[].value_keys[<as>]`, entity keys in
891
997
  * `groups[].keys`.
892
998
  */
893
- sparql(body) {
894
- 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
+ });
895
1003
  }
896
- /** SPARQL 1.1 query from text (SELECT/ASK) over the live graph; `results` is SPARQL 1.1 Query Results JSON. */
897
- sparqlText(body) {
898
- 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
+ });
899
1010
  }
900
1011
  /**
901
1012
  * Run a SPARQL 1.1 text query and return parsed results — the ergonomic
@@ -904,8 +1015,8 @@ export class LbbClient {
904
1015
  * `rows` is the bindings flattened to `{ variable: lexicalValue }`, `boolean`
905
1016
  * is the ASK answer (or `null` for a SELECT).
906
1017
  */
907
- async sparqlRows(body) {
908
- return parseSparqlResults(await this.sparqlText(body));
1018
+ async sparqlRows(body, opts) {
1019
+ return parseSparqlResults(await this.sparqlText(body, opts));
909
1020
  }
910
1021
  /**
911
1022
  * Basic-graph-pattern query with group-graph-pattern combinators
@@ -1078,9 +1189,15 @@ export class LbbClient {
1078
1189
  status() {
1079
1190
  return this.request("GET", "/v1/status");
1080
1191
  }
1081
- /** Graph footprint, WAL tail, and index coverage. */
1082
- metadata() {
1083
- return this.request("GET", "/v1/graph/metadata");
1192
+ /** Graph footprint, WAL tail, and index coverage. Exact object inventory is opt-in. */
1193
+ metadata(opts = {}) {
1194
+ return this.request("GET", "/v1/graph/metadata", {
1195
+ query: {
1196
+ include_objects: opts.includeObjects,
1197
+ include_indexes: opts.includeIndexes,
1198
+ include_temporal_coverage: opts.includeTemporalCoverage,
1199
+ },
1200
+ });
1084
1201
  }
1085
1202
  async waitForIndexLineage(targetSeq, opts = {}) {
1086
1203
  const deadline = Date.now() + (opts.timeoutMs ?? 30_000);
@@ -1113,17 +1230,14 @@ export class LbbClient {
1113
1230
  await sleep(opts.pollIntervalMs ?? 250);
1114
1231
  }
1115
1232
  }
1116
- /** Graph counts and type/relation buckets. */
1117
- summary() {
1118
- 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
+ });
1119
1238
  }
1120
1239
  /** List the graphs (and branches) under the scoped tenant. */
1121
1240
  listGraphs() {
1122
1241
  return this.request("GET", "/v1/graphs");
1123
1242
  }
1124
- // --- stack activity ---
1125
- /** Activity for the stack selected by the bearer stack key or session. */
1126
- stackActivity(window = "24h") {
1127
- return this.request("GET", "/v1/stack/activity", { query: { window } });
1128
- }
1129
1243
  }
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, LbbStackActivityResponse, LbbStackActivityWindow, 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;
@@ -143,6 +147,15 @@ export declare class IndexNamespace {
143
147
  export declare class EntityNamespace {
144
148
  private readonly client;
145
149
  constructor(client: LbbClient);
150
+ /**
151
+ * Return the exact type cardinality and a bounded deterministic sample from
152
+ * the ranged adjacency index. The server returns `index_busy` rather than
153
+ * falling back to an exhaustive snapshot scan when the index is unavailable.
154
+ */
155
+ sample(opts: {
156
+ type: string;
157
+ limit?: number;
158
+ } & CallOptions): Promise<Schemas["EntityTypeSampleResponse"]>;
146
159
  /**
147
160
  * Browse entities as the unified list envelope. Pass `fields` (names or `*`)
148
161
  * to inline each row's typed attributes as native JSON (under `attributes`) —
@@ -216,9 +229,9 @@ export declare class OntologyNamespace {
216
229
  export declare class QueryNamespace {
217
230
  private readonly client;
218
231
  constructor(client: LbbClient);
219
- structured(body: Schemas["SparqlSelectRequest"], opts?: CallOptions): Promise<Schemas["SparqlSelectResponse"]>;
220
- sparql(body: Schemas["SparqlTextRequest"], opts?: CallOptions): Promise<import("./types.js").SparqlResults>;
221
- 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"]>;
222
235
  analytics(body: Schemas["AnalyticQueryRequest"], opts?: CallOptions): Promise<Schemas["AnalyticQueryResponse"]>;
223
236
  shacl(body: Schemas["ShaclQueryRequest"], opts?: CallOptions): Promise<Schemas["ShaclQueryResponse"]>;
224
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
  }
@@ -344,6 +363,14 @@ export class EntityNamespace {
344
363
  constructor(client) {
345
364
  this.client = client;
346
365
  }
366
+ /**
367
+ * Return the exact type cardinality and a bounded deterministic sample from
368
+ * the ranged adjacency index. The server returns `index_busy` rather than
369
+ * falling back to an exhaustive snapshot scan when the index is unavailable.
370
+ */
371
+ sample(opts) {
372
+ return this.client.entityTypeSample(opts);
373
+ }
347
374
  /**
348
375
  * Browse entities as the unified list envelope. Pass `fields` (names or `*`)
349
376
  * to inline each row's typed attributes as native JSON (under `attributes`) —
@@ -529,11 +556,20 @@ export class QueryNamespace {
529
556
  return this.client.request("POST", "/v1/query/sparql", {
530
557
  ...opts,
531
558
  retry: opts.retry ?? true,
532
- body,
559
+ body: withReadConsistency(this.client, body, opts),
533
560
  });
534
561
  }
535
562
  async sparql(body, opts = {}) {
536
- 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
+ });
537
573
  return parseSparqlResults(response);
538
574
  }
539
575
  sparqlRaw(body, opts = {}) {
@@ -541,6 +577,10 @@ export class QueryNamespace {
541
577
  ...opts,
542
578
  retry: opts.retry ?? true,
543
579
  body,
580
+ query: {
581
+ consistency: opts.consistency ?? this.client.defaultConsistency,
582
+ min_indexed_seq: opts.minIndexedSeq,
583
+ },
544
584
  });
545
585
  }
546
586
  analytics(body, opts = {}) {