@littlebigbrain/client 0.5.2 → 0.6.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/README.md CHANGED
@@ -79,6 +79,16 @@ graph.context.ask(...)
79
79
  graph.ontology.view(...)
80
80
  graph.query.sparql(...)
81
81
  graph.schema.audit(...)
82
+
83
+ const build = await graph.indexes.submit({}, { idempotencyKey: "index:head:147" })
84
+ await graph.indexes.job(build.job_id)
85
+ await graph.indexes.cancel(build.job_id)
86
+
87
+ const gc = await graph.indexes.submitGc({ dry_run: false }, { idempotencyKey: "gc:2026-07-15" })
88
+ await graph.indexes.gcJob(gc.job_id)
89
+
90
+ await graph.branch("review").deleteBranch({ confirm: "review" })
91
+ await graph.delete({ confirm: "main" }) // whole graph, every branch
82
92
  ```
83
93
 
84
94
  Other focused methods cover managed embeddings, full-text/vector search,
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { ImportLine, LbbClientOptions, LbbStackActivityResponse, LbbStackAc
2
2
  import { 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, 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, 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";
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";
@@ -31,9 +31,11 @@ export declare class LbbClient {
31
31
  private readonly apiVersion;
32
32
  private readonly maxRetries;
33
33
  private readonly retryDelayMs;
34
+ private readonly retryBudgetMs;
34
35
  private readonly timeoutMs;
35
36
  private readonly onRequest?;
36
37
  private readonly onResponse?;
38
+ private readonly onRetry?;
37
39
  readonly context: ContextNamespace;
38
40
  readonly search: SearchNamespace;
39
41
  readonly indexes: IndexNamespace;
@@ -132,15 +134,25 @@ export declare class LbbClient {
132
134
  observe(body: Schemas["ObserveRequest"], opts?: {
133
135
  idempotencyKey?: string;
134
136
  }): Promise<Schemas["ObserveResponse"]>;
135
- /**
136
- * Delete every object under the scoped graph/branch — a destructive reset.
137
- * `confirm` must equal the scoped graph id; the next commit re-initializes the
138
- * graph. Branch-scoped: sibling branches are untouched.
139
- */
137
+ /** Delete the scoped graph, including every branch, feedback, and active graph-scoped job. */
140
138
  deleteGraph(opts: {
141
139
  confirm: string;
142
- }): Promise<unknown>;
140
+ }): Promise<Schemas["GraphDeleteResponse"]>;
141
+ /** Delete only the scoped branch. The server refuses to delete a graph's final live branch. */
142
+ deleteBranch(opts: {
143
+ confirm: string;
144
+ }): Promise<Schemas["GraphBranchDeleteResponse"]>;
143
145
  embeddingConfig(): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
146
+ /** List the embedding models available on this deployment. */
147
+ embeddingModels(): Promise<Schemas["ManagedEmbeddingModelsResponse"]>;
148
+ /**
149
+ * Choose the model used automatically for writes and vector queries.
150
+ * Provider credentials and native dimension discovery stay server-side.
151
+ */
152
+ setEmbeddingModel(modelId: string, opts?: {
153
+ autoEmbedQuery?: boolean;
154
+ }): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
155
+ /** Advanced configuration escape hatch. Prefer `setEmbeddingModel`. */
144
156
  setEmbeddingConfig(body: Schemas["ManagedEmbeddingConfigRequest"]): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
145
157
  submitEmbeddingBackfill(opts?: {
146
158
  batchSize?: number;
@@ -539,6 +551,14 @@ export declare class LbbClient {
539
551
  indexRun(opts?: {
540
552
  background?: boolean;
541
553
  }): Promise<unknown>;
554
+ /** Submit a durable full-index build. Requires a reconnect-safe idempotency key. */
555
+ indexSubmit(body: Partial<Schemas["IndexBuildOptions"]> | undefined, opts: {
556
+ idempotencyKey: string;
557
+ }): Promise<Schemas["SearchIndexJobStatusResponse"]>;
558
+ /** Poll a durable full-index build. */
559
+ indexJob(jobId: string): Promise<Schemas["SearchIndexJobStatusResponse"]>;
560
+ /** Cancel a durable full-index build. Repeated cancellation returns its current terminal status. */
561
+ cancelIndexJob(jobId: string): Promise<Schemas["SearchIndexJobStatusResponse"]>;
542
562
  /** Append a BM25 delta segment for the unindexed WAL tail. */
543
563
  indexDelta(): Promise<Schemas["IndexDeltaResponse"]>;
544
564
  /** Preview or delete superseded persisted index runs. */
@@ -546,6 +566,14 @@ export declare class LbbClient {
546
566
  keepRuns?: number;
547
567
  dryRun?: boolean;
548
568
  }): Promise<Schemas["IndexGcResponse"]>;
569
+ /** Submit durable, cancellable index garbage collection. */
570
+ indexGcSubmit(body: Schemas["IndexGcRequest"] | undefined, opts: {
571
+ idempotencyKey: string;
572
+ }): Promise<Schemas["IndexGcJobStatusResponse"]>;
573
+ /** Poll exact planning/deletion progress for durable index garbage collection. */
574
+ indexGcJob(jobId: string): Promise<Schemas["IndexGcJobStatusResponse"]>;
575
+ /** Cancel durable index garbage collection. */
576
+ cancelIndexGcJob(jobId: string): Promise<Schemas["IndexGcJobStatusResponse"]>;
549
577
  /** Fold the WAL tail into snapshot segments. */
550
578
  compact(opts?: {
551
579
  minTailCommits?: number;
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parseSparqlResults } from "./types.js";
2
- import { parseLbbError, parseResponseJson, retryAllowed, retryDelayForAttempt, retryableStatus, sleep, } from "./transport.js";
2
+ import { bodyMarksTerminal, errorCodeFromBody, fullJitterBackoffMs, parseLbbError, parseResponseJson, retryAllowed, retryableStatus, retryDelayMs, sleep, } 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
5
  export { LbbError } from "./transport.js";
@@ -19,9 +19,11 @@ export class LbbClient {
19
19
  apiVersion;
20
20
  maxRetries;
21
21
  retryDelayMs;
22
+ retryBudgetMs;
22
23
  timeoutMs;
23
24
  onRequest;
24
25
  onResponse;
26
+ onRetry;
25
27
  context;
26
28
  search;
27
29
  indexes;
@@ -36,17 +38,22 @@ export class LbbClient {
36
38
  this.branchName = options.branch;
37
39
  this.stack = options.stack;
38
40
  this.apiVersion = options.apiVersion ?? "2026-06-22";
39
- this.maxRetries = options.maxRetries ?? 2;
41
+ this.maxRetries = options.maxRetries ?? 6;
40
42
  this.retryDelayMs = options.retryDelayMs ?? 100;
43
+ this.retryBudgetMs = options.retryBudgetMs ?? 60_000;
41
44
  this.timeoutMs = options.timeoutMs ?? 120_000;
42
45
  this.onRequest = options.onRequest;
43
46
  this.onResponse = options.onResponse;
47
+ this.onRetry = options.onRetry;
44
48
  if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) {
45
49
  throw new RangeError("maxRetries must be a non-negative integer");
46
50
  }
47
51
  if (!Number.isFinite(this.retryDelayMs) || this.retryDelayMs < 0) {
48
52
  throw new RangeError("retryDelayMs must be a non-negative number");
49
53
  }
54
+ if (!Number.isFinite(this.retryBudgetMs) || this.retryBudgetMs < 0) {
55
+ throw new RangeError("retryBudgetMs must be a non-negative number");
56
+ }
50
57
  if (!Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {
51
58
  throw new RangeError("timeoutMs must be a non-negative number");
52
59
  }
@@ -87,9 +94,11 @@ export class LbbClient {
87
94
  apiVersion: this.apiVersion,
88
95
  maxRetries: this.maxRetries,
89
96
  retryDelayMs: this.retryDelayMs,
97
+ retryBudgetMs: this.retryBudgetMs,
90
98
  timeoutMs: this.timeoutMs,
91
99
  onRequest: this.onRequest,
92
100
  onResponse: this.onResponse,
101
+ onRetry: this.onRetry,
93
102
  });
94
103
  }
95
104
  buildUrl(path, query) {
@@ -135,6 +144,8 @@ export class LbbClient {
135
144
  throw new RangeError("maxRetries must be a non-negative integer");
136
145
  }
137
146
  const startedAt = Date.now();
147
+ // Deadline is the binding limit; `maxRetries` is a secondary safety cap.
148
+ const deadline = startedAt + Math.max(0, opts.retryBudgetMs ?? this.retryBudgetMs);
138
149
  const url = this.buildUrl(path, opts.query);
139
150
  let attempts = 0;
140
151
  let response;
@@ -175,8 +186,19 @@ export class LbbClient {
175
186
  }), { name: "TimeoutError" })
176
187
  : error;
177
188
  if (!callerAborted && canRetry && attempt < maxRetries) {
178
- await sleep(this.retryDelayMs * (attempt + 1));
179
- continue;
189
+ const delayMs = fullJitterBackoffMs(this.retryDelayMs, attempt);
190
+ if (Date.now() + delayMs <= deadline) {
191
+ this.onRetry?.({
192
+ method: method.toUpperCase(),
193
+ url,
194
+ attempt: attempts,
195
+ status: undefined,
196
+ delayMs,
197
+ elapsedMs: Math.max(0, Date.now() - startedAt),
198
+ });
199
+ await sleep(delayMs);
200
+ continue;
201
+ }
180
202
  }
181
203
  throw requestError;
182
204
  }
@@ -193,7 +215,29 @@ export class LbbClient {
193
215
  if (!canRetry) {
194
216
  break;
195
217
  }
196
- await sleep(retryDelayForAttempt(this.retryDelayMs, attempt, response.headers?.get("retry-after")));
218
+ // Honor the server's typed body verdict: a terminal error
219
+ // (`retryable: false`, e.g. an exhausted quota) is surfaced at once
220
+ // rather than retried to the budget.
221
+ if (bodyMarksTerminal(text)) {
222
+ break;
223
+ }
224
+ const delayMs = retryDelayMs(this.retryDelayMs, attempt, {
225
+ retryAfterHeader: response.headers?.get("retry-after"),
226
+ body: text,
227
+ });
228
+ if (Date.now() + delayMs > deadline) {
229
+ break;
230
+ }
231
+ this.onRetry?.({
232
+ method: method.toUpperCase(),
233
+ url,
234
+ attempt: attempts,
235
+ status: response.status,
236
+ errorCode: errorCodeFromBody(text),
237
+ delayMs,
238
+ elapsedMs: Math.max(0, Date.now() - startedAt),
239
+ });
240
+ await sleep(delayMs);
197
241
  }
198
242
  if (response === undefined)
199
243
  throw new Error("request did not produce a response");
@@ -379,19 +423,38 @@ export class LbbClient {
379
423
  idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("observe"),
380
424
  });
381
425
  }
382
- /**
383
- * Delete every object under the scoped graph/branch — a destructive reset.
384
- * `confirm` must equal the scoped graph id; the next commit re-initializes the
385
- * graph. Branch-scoped: sibling branches are untouched.
386
- */
426
+ /** Delete the scoped graph, including every branch, feedback, and active graph-scoped job. */
387
427
  deleteGraph(opts) {
388
428
  return this.request("POST", "/v1/graph/delete", {
389
429
  query: { confirm: opts.confirm },
430
+ retry: true,
431
+ });
432
+ }
433
+ /** Delete only the scoped branch. The server refuses to delete a graph's final live branch. */
434
+ deleteBranch(opts) {
435
+ return this.request("DELETE", "/v1/graph/branch", {
436
+ query: { confirm: opts.confirm },
390
437
  });
391
438
  }
392
439
  embeddingConfig() {
393
440
  return this.request("GET", "/v1/graph/embedding");
394
441
  }
442
+ /** List the embedding models available on this deployment. */
443
+ embeddingModels() {
444
+ return this.request("GET", "/v1/graph/embedding/models");
445
+ }
446
+ /**
447
+ * Choose the model used automatically for writes and vector queries.
448
+ * Provider credentials and native dimension discovery stay server-side.
449
+ */
450
+ setEmbeddingModel(modelId, opts = {}) {
451
+ return this.setEmbeddingConfig({
452
+ model_id: modelId,
453
+ service: "open_router",
454
+ auto_embed_query: opts.autoEmbedQuery ?? true,
455
+ });
456
+ }
457
+ /** Advanced configuration escape hatch. Prefer `setEmbeddingModel`. */
395
458
  setEmbeddingConfig(body) {
396
459
  return this.request("POST", "/v1/graph/embedding", { body });
397
460
  }
@@ -951,6 +1014,23 @@ export class LbbClient {
951
1014
  query: { background: opts.background || undefined },
952
1015
  });
953
1016
  }
1017
+ /** Submit a durable full-index build. Requires a reconnect-safe idempotency key. */
1018
+ indexSubmit(body = {}, opts) {
1019
+ return this.request("POST", "/v1/index/jobs", {
1020
+ body,
1021
+ idempotencyKey: opts.idempotencyKey,
1022
+ });
1023
+ }
1024
+ /** Poll a durable full-index build. */
1025
+ indexJob(jobId) {
1026
+ return this.request("GET", "/v1/index/jobs", { query: { job_id: jobId } });
1027
+ }
1028
+ /** Cancel a durable full-index build. Repeated cancellation returns its current terminal status. */
1029
+ cancelIndexJob(jobId) {
1030
+ return this.request("DELETE", "/v1/index/jobs", {
1031
+ query: { job_id: jobId },
1032
+ });
1033
+ }
954
1034
  /** Append a BM25 delta segment for the unindexed WAL tail. */
955
1035
  indexDelta() {
956
1036
  return this.request("POST", "/v1/index/delta");
@@ -961,6 +1041,25 @@ export class LbbClient {
961
1041
  query: { keep_runs: opts.keepRuns, dry_run: opts.dryRun },
962
1042
  });
963
1043
  }
1044
+ /** Submit durable, cancellable index garbage collection. */
1045
+ indexGcSubmit(body = {}, opts) {
1046
+ return this.request("POST", "/v1/index/gc-jobs", {
1047
+ body,
1048
+ idempotencyKey: opts.idempotencyKey,
1049
+ });
1050
+ }
1051
+ /** Poll exact planning/deletion progress for durable index garbage collection. */
1052
+ indexGcJob(jobId) {
1053
+ return this.request("GET", "/v1/index/gc-jobs", {
1054
+ query: { job_id: jobId },
1055
+ });
1056
+ }
1057
+ /** Cancel durable index garbage collection. */
1058
+ cancelIndexGcJob(jobId) {
1059
+ return this.request("DELETE", "/v1/index/gc-jobs", {
1060
+ query: { job_id: jobId },
1061
+ });
1062
+ }
964
1063
  /** Fold the WAL tail into snapshot segments. */
965
1064
  compact(opts = {}) {
966
1065
  return this.request("POST", "/v1/graph/compact", {
@@ -32,8 +32,18 @@ export declare class GraphNamespace {
32
32
  create(opts?: CallOptions): Promise<Schemas["CreateGraphResponse"]>;
33
33
  delete(opts: {
34
34
  confirm: string;
35
- } & CallOptions): Promise<unknown>;
35
+ } & CallOptions): Promise<Schemas["GraphDeleteResponse"]>;
36
+ deleteBranch(opts: {
37
+ confirm: string;
38
+ } & CallOptions): Promise<Schemas["GraphBranchDeleteResponse"]>;
36
39
  embeddingConfig(opts?: CallOptions): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
40
+ /** List the embedding models available on this deployment. */
41
+ embeddingModels(opts?: CallOptions): Promise<Schemas["ManagedEmbeddingModelsResponse"]>;
42
+ /** Choose the model used automatically for writes and vector queries. */
43
+ setEmbeddingModel(modelId: string, options?: CallOptions & {
44
+ autoEmbedQuery?: boolean;
45
+ }): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
46
+ /** Advanced configuration escape hatch. Prefer `setEmbeddingModel`. */
37
47
  setEmbeddingConfig(body: Schemas["ManagedEmbeddingConfigRequest"], opts?: CallOptions): Promise<Schemas["ManagedEmbeddingConfigResponse"]>;
38
48
  backfillEmbeddings(options?: CallOptions & {
39
49
  batchSize?: number;
@@ -115,10 +125,20 @@ export declare class IndexNamespace {
115
125
  background?: boolean;
116
126
  } & CallOptions): Promise<unknown>;
117
127
  delta(opts?: CallOptions): Promise<Schemas["IndexDeltaResponse"]>;
128
+ submit(body: Partial<Schemas["IndexBuildOptions"]> | undefined, opts: CallOptions & {
129
+ idempotencyKey: string;
130
+ }): Promise<Schemas["SearchIndexJobStatusResponse"]>;
131
+ job(jobId: string, opts?: CallOptions): Promise<Schemas["SearchIndexJobStatusResponse"]>;
132
+ cancel(jobId: string, opts?: CallOptions): Promise<Schemas["SearchIndexJobStatusResponse"]>;
118
133
  gc(opts?: {
119
134
  keepRuns?: number;
120
135
  dryRun?: boolean;
121
136
  } & CallOptions): Promise<Schemas["IndexGcResponse"]>;
137
+ submitGc(body: Schemas["IndexGcRequest"] | undefined, opts: CallOptions & {
138
+ idempotencyKey: string;
139
+ }): Promise<Schemas["IndexGcJobStatusResponse"]>;
140
+ gcJob(jobId: string, opts?: CallOptions): Promise<Schemas["IndexGcJobStatusResponse"]>;
141
+ cancelGc(jobId: string, opts?: CallOptions): Promise<Schemas["IndexGcJobStatusResponse"]>;
122
142
  }
123
143
  export declare class EntityNamespace {
124
144
  private readonly client;
@@ -41,11 +41,36 @@ export class GraphNamespace {
41
41
  return this.client.request("POST", "/v1/graph/delete", {
42
42
  ...request,
43
43
  query: { confirm },
44
+ retry: request.retry ?? true,
45
+ });
46
+ }
47
+ deleteBranch(opts) {
48
+ const { confirm, ...request } = opts;
49
+ return this.client.request("DELETE", "/v1/graph/branch", {
50
+ ...request,
51
+ query: { confirm },
44
52
  });
45
53
  }
46
54
  embeddingConfig(opts = {}) {
47
55
  return this.client.request("GET", "/v1/graph/embedding", opts);
48
56
  }
57
+ /** List the embedding models available on this deployment. */
58
+ embeddingModels(opts = {}) {
59
+ return this.client.request("GET", "/v1/graph/embedding/models", opts);
60
+ }
61
+ /** Choose the model used automatically for writes and vector queries. */
62
+ setEmbeddingModel(modelId, options = {}) {
63
+ const { autoEmbedQuery, ...request } = options;
64
+ return this.client.request("POST", "/v1/graph/embedding", {
65
+ ...request,
66
+ body: {
67
+ model_id: modelId,
68
+ service: "open_router",
69
+ auto_embed_query: autoEmbedQuery ?? true,
70
+ },
71
+ });
72
+ }
73
+ /** Advanced configuration escape hatch. Prefer `setEmbeddingModel`. */
49
74
  setEmbeddingConfig(body, opts = {}) {
50
75
  return this.client.request("POST", "/v1/graph/embedding", {
51
76
  ...opts,
@@ -276,6 +301,21 @@ export class IndexNamespace {
276
301
  delta(opts = {}) {
277
302
  return this.client.request("POST", "/v1/index/delta", opts);
278
303
  }
304
+ submit(body = {}, opts) {
305
+ return this.client.request("POST", "/v1/index/jobs", { ...opts, body });
306
+ }
307
+ job(jobId, opts = {}) {
308
+ return this.client.request("GET", "/v1/index/jobs", {
309
+ ...opts,
310
+ query: { job_id: jobId },
311
+ });
312
+ }
313
+ cancel(jobId, opts = {}) {
314
+ return this.client.request("DELETE", "/v1/index/jobs", {
315
+ ...opts,
316
+ query: { job_id: jobId },
317
+ });
318
+ }
279
319
  gc(opts = {}) {
280
320
  const { keepRuns, dryRun, ...request } = opts;
281
321
  return this.client.request("POST", "/v1/index/gc", {
@@ -283,6 +323,21 @@ export class IndexNamespace {
283
323
  query: { keep_runs: keepRuns, dry_run: dryRun },
284
324
  });
285
325
  }
326
+ submitGc(body = {}, opts) {
327
+ return this.client.request("POST", "/v1/index/gc-jobs", { ...opts, body });
328
+ }
329
+ gcJob(jobId, opts = {}) {
330
+ return this.client.request("GET", "/v1/index/gc-jobs", {
331
+ ...opts,
332
+ query: { job_id: jobId },
333
+ });
334
+ }
335
+ cancelGc(jobId, opts = {}) {
336
+ return this.client.request("DELETE", "/v1/index/gc-jobs", {
337
+ ...opts,
338
+ query: { job_id: jobId },
339
+ });
340
+ }
286
341
  }
287
342
  export class EntityNamespace {
288
343
  client;