@littlebigbrain/client 0.8.1 → 0.9.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 +14 -0
- package/dist/client.d.ts +27 -3
- package/dist/client.js +148 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/schema.d.ts +812 -117
- package/dist/transport.d.ts +9 -2
- package/dist/transport.js +9 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,6 +78,20 @@ await graph.facts.import(
|
|
|
78
78
|
);
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
For large or long-running loads, stream records to a durable job instead:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const accepted = await lbb.submitImport(records(), {
|
|
85
|
+
idempotencyKey: "hubspot:portal-42:run-2026-07-29",
|
|
86
|
+
});
|
|
87
|
+
const completed = await lbb.waitForImportJob(accepted.job_id);
|
|
88
|
+
console.log(completed.state, completed.committed_commit_seq);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`records()` may be an iterable or async iterable. Success means every grouped
|
|
92
|
+
commit is durable and final publication was enqueued; it does not mean indexes
|
|
93
|
+
have already reached `committed_commit_seq`.
|
|
94
|
+
|
|
81
95
|
**Time-travel read.** Pin any search to a past instant — results reflect the graph as it was then:
|
|
82
96
|
|
|
83
97
|
```ts
|
package/dist/client.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ImportLine, LbbClientOptions, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults } from "./types.js";
|
|
1
|
+
import type { DurableImportSource, ImportLine, LbbClientOptions, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults } from "./types.js";
|
|
2
2
|
import { type CallOptions, type RequestOptions } from "./transport.js";
|
|
3
3
|
import { ContextNamespace, EntityNamespace, GraphNamespace, 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, ReadConsistencyOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults, SparqlResultsJson, SparqlTerm, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./types.js";
|
|
6
|
-
export { LbbError } from "./transport.js";
|
|
5
|
+
export type { AttributeFilter, AttributeFilterOp, AttributeFilterValue, EntityAttributeFilterOptions, EntityPropertiesLine, DurableImportLine, DurableImportSource, FetchLike, FlatProperties, ImportLine, LbbClientOptions, LbbRequestEvent, LbbResponseEvent, LbbRetryEvent, LbbErrorPayload, ListResponse, RawLbbResponse, ReadConsistencyOptions, RdfImportOptions, Schemas, SearchConsistency, SparqlResults, SparqlResultsJson, SparqlTerm, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./types.js";
|
|
6
|
+
export { LbbCapabilityError, LbbError } from "./transport.js";
|
|
7
7
|
export type { CallOptions, Query, QueryValue, RequestOptions, } from "./transport.js";
|
|
8
8
|
export type { HybridSearchOptions } from "./namespaces.js";
|
|
9
9
|
export { ContextNamespace, EntityNamespace, FactsNamespace, GraphNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
|
|
@@ -36,6 +36,7 @@ export declare class LbbClient {
|
|
|
36
36
|
private readonly onRequest?;
|
|
37
37
|
private readonly onResponse?;
|
|
38
38
|
private readonly onRetry?;
|
|
39
|
+
private capabilities?;
|
|
39
40
|
/** A5 default read consistency applied when a read omits its own value. */
|
|
40
41
|
readonly defaultConsistency?: SearchConsistency;
|
|
41
42
|
readonly context: ContextNamespace;
|
|
@@ -75,6 +76,7 @@ export declare class LbbClient {
|
|
|
75
76
|
request<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
|
|
76
77
|
private mutationKey;
|
|
77
78
|
idempotencyKey(prefix?: string): string;
|
|
79
|
+
private requireCapability;
|
|
78
80
|
/** Commit triplets and optional entity embeddings. Prefer `client.graph("main").facts.create(...)`. */
|
|
79
81
|
commit(body: Schemas["TripletCommitFile"], opts?: {
|
|
80
82
|
idempotencyKey?: string;
|
|
@@ -109,6 +111,28 @@ export declare class LbbClient {
|
|
|
109
111
|
}): Promise<Schemas["GraphImportResponse"] & {
|
|
110
112
|
commitSeq: number | null;
|
|
111
113
|
}>;
|
|
114
|
+
/**
|
|
115
|
+
* Stream NDJSON into immutable storage and enqueue a durable import job.
|
|
116
|
+
*
|
|
117
|
+
* The idempotency key is mandatory and binds the key to the uploaded content.
|
|
118
|
+
* Streaming uploads are attempted once: a one-shot async iterator cannot be
|
|
119
|
+
* replayed safely by an automatic HTTP retry. Call this method again with a
|
|
120
|
+
* fresh iterable and the same key to perform an explicit idempotent replay.
|
|
121
|
+
*/
|
|
122
|
+
submitImport(lines: DurableImportSource, opts: {
|
|
123
|
+
idempotencyKey: string;
|
|
124
|
+
batch?: number;
|
|
125
|
+
strict?: boolean;
|
|
126
|
+
observedAt?: string;
|
|
127
|
+
signal?: AbortSignal;
|
|
128
|
+
}): Promise<Schemas["GraphImportJobAccepted"]>;
|
|
129
|
+
getImportJob(jobId: string): Promise<Schemas["GraphImportJobStatus"]>;
|
|
130
|
+
cancelImportJob(jobId: string): Promise<Schemas["GraphImportJobCancelResponse"]>;
|
|
131
|
+
waitForImportJob(jobId: string, opts?: {
|
|
132
|
+
pollIntervalMs?: number;
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
signal?: AbortSignal;
|
|
135
|
+
}): Promise<Schemas["GraphImportJobStatus"]>;
|
|
112
136
|
/**
|
|
113
137
|
* Bulk-ingest N-Triples, Turtle, N-Quads, or TriG without client-side conversion. Resource-object
|
|
114
138
|
* triples become keyed Resource edges; literal-object triples become text
|
package/dist/client.js
CHANGED
|
@@ -1,9 +1,74 @@
|
|
|
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
4
|
import { ContextNamespace, EntityNamespace, GraphNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
|
|
4
5
|
export { parseSparqlResults } from "./types.js";
|
|
5
|
-
export { LbbError } from "./transport.js";
|
|
6
|
+
export { LbbCapabilityError, LbbError } from "./transport.js";
|
|
6
7
|
export { ContextNamespace, EntityNamespace, FactsNamespace, GraphNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
|
|
8
|
+
function durableImportBytes(line) {
|
|
9
|
+
if (line instanceof Uint8Array)
|
|
10
|
+
return line;
|
|
11
|
+
const encoded = typeof line === "string" ? line : JSON.stringify(line);
|
|
12
|
+
return new TextEncoder().encode(encoded.endsWith("\n") ? encoded : `${encoded}\n`);
|
|
13
|
+
}
|
|
14
|
+
function durableImportIterator(source) {
|
|
15
|
+
if (typeof source === "string") {
|
|
16
|
+
return (async function* () {
|
|
17
|
+
yield source;
|
|
18
|
+
})();
|
|
19
|
+
}
|
|
20
|
+
if (Symbol.asyncIterator in Object(source)) {
|
|
21
|
+
return source[Symbol.asyncIterator]();
|
|
22
|
+
}
|
|
23
|
+
const iterator = source[Symbol.iterator]();
|
|
24
|
+
return {
|
|
25
|
+
next: async () => iterator.next(),
|
|
26
|
+
return: async () => {
|
|
27
|
+
iterator.return?.();
|
|
28
|
+
return { done: true, value: undefined };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function durableImportBody(source) {
|
|
33
|
+
const iterator = durableImportIterator(source);
|
|
34
|
+
const Stream = globalThis.ReadableStream;
|
|
35
|
+
if (Stream) {
|
|
36
|
+
return new Stream({
|
|
37
|
+
async pull(controller) {
|
|
38
|
+
try {
|
|
39
|
+
const item = await iterator.next();
|
|
40
|
+
if (item.done) {
|
|
41
|
+
controller.close();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
controller.enqueue(durableImportBytes(item.value));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
controller.error(error);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async cancel() {
|
|
52
|
+
await iterator.return?.();
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
async *[Symbol.asyncIterator]() {
|
|
58
|
+
try {
|
|
59
|
+
for (;;) {
|
|
60
|
+
const item = await iterator.next();
|
|
61
|
+
if (item.done)
|
|
62
|
+
return;
|
|
63
|
+
yield durableImportBytes(item.value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await iterator.return?.();
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
7
72
|
/**
|
|
8
73
|
* A typed HTTP client for a little big brain graph server. One instance is scoped to a
|
|
9
74
|
* single graph/branch; construct another for a different scope. All methods
|
|
@@ -24,6 +89,7 @@ export class LbbClient {
|
|
|
24
89
|
onRequest;
|
|
25
90
|
onResponse;
|
|
26
91
|
onRetry;
|
|
92
|
+
capabilities;
|
|
27
93
|
/** A5 default read consistency applied when a read omits its own value. */
|
|
28
94
|
defaultConsistency;
|
|
29
95
|
context;
|
|
@@ -172,6 +238,7 @@ export class LbbClient {
|
|
|
172
238
|
method,
|
|
173
239
|
headers,
|
|
174
240
|
body,
|
|
241
|
+
...(opts.duplex ? { duplex: opts.duplex } : {}),
|
|
175
242
|
};
|
|
176
243
|
const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
|
|
177
244
|
const maxRetries = opts.maxRetries ?? this.maxRetries;
|
|
@@ -207,10 +274,16 @@ export class LbbClient {
|
|
|
207
274
|
maxAttempts: maxRetries + 1,
|
|
208
275
|
idempotencyKey: opts.idempotencyKey,
|
|
209
276
|
});
|
|
210
|
-
|
|
277
|
+
const requestInit = {
|
|
211
278
|
...init,
|
|
212
279
|
signal: controller?.signal ?? opts.signal,
|
|
213
|
-
}
|
|
280
|
+
};
|
|
281
|
+
// Keep FetchLike's long-standing string-body test-double contract while
|
|
282
|
+
// allowing this one endpoint to pass a native streaming body to fetch.
|
|
283
|
+
// Native browser/Node fetch implementations accept the extended shape;
|
|
284
|
+
// custom transports that need durable imports can inspect it at runtime.
|
|
285
|
+
const streamingFetch = this.fetchImpl;
|
|
286
|
+
response = await streamingFetch(url, requestInit);
|
|
214
287
|
text = await response.text();
|
|
215
288
|
}
|
|
216
289
|
catch (error) {
|
|
@@ -321,6 +394,12 @@ export class LbbClient {
|
|
|
321
394
|
idempotencyKey(prefix = "request") {
|
|
322
395
|
return this.mutationKey(prefix);
|
|
323
396
|
}
|
|
397
|
+
async requireCapability(capability) {
|
|
398
|
+
this.capabilities ??= this.request("GET", "/version").then((version) => new Set(version.capabilities));
|
|
399
|
+
if (!(await this.capabilities).has(capability)) {
|
|
400
|
+
throw new LbbCapabilityError(capability);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
324
403
|
// --- writes ---
|
|
325
404
|
/** Commit triplets and optional entity embeddings. Prefer `client.graph("main").facts.create(...)`. */
|
|
326
405
|
async commit(body, opts = {}) {
|
|
@@ -375,6 +454,72 @@ export class LbbClient {
|
|
|
375
454
|
// write→floor→read loop reads naturally after a bulk load.
|
|
376
455
|
return { ...response, commitSeq: response.committed_commit_seq ?? null };
|
|
377
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Stream NDJSON into immutable storage and enqueue a durable import job.
|
|
459
|
+
*
|
|
460
|
+
* The idempotency key is mandatory and binds the key to the uploaded content.
|
|
461
|
+
* Streaming uploads are attempted once: a one-shot async iterator cannot be
|
|
462
|
+
* replayed safely by an automatic HTTP retry. Call this method again with a
|
|
463
|
+
* fresh iterable and the same key to perform an explicit idempotent replay.
|
|
464
|
+
*/
|
|
465
|
+
async submitImport(lines, opts) {
|
|
466
|
+
if (!opts?.idempotencyKey?.trim()) {
|
|
467
|
+
throw new TypeError("submitImport requires a non-empty idempotencyKey");
|
|
468
|
+
}
|
|
469
|
+
await this.requireCapability("durable_import_jobs_v1");
|
|
470
|
+
return this.request("POST", "/v1/graph/import-jobs", {
|
|
471
|
+
rawBody: durableImportBody(lines),
|
|
472
|
+
contentType: "application/x-ndjson",
|
|
473
|
+
duplex: "half",
|
|
474
|
+
query: {
|
|
475
|
+
batch: opts.batch,
|
|
476
|
+
strict: opts.strict,
|
|
477
|
+
observed_at: opts.observedAt,
|
|
478
|
+
},
|
|
479
|
+
idempotencyKey: opts.idempotencyKey,
|
|
480
|
+
maxRetries: 0,
|
|
481
|
+
retry: false,
|
|
482
|
+
signal: opts.signal,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
async getImportJob(jobId) {
|
|
486
|
+
await this.requireCapability("durable_import_jobs_v1");
|
|
487
|
+
return this.request("GET", "/v1/graph/import-jobs", {
|
|
488
|
+
query: { job_id: jobId },
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
async cancelImportJob(jobId) {
|
|
492
|
+
await this.requireCapability("durable_import_jobs_v1");
|
|
493
|
+
return this.request("DELETE", "/v1/graph/import-jobs", {
|
|
494
|
+
query: { job_id: jobId },
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
async waitForImportJob(jobId, opts = {}) {
|
|
498
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 1_000;
|
|
499
|
+
const timeoutMs = opts.timeoutMs ?? 0;
|
|
500
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs < 0) {
|
|
501
|
+
throw new RangeError("pollIntervalMs must be a non-negative number");
|
|
502
|
+
}
|
|
503
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
504
|
+
throw new RangeError("timeoutMs must be a non-negative number");
|
|
505
|
+
}
|
|
506
|
+
const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : undefined;
|
|
507
|
+
for (;;) {
|
|
508
|
+
if (opts.signal?.aborted) {
|
|
509
|
+
throw opts.signal.reason ?? new Error("request aborted");
|
|
510
|
+
}
|
|
511
|
+
const status = await this.getImportJob(jobId);
|
|
512
|
+
if (status.state === "succeeded" ||
|
|
513
|
+
status.state === "failed" ||
|
|
514
|
+
status.state === "cancelled") {
|
|
515
|
+
return status;
|
|
516
|
+
}
|
|
517
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
518
|
+
throw new Error(`timed out waiting for durable import job ${jobId}`);
|
|
519
|
+
}
|
|
520
|
+
await sleep(pollIntervalMs);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
378
523
|
/**
|
|
379
524
|
* Bulk-ingest N-Triples, Turtle, N-Quads, or TriG without client-side conversion. Resource-object
|
|
380
525
|
* triples become keyed Resource edges; literal-object triples become text
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { LbbClient, LbbError, parseSparqlResults } from "./client.js";
|
|
2
|
-
export type { LbbClientOptions, CallOptions, RequestOptions, HybridSearchOptions, LbbRequestEvent, LbbResponseEvent, FetchLike, ReadConsistencyOptions, SearchConsistency, Schemas, SparqlResults, SparqlResultsJson, SparqlTerm, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./client.js";
|
|
1
|
+
export { LbbCapabilityError, LbbClient, LbbError, parseSparqlResults, } from "./client.js";
|
|
2
|
+
export type { DurableImportLine, DurableImportSource, LbbClientOptions, CallOptions, RequestOptions, HybridSearchOptions, LbbRequestEvent, LbbResponseEvent, FetchLike, ReadConsistencyOptions, SearchConsistency, Schemas, SparqlResults, SparqlResultsJson, SparqlTerm, CommitRequest, CommitResponse, Entity, EntitySelector, GraphMetadata, GraphSummary, SchemaView, SearchRequest, SearchResponse, SearchResult, Snapshot, } from "./client.js";
|
|
3
3
|
export type { components, paths, operations } from "./schema.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LbbClient, LbbError, parseSparqlResults } from "./client.js";
|
|
1
|
+
export { LbbCapabilityError, LbbClient, LbbError, parseSparqlResults, } from "./client.js";
|