@littlebigbrain/client 0.11.0 → 0.11.1

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/README.md CHANGED
@@ -68,12 +68,18 @@ const accepted = await lbb.submitImport(records(), {
68
68
  });
69
69
  const completed = await lbb.waitForImportJob(accepted.job_id);
70
70
  console.log(completed.state, completed.committed_commit_seq);
71
+ if (completed.committed_commit_seq != null) {
72
+ await lbb.waitForIndexLineage(completed.committed_commit_seq);
73
+ }
71
74
  ```
72
75
 
73
76
  `records()` may be an iterable or async iterable. Success means every grouped
74
77
  commit is durable and final publication was enqueued; it does not mean indexes
75
- have already reached `committed_commit_seq`. An empty iterable is rejected
76
- locally before an import POST is sent.
78
+ have already reached `committed_commit_seq`. Wait once after the final commit,
79
+ not after each source row or chunk. The lineage waiter polls normal
80
+ `index_caught_up=false` metadata until its own deadline, including on an
81
+ RDF-only deployment. An empty iterable is rejected locally before an import
82
+ POST is sent.
77
83
 
78
84
  **Time-travel read.** Pin a SPARQL read to a past instant — results reflect the graph as it was then:
79
85
 
@@ -95,6 +101,8 @@ const { rows } = await lbb.sparqlRows({
95
101
  ## Errors & retries
96
102
 
97
103
  Methods return parsed JSON and throw `LbbError` (with `status`, `code`, `message`, `param`, `requestId`, `docUrl`) on any non-2xx response. Safe reads and idempotency-keyed writes retry `429`/`5xx` and network failures with full-jitter backoff, bounded by a retry budget (`retryBudgetMs`, default 60s) rather than a fixed count, and honor `Retry-After` — a terminal error the server marks non-retryable surfaces immediately. Use `rawRequest()` for response headers, request id, and retry/timing metadata.
104
+ `waitForIndexLineage(...)` is a separate deadline-bounded poller, so the generic
105
+ request retry-count cap cannot end publication waiting early.
98
106
 
99
107
  ## More
100
108
 
package/dist/client.d.ts CHANGED
@@ -146,7 +146,11 @@ export declare class LbbClient {
146
146
  retract(body: Schemas["GraphRetractRequest"], opts?: {
147
147
  idempotencyKey?: string;
148
148
  }): Promise<Schemas["GraphRetractResponse"]>;
149
- /** Create the scoped graph/branch. Construct the client with the desired graph/branch first. */
149
+ /**
150
+ * Create the scoped graph/branch with an empty ontology. Construct the client
151
+ * with the desired graph/branch first, then call `ontology.define` before
152
+ * writing typed data.
153
+ */
150
154
  createGraph(): Promise<Schemas["CreateGraphResponse"]>;
151
155
  /**
152
156
  * Fork a whole graph into a brand-new destination graph in the same tenant.
@@ -432,7 +436,17 @@ export declare class LbbClient {
432
436
  ontologySearch(body: Schemas["OntologySearchRequest"]): Promise<Schemas["OntologySearchResponse"]>;
433
437
  /** Resolve mentions to concepts/entities. */
434
438
  ontologyResolve(body: Schemas["OntologyResolveRequest"]): Promise<Schemas["OntologyResolveResponse"]>;
435
- /** Define the active ontology before the scoped graph's first commit. */
439
+ /**
440
+ * Put the scoped graph on an imported ontology, creating the graph when it
441
+ * does not exist yet. Safe to repeat: an unchanged ontology answers
442
+ * `changed: false` without writing. An additive difference is applied,
443
+ * including a wider relation domain or range and a new property field, and
444
+ * `changed` reports what was written. A document that narrows or drops what
445
+ * the graph already defines, or states a change no additive operation
446
+ * expresses, is refused with `ontology_restrictive_change`,
447
+ * `ontology_identity_breaking_change`, or `ontology_unsupported_change`, and
448
+ * writes nothing.
449
+ */
436
450
  ontologyDefine(body: Schemas["OntologyDefineRequest"]): Promise<Schemas["OntologyDefineResponse"]>;
437
451
  /**
438
452
  * Additively evolve the active ontology of an existing graph: widen relation
@@ -454,6 +468,13 @@ export declare class LbbClient {
454
468
  metadata(opts?: {
455
469
  includeIndexes?: boolean;
456
470
  }): Promise<Schemas["GraphMetadataResponse"]>;
471
+ /**
472
+ * Wait until one published generation covers `targetSeq`.
473
+ *
474
+ * The returned lineage may name absent BM25/ANN families on an RDF-only
475
+ * deployment; `metadata.index_caught_up` is the generation-level readiness
476
+ * signal used by bulk loaders.
477
+ */
457
478
  waitForIndexLineage(targetSeq: number, opts?: {
458
479
  timeoutMs?: number;
459
480
  pollIntervalMs?: number;
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { parseSparqlResults } from "./types.js";
2
2
  import { bodyMarksTerminal, errorCodeFromBody, fullJitterBackoffMs, parseLbbError, parseResponseJson, retryAllowed, retryableStatus, retryDelayMs, sleep, } from "./transport.js";
3
- import { LbbCapabilityError } from "./transport.js";
3
+ import { LbbCapabilityError, LbbError } from "./transport.js";
4
4
  import { EntityNamespace, GraphNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
5
5
  export { parseSparqlResults } from "./types.js";
6
6
  export { LbbCapabilityError, LbbError } from "./transport.js";
@@ -575,7 +575,11 @@ export class LbbClient {
575
575
  idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("retract"),
576
576
  });
577
577
  }
578
- /** Create the scoped graph/branch. Construct the client with the desired graph/branch first. */
578
+ /**
579
+ * Create the scoped graph/branch with an empty ontology. Construct the client
580
+ * with the desired graph/branch first, then call `ontology.define` before
581
+ * writing typed data.
582
+ */
579
583
  createGraph() {
580
584
  return this.request("POST", "/v1/graph/create");
581
585
  }
@@ -985,7 +989,17 @@ export class LbbClient {
985
989
  ontologyResolve(body) {
986
990
  return this.request("POST", "/v1/ontology/resolve", { body });
987
991
  }
988
- /** Define the active ontology before the scoped graph's first commit. */
992
+ /**
993
+ * Put the scoped graph on an imported ontology, creating the graph when it
994
+ * does not exist yet. Safe to repeat: an unchanged ontology answers
995
+ * `changed: false` without writing. An additive difference is applied,
996
+ * including a wider relation domain or range and a new property field, and
997
+ * `changed` reports what was written. A document that narrows or drops what
998
+ * the graph already defines, or states a change no additive operation
999
+ * expresses, is refused with `ontology_restrictive_change`,
1000
+ * `ontology_identity_breaking_change`, or `ontology_unsupported_change`, and
1001
+ * writes nothing.
1002
+ */
989
1003
  ontologyDefine(body) {
990
1004
  return this.request("POST", "/v1/ontology/define", { body });
991
1005
  }
@@ -1024,17 +1038,52 @@ export class LbbClient {
1024
1038
  },
1025
1039
  });
1026
1040
  }
1041
+ /**
1042
+ * Wait until one published generation covers `targetSeq`.
1043
+ *
1044
+ * The returned lineage may name absent BM25/ANN families on an RDF-only
1045
+ * deployment; `metadata.index_caught_up` is the generation-level readiness
1046
+ * signal used by bulk loaders.
1047
+ */
1027
1048
  async waitForIndexLineage(targetSeq, opts = {}) {
1028
1049
  const deadline = Date.now() + (opts.timeoutMs ?? 30_000);
1029
1050
  let last;
1030
1051
  while (true) {
1031
- last = await this.rawRequest("GET", "/v1/graph/metadata");
1052
+ try {
1053
+ // This method is already an explicit, deadline-bounded poller. Avoid
1054
+ // nesting the generic request retry count inside it: publication may
1055
+ // legitimately take longer than that secondary cap.
1056
+ last = await this.rawRequest("GET", "/v1/graph/metadata", {
1057
+ maxRetries: 0,
1058
+ });
1059
+ }
1060
+ catch (error) {
1061
+ const retryableHttpError = error instanceof LbbError &&
1062
+ retryableStatus(error.status) &&
1063
+ error.retryable !== false;
1064
+ const retryableTransportError = error instanceof Error && !(error instanceof LbbError);
1065
+ if (!retryableHttpError && !retryableTransportError)
1066
+ throw error;
1067
+ const now = Date.now();
1068
+ if (now >= deadline) {
1069
+ throw new Error(`index lineage did not reach ${targetSeq} before timeout (last_error=${error.message})`, { cause: error });
1070
+ }
1071
+ const retryAfterMs = error instanceof LbbError
1072
+ ? (error.retryAfterSeconds ?? 0) * 1_000
1073
+ : 0;
1074
+ await sleep(Math.min(Math.max(opts.pollIntervalMs ?? 250, retryAfterMs), deadline - now));
1075
+ continue;
1076
+ }
1032
1077
  const lineage = last.data.index_lineage;
1078
+ const servedAt = last.data.snapshot.served_at_seq;
1033
1079
  if (lineage != null &&
1034
- lineage.bm25_indexed_commit_seq != null &&
1035
- lineage.bm25_indexed_commit_seq >= targetSeq &&
1036
- lineage.ann_indexed_commit_seq != null &&
1037
- lineage.ann_indexed_commit_seq >= targetSeq) {
1080
+ servedAt != null &&
1081
+ servedAt >= targetSeq &&
1082
+ (last.data.index_caught_up === true ||
1083
+ (lineage.bm25_indexed_commit_seq != null &&
1084
+ lineage.bm25_indexed_commit_seq >= targetSeq &&
1085
+ lineage.ann_indexed_commit_seq != null &&
1086
+ lineage.ann_indexed_commit_seq >= targetSeq))) {
1038
1087
  return {
1039
1088
  metadata: last.data,
1040
1089
  lineage,
@@ -100,6 +100,10 @@ export declare class OntologyNamespace {
100
100
  conformance(opts?: CallOptions & Pick<ReadConsistencyOptions, "consistency">): Promise<Schemas["SchemaAuditReport"]>;
101
101
  search(body: Schemas["OntologySearchRequest"], opts?: CallOptions): Promise<Schemas["OntologySearchResponse"]>;
102
102
  resolve(body: Schemas["OntologyResolveRequest"], opts?: CallOptions): Promise<Schemas["OntologyResolveResponse"]>;
103
+ /**
104
+ * Put the scoped graph on an imported ontology, creating the graph when it
105
+ * does not exist yet. Safe to repeat. See {@link LbbClient.ontologyDefine}.
106
+ */
103
107
  define(body: Schemas["OntologyDefineRequest"], opts?: CallOptions): Promise<Schemas["OntologyDefineResponse"]>;
104
108
  evolve(body: Schemas["OntologyEvolveRequest"], opts?: CallOptions): Promise<Schemas["OntologyEvolveResponse"]>;
105
109
  induce(body: Schemas["OntologyInduceRequest"], opts?: CallOptions): Promise<Schemas["OntologyInduceResponse"]>;
@@ -241,6 +241,10 @@ export class OntologyNamespace {
241
241
  body,
242
242
  });
243
243
  }
244
+ /**
245
+ * Put the scoped graph on an imported ontology, creating the graph when it
246
+ * does not exist yet. Safe to repeat. See {@link LbbClient.ontologyDefine}.
247
+ */
244
248
  define(body, opts = {}) {
245
249
  return this.client.request("POST", "/v1/ontology/define", {
246
250
  ...opts,
package/dist/schema.d.ts CHANGED
@@ -183,7 +183,7 @@ export interface paths {
183
183
  };
184
184
  get?: never;
185
185
  put?: never;
186
- /** Create the scoped graph and branch */
186
+ /** Create the scoped graph and branch with an empty ontology */
187
187
  post: operations["post_v1_graph_create"];
188
188
  delete?: never;
189
189
  options?: never;
@@ -3786,10 +3786,15 @@ export interface components {
3786
3786
  subject: string;
3787
3787
  };
3788
3788
  /**
3789
- * @description Define a custom ontology for the scoped graph before its first commit.
3790
- * Imports `source` (any [`crate`]-supported format), optionally merges the
3791
- * built-in default so the standard entity types/relations stay available, and
3792
- * creates the graph head with the result. Fails if the graph already exists.
3789
+ * @description Define the scoped graph's ontology. Imports `source` (any
3790
+ * [`crate`]-supported format), optionally merges the built-in default so the
3791
+ * standard entity types/relations stay available, and creates the graph head
3792
+ * with the result.
3793
+ *
3794
+ * The call is re-runnable on a graph that already exists. An unchanged
3795
+ * ontology is a no-op; an additive difference is applied, including a widened
3796
+ * relation domain or range and a new property field; a narrowing or removing
3797
+ * difference is refused with a typed error naming the route that applies it.
3793
3798
  */
3794
3799
  OntologyDefineRequest: {
3795
3800
  /** @description Import format hint. `auto` (the default) sniffs the content. */
@@ -3806,9 +3811,31 @@ export interface components {
3806
3811
  source: string;
3807
3812
  };
3808
3813
  OntologyDefineResponse: {
3814
+ /**
3815
+ * @description True when this call wrote a new ontology version, either by creating the
3816
+ * graph or by applying an additive difference. False when the proposed
3817
+ * ontology already matched the active one, which is what makes re-running
3818
+ * the same bootstrap request a no-op.
3819
+ *
3820
+ * This tracks what was written, so it never reports `false` for a call
3821
+ * that changed the ontology: a widened relation domain or range and a new
3822
+ * property field both set it, even though neither appears in the
3823
+ * class-and-relation schema diff.
3824
+ */
3825
+ changed?: boolean;
3826
+ /**
3827
+ * @description The additive changes this call applied to an existing graph's ontology.
3828
+ * Empty when the graph was created and when nothing changed.
3829
+ */
3830
+ changes?: components["schemas"]["SchemaDiffEntry"][];
3809
3831
  entity_types: components["schemas"]["OntologyTermView"][];
3810
3832
  format: string;
3811
3833
  graph: components["schemas"]["GraphKey"];
3834
+ /**
3835
+ * @description True when this call created the graph head. False when the graph already
3836
+ * existed, whether the ontology then changed or not. This is the `created`
3837
+ * signal for the define route.
3838
+ */
3812
3839
  graph_created: boolean;
3813
3840
  merged_default: boolean;
3814
3841
  /** Format: int64 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@littlebigbrain/client",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "TypeScript client for the little big brain graph + hybrid search HTTP API",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -53,5 +53,13 @@
53
53
  "openapi-typescript": "^7",
54
54
  "publint": "^0.3.21",
55
55
  "typescript": "^5.6"
56
+ },
57
+ "overrides": {
58
+ "@redocly/openapi-core": {
59
+ "js-yaml": "^4.3.1"
60
+ },
61
+ "minimatch": {
62
+ "brace-expansion": "^2.1.2"
63
+ }
56
64
  }
57
65
  }