@littlebigbrain/client 0.1.0 → 0.3.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/LICENSE +56 -0
- package/README.md +56 -15
- package/dist/client.d.ts +213 -460
- package/dist/client.js +391 -350
- package/dist/index.d.ts +1 -1
- package/dist/namespaces.d.ts +185 -0
- package/dist/namespaces.js +469 -0
- package/dist/schema.d.ts +10857 -3600
- package/dist/transport.d.ts +44 -0
- package/dist/transport.js +93 -0
- package/dist/types.d.ts +299 -0
- package/dist/types.js +47 -0
- package/package.json +35 -6
- package/dist/client.test.d.ts +0 -1
- package/dist/client.test.js +0 -591
- package/dist/contract-routes.test.d.ts +0 -1
- package/dist/contract-routes.test.js +0 -74
- package/src/client.test.ts +0 -673
- package/src/client.ts +0 -1428
- package/src/contract-routes.test.ts +0 -91
- package/src/index.ts +0 -16
- package/src/node-test-shim.d.ts +0 -16
- package/src/schema.ts +0 -13865
package/dist/client.js
CHANGED
|
@@ -1,114 +1,11 @@
|
|
|
1
|
+
import { parseSparqlResults } from "./types.js";
|
|
2
|
+
import { parseLbbError, parseResponseJson, retryAllowed, retryDelayForAttempt, retryableStatus, sleep, } from "./transport.js";
|
|
3
|
+
import { ContextNamespace, EntityNamespace, GraphNamespace, IndexNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
|
|
4
|
+
export { parseSparqlResults } from "./types.js";
|
|
5
|
+
export { LbbError } from "./transport.js";
|
|
6
|
+
export { ContextNamespace, EntityNamespace, FactsNamespace, GraphNamespace, IndexNamespace, OntologyNamespace, QueryNamespace, SchemaNamespace, SearchNamespace, } from "./namespaces.js";
|
|
1
7
|
/**
|
|
2
|
-
*
|
|
3
|
-
* SPARQL Results document as a JSON *string*) into typed bindings plus flat
|
|
4
|
-
* `{ variable: lexicalValue }` rows — the form most callers want, so they never
|
|
5
|
-
* have to `JSON.parse` and zip `head.vars` with binding values by hand.
|
|
6
|
-
*/
|
|
7
|
-
export function parseSparqlResults(response) {
|
|
8
|
-
const doc = JSON.parse(response.results);
|
|
9
|
-
const vars = doc.head?.vars ?? [];
|
|
10
|
-
if (typeof doc.boolean === "boolean") {
|
|
11
|
-
return { vars, boolean: doc.boolean, bindings: [], rows: [] };
|
|
12
|
-
}
|
|
13
|
-
const bindings = doc.results?.bindings ?? [];
|
|
14
|
-
const rows = bindings.map((binding) => Object.fromEntries(Object.entries(binding).map(([name, term]) => [name, term.value])));
|
|
15
|
-
return { vars, boolean: null, bindings, rows };
|
|
16
|
-
}
|
|
17
|
-
function firstPatternVariable(patterns) {
|
|
18
|
-
for (const pattern of patterns) {
|
|
19
|
-
if ("var" in pattern.subject)
|
|
20
|
-
return pattern.subject.var;
|
|
21
|
-
if ("var" in pattern.object)
|
|
22
|
-
return pattern.object.var;
|
|
23
|
-
}
|
|
24
|
-
return "entity";
|
|
25
|
-
}
|
|
26
|
-
function attributeFilterValue(value) {
|
|
27
|
-
if (typeof value === "boolean")
|
|
28
|
-
return { bool: value };
|
|
29
|
-
if (typeof value === "number")
|
|
30
|
-
return Number.isInteger(value) ? { i64: value } : { f64: value };
|
|
31
|
-
if (typeof value === "string")
|
|
32
|
-
return { str: value };
|
|
33
|
-
if ("dateTime" in value)
|
|
34
|
-
return { date_time: value.dateTime };
|
|
35
|
-
return { entity: value.entity };
|
|
36
|
-
}
|
|
37
|
-
function attributeFilter(filter, defaultVar) {
|
|
38
|
-
return {
|
|
39
|
-
compare: {
|
|
40
|
-
op: filter.op ?? "eq",
|
|
41
|
-
left: { property: { var: filter.var ?? defaultVar, field: filter.field } },
|
|
42
|
-
right: { value: attributeFilterValue(filter.value) },
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
/** Thrown when the server responds with a non-2xx status. */
|
|
47
|
-
export class LbbError extends Error {
|
|
48
|
-
status;
|
|
49
|
-
body;
|
|
50
|
-
error;
|
|
51
|
-
type;
|
|
52
|
-
code;
|
|
53
|
-
param;
|
|
54
|
-
requestId;
|
|
55
|
-
docUrl;
|
|
56
|
-
constructor(status, body, error) {
|
|
57
|
-
super(error?.message ?? `Little Big Brain ${status}: ${body}`);
|
|
58
|
-
this.status = status;
|
|
59
|
-
this.body = body;
|
|
60
|
-
this.error = error;
|
|
61
|
-
this.name = "LbbError";
|
|
62
|
-
this.type = error?.type;
|
|
63
|
-
this.code = error?.code;
|
|
64
|
-
this.param = error?.param;
|
|
65
|
-
this.requestId = error?.request_id;
|
|
66
|
-
this.docUrl = error?.doc_url;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function sleep(ms) {
|
|
70
|
-
if (ms <= 0)
|
|
71
|
-
return Promise.resolve();
|
|
72
|
-
const timer = globalThis
|
|
73
|
-
.setTimeout;
|
|
74
|
-
return new Promise((resolve) => {
|
|
75
|
-
if (timer) {
|
|
76
|
-
timer(resolve, ms);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
resolve();
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
function retryableStatus(status) {
|
|
84
|
-
return status === 429 || status >= 500;
|
|
85
|
-
}
|
|
86
|
-
function retryAllowed(method, idempotencyKey) {
|
|
87
|
-
const upper = method.toUpperCase();
|
|
88
|
-
return upper === "GET" || upper === "HEAD" || upper === "OPTIONS" || idempotencyKey !== undefined;
|
|
89
|
-
}
|
|
90
|
-
function parseLbbError(status, body, fallbackRequestId) {
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(body);
|
|
93
|
-
if (parsed.error) {
|
|
94
|
-
return new LbbError(status, body, {
|
|
95
|
-
...parsed.error,
|
|
96
|
-
request_id: parsed.error.request_id ?? fallbackRequestId ?? null,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Fall through to an unstructured error.
|
|
102
|
-
}
|
|
103
|
-
return new LbbError(status, body, {
|
|
104
|
-
type: "api_error",
|
|
105
|
-
code: "unstructured_error",
|
|
106
|
-
message: body || `Little Big Brain ${status}`,
|
|
107
|
-
request_id: fallbackRequestId ?? null,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* A typed HTTP client for a Little Big Brain graph server. One instance is scoped to a
|
|
8
|
+
* A typed HTTP client for a little big brain graph server. One instance is scoped to a
|
|
112
9
|
* single graph/branch; construct another for a different scope. All methods
|
|
113
10
|
* return the parsed JSON response and throw {@link LbbError} on failure.
|
|
114
11
|
*/
|
|
@@ -122,10 +19,16 @@ export class LbbClient {
|
|
|
122
19
|
apiVersion;
|
|
123
20
|
maxRetries;
|
|
124
21
|
retryDelayMs;
|
|
22
|
+
timeoutMs;
|
|
23
|
+
onRequest;
|
|
24
|
+
onResponse;
|
|
25
|
+
context;
|
|
125
26
|
search;
|
|
126
27
|
indexes;
|
|
127
28
|
entities;
|
|
128
29
|
schema;
|
|
30
|
+
ontology;
|
|
31
|
+
query;
|
|
129
32
|
constructor(options) {
|
|
130
33
|
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
131
34
|
this.apiKey = options.apiKey;
|
|
@@ -135,16 +38,31 @@ export class LbbClient {
|
|
|
135
38
|
this.apiVersion = options.apiVersion ?? "2026-06-22";
|
|
136
39
|
this.maxRetries = options.maxRetries ?? 2;
|
|
137
40
|
this.retryDelayMs = options.retryDelayMs ?? 100;
|
|
41
|
+
this.timeoutMs = options.timeoutMs ?? 120_000;
|
|
42
|
+
this.onRequest = options.onRequest;
|
|
43
|
+
this.onResponse = options.onResponse;
|
|
44
|
+
if (!Number.isInteger(this.maxRetries) || this.maxRetries < 0) {
|
|
45
|
+
throw new RangeError("maxRetries must be a non-negative integer");
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isFinite(this.retryDelayMs) || this.retryDelayMs < 0) {
|
|
48
|
+
throw new RangeError("retryDelayMs must be a non-negative number");
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs < 0) {
|
|
51
|
+
throw new RangeError("timeoutMs must be a non-negative number");
|
|
52
|
+
}
|
|
138
53
|
const fallback = globalThis.fetch;
|
|
139
54
|
const chosen = options.fetch ?? (fallback ? fallback.bind(globalThis) : undefined);
|
|
140
55
|
if (!chosen) {
|
|
141
56
|
throw new Error("no fetch implementation available; pass options.fetch");
|
|
142
57
|
}
|
|
143
58
|
this.fetchImpl = chosen;
|
|
59
|
+
this.context = new ContextNamespace(this);
|
|
144
60
|
this.search = new SearchNamespace(this);
|
|
145
61
|
this.indexes = new IndexNamespace(this);
|
|
146
62
|
this.entities = new EntityNamespace(this);
|
|
147
63
|
this.schema = new SchemaNamespace(this);
|
|
64
|
+
this.ontology = new OntologyNamespace(this);
|
|
65
|
+
this.query = new QueryNamespace(this);
|
|
148
66
|
}
|
|
149
67
|
graph(name, opts = {}) {
|
|
150
68
|
return new GraphNamespace(this.withScope({
|
|
@@ -169,6 +87,9 @@ export class LbbClient {
|
|
|
169
87
|
apiVersion: this.apiVersion,
|
|
170
88
|
maxRetries: this.maxRetries,
|
|
171
89
|
retryDelayMs: this.retryDelayMs,
|
|
90
|
+
timeoutMs: this.timeoutMs,
|
|
91
|
+
onRequest: this.onRequest,
|
|
92
|
+
onResponse: this.onResponse,
|
|
172
93
|
});
|
|
173
94
|
}
|
|
174
95
|
buildUrl(path, query) {
|
|
@@ -196,7 +117,8 @@ export class LbbClient {
|
|
|
196
117
|
headers["authorization"] = `Bearer ${this.apiKey}`;
|
|
197
118
|
if (opts.idempotencyKey !== undefined)
|
|
198
119
|
headers["idempotency-key"] = opts.idempotencyKey;
|
|
199
|
-
|
|
120
|
+
Object.assign(headers, opts.headers ?? {});
|
|
121
|
+
const canRetry = opts.retry ?? retryAllowed(method, opts.idempotencyKey);
|
|
200
122
|
const body = opts.rawBody !== undefined
|
|
201
123
|
? opts.rawBody
|
|
202
124
|
: opts.body !== undefined
|
|
@@ -207,40 +129,106 @@ export class LbbClient {
|
|
|
207
129
|
headers,
|
|
208
130
|
body,
|
|
209
131
|
};
|
|
132
|
+
const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
|
|
133
|
+
const maxRetries = opts.maxRetries ?? this.maxRetries;
|
|
134
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0) {
|
|
135
|
+
throw new RangeError("maxRetries must be a non-negative integer");
|
|
136
|
+
}
|
|
137
|
+
const startedAt = Date.now();
|
|
138
|
+
const url = this.buildUrl(path, opts.query);
|
|
139
|
+
let attempts = 0;
|
|
210
140
|
let response;
|
|
211
141
|
let text = "";
|
|
212
|
-
for (let attempt = 0; attempt <=
|
|
142
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
143
|
+
if (opts.signal?.aborted)
|
|
144
|
+
throw opts.signal.reason ?? new Error("request aborted");
|
|
145
|
+
attempts = attempt + 1;
|
|
146
|
+
const controller = (timeoutMs > 0 || opts.signal) && typeof AbortController !== "undefined"
|
|
147
|
+
? new AbortController()
|
|
148
|
+
: undefined;
|
|
149
|
+
const abortFromCaller = () => controller?.abort(opts.signal?.reason);
|
|
150
|
+
opts.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
151
|
+
const timer = controller
|
|
152
|
+
? timeoutMs > 0
|
|
153
|
+
? setTimeout(() => controller.abort(), timeoutMs)
|
|
154
|
+
: undefined
|
|
155
|
+
: undefined;
|
|
213
156
|
try {
|
|
214
|
-
|
|
157
|
+
this.onRequest?.({
|
|
158
|
+
method: method.toUpperCase(),
|
|
159
|
+
url,
|
|
160
|
+
attempt: attempts,
|
|
161
|
+
maxAttempts: maxRetries + 1,
|
|
162
|
+
idempotencyKey: opts.idempotencyKey,
|
|
163
|
+
});
|
|
164
|
+
response = await this.fetchImpl(url, {
|
|
165
|
+
...init,
|
|
166
|
+
signal: controller?.signal ?? opts.signal,
|
|
167
|
+
});
|
|
215
168
|
text = await response.text();
|
|
216
169
|
}
|
|
217
170
|
catch (error) {
|
|
218
|
-
|
|
171
|
+
const callerAborted = opts.signal?.aborted === true;
|
|
172
|
+
const requestError = controller?.signal.aborted && !callerAborted
|
|
173
|
+
? Object.assign(new Error(`Little Big Brain request timed out after ${timeoutMs}ms`, {
|
|
174
|
+
cause: error,
|
|
175
|
+
}), { name: "TimeoutError" })
|
|
176
|
+
: error;
|
|
177
|
+
if (!callerAborted && canRetry && attempt < maxRetries) {
|
|
219
178
|
await sleep(this.retryDelayMs * (attempt + 1));
|
|
220
179
|
continue;
|
|
221
180
|
}
|
|
222
|
-
throw
|
|
181
|
+
throw requestError;
|
|
223
182
|
}
|
|
224
|
-
|
|
183
|
+
finally {
|
|
184
|
+
if (timer !== undefined)
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
opts.signal?.removeEventListener("abort", abortFromCaller);
|
|
187
|
+
}
|
|
188
|
+
if (response.ok ||
|
|
189
|
+
!retryableStatus(response.status) ||
|
|
190
|
+
attempt === maxRetries) {
|
|
225
191
|
break;
|
|
226
192
|
}
|
|
227
193
|
if (!canRetry) {
|
|
228
194
|
break;
|
|
229
195
|
}
|
|
230
|
-
await sleep(this.retryDelayMs
|
|
196
|
+
await sleep(retryDelayForAttempt(this.retryDelayMs, attempt, response.headers?.get("retry-after")));
|
|
231
197
|
}
|
|
232
198
|
if (response === undefined)
|
|
233
199
|
throw new Error("request did not produce a response");
|
|
234
200
|
const requestId = response.headers?.get("x-request-id") ?? undefined;
|
|
235
201
|
const version = response.headers?.get("lbb-version") ?? undefined;
|
|
202
|
+
const elapsedMs = Math.max(0, Date.now() - startedAt);
|
|
203
|
+
this.onResponse?.({
|
|
204
|
+
method: method.toUpperCase(),
|
|
205
|
+
url,
|
|
206
|
+
status: response.status,
|
|
207
|
+
requestId,
|
|
208
|
+
attempts,
|
|
209
|
+
retryCount: Math.max(0, attempts - 1),
|
|
210
|
+
elapsedMs,
|
|
211
|
+
});
|
|
236
212
|
if (!response.ok)
|
|
237
213
|
throw parseLbbError(response.status, text.trim(), requestId);
|
|
214
|
+
const responseContentType = response.headers?.get("content-type")?.toLowerCase() ?? "";
|
|
215
|
+
const isRdfText = responseContentType.includes("text/turtle") ||
|
|
216
|
+
responseContentType.includes("application/n-triples") ||
|
|
217
|
+
responseContentType.includes("application/trig") ||
|
|
218
|
+
responseContentType.includes("application/n-quads");
|
|
238
219
|
return {
|
|
239
|
-
data:
|
|
220
|
+
data: text
|
|
221
|
+
? isRdfText
|
|
222
|
+
? text
|
|
223
|
+
: parseResponseJson(text, response.status, requestId)
|
|
224
|
+
: undefined,
|
|
240
225
|
status: response.status,
|
|
241
226
|
requestId,
|
|
242
227
|
version,
|
|
243
228
|
headers: response.headers,
|
|
229
|
+
attempts,
|
|
230
|
+
retryCount: Math.max(0, attempts - 1),
|
|
231
|
+
elapsedMs,
|
|
244
232
|
};
|
|
245
233
|
}
|
|
246
234
|
async request(method, path, opts = {}) {
|
|
@@ -281,6 +269,13 @@ export class LbbClient {
|
|
|
281
269
|
* bounded internal commits server-side, so a whole dataset loads in one
|
|
282
270
|
* streamed request without a single oversized commit. Pass `lines` as an array
|
|
283
271
|
* (serialized to NDJSON here) or a pre-built NDJSON string.
|
|
272
|
+
*
|
|
273
|
+
* Set `index: true` to run one full index build after the last batch, so the
|
|
274
|
+
* data is served from the persisted runs (not just the ephemeral snapshot
|
|
275
|
+
* fallback) by the time the call returns — the "bulk load, queryable on return"
|
|
276
|
+
* path. Prefer this over indexing per batch (which serializes builds and races
|
|
277
|
+
* the throttle): import the whole dataset, index once. The response's `index`
|
|
278
|
+
* object reports whether the build ran or was skipped.
|
|
284
279
|
*/
|
|
285
280
|
import(lines, opts = {}) {
|
|
286
281
|
const ndjson = typeof lines === "string"
|
|
@@ -289,28 +284,56 @@ export class LbbClient {
|
|
|
289
284
|
return this.request("POST", "/v1/graph/import", {
|
|
290
285
|
rawBody: ndjson,
|
|
291
286
|
contentType: "application/x-ndjson",
|
|
292
|
-
query: {
|
|
293
|
-
|
|
287
|
+
query: {
|
|
288
|
+
batch: opts.batch,
|
|
289
|
+
strict: opts.strict,
|
|
290
|
+
observed_at: opts.observedAt,
|
|
291
|
+
index: opts.index,
|
|
292
|
+
},
|
|
293
|
+
idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("import"),
|
|
294
294
|
});
|
|
295
295
|
}
|
|
296
296
|
/**
|
|
297
|
-
* Bulk-ingest N-Triples without client-side conversion. Resource-object
|
|
297
|
+
* Bulk-ingest N-Triples, Turtle, N-Quads, or TriG without client-side conversion. Resource-object
|
|
298
298
|
* triples become keyed Resource edges; literal-object triples become text
|
|
299
299
|
* properties on the subject Resource.
|
|
300
300
|
*/
|
|
301
|
-
importRdf(
|
|
301
|
+
importRdf(rdf, opts = {}) {
|
|
302
|
+
const format = opts.format ?? "ntriples";
|
|
303
|
+
const contentTypes = {
|
|
304
|
+
ntriples: "application/n-triples",
|
|
305
|
+
turtle: "text/turtle",
|
|
306
|
+
nquads: "application/n-quads",
|
|
307
|
+
trig: "application/trig",
|
|
308
|
+
};
|
|
302
309
|
return this.request("POST", "/v1/graph/import/rdf", {
|
|
303
|
-
rawBody:
|
|
304
|
-
contentType:
|
|
310
|
+
rawBody: rdf,
|
|
311
|
+
contentType: contentTypes[format],
|
|
305
312
|
query: {
|
|
306
313
|
batch: opts.batch,
|
|
307
314
|
strict: opts.strict,
|
|
308
315
|
observed_at: opts.observedAt,
|
|
309
|
-
format
|
|
316
|
+
format,
|
|
317
|
+
base_iri: opts.baseIri,
|
|
318
|
+
graph_uri: opts.graphUri,
|
|
319
|
+
blank_node_scope: opts.blankNodeScope,
|
|
310
320
|
resource_type: opts.resourceType,
|
|
311
321
|
edge_idempotency: opts.edgeIdempotency,
|
|
312
322
|
},
|
|
313
|
-
idempotencyKey: opts.idempotencyKey,
|
|
323
|
+
idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("import-rdf"),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/** Export the snapshot-visible RDF projection as Turtle, N-Triples, TriG, or N-Quads. */
|
|
327
|
+
exportRdf(opts = {}) {
|
|
328
|
+
return this.request("GET", "/v1/graph/export/rdf", {
|
|
329
|
+
query: {
|
|
330
|
+
format: opts.format === "ntriples" ? "nt" : opts.format,
|
|
331
|
+
max_triples: opts.maxTriples,
|
|
332
|
+
as_of_valid_time: opts.asOfValidTime,
|
|
333
|
+
as_of_commit_seq: opts.asOfCommitSeq,
|
|
334
|
+
entailment: opts.entailment,
|
|
335
|
+
reason: opts.reason,
|
|
336
|
+
},
|
|
314
337
|
});
|
|
315
338
|
}
|
|
316
339
|
/**
|
|
@@ -333,13 +356,200 @@ export class LbbClient {
|
|
|
333
356
|
createBranch(body) {
|
|
334
357
|
return this.request("POST", "/v1/graph/branch", { body });
|
|
335
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Validate-then-merge: replay `from_branch`'s post-fork commits onto the
|
|
361
|
+
* SCOPED branch (its fork parent) as one new commit. A write — sends an
|
|
362
|
+
* Idempotency-Key so a retry replays instead of re-applying.
|
|
363
|
+
*/
|
|
364
|
+
mergeBranch(body, opts = {}) {
|
|
365
|
+
return this.request("POST", "/v1/graph/branch/merge", {
|
|
366
|
+
body,
|
|
367
|
+
idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("branch-merge"),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Observe: store a conversation episode verbatim as EPISODE evidence,
|
|
372
|
+
* anchor + gate extracted facts on an observe branch, and optionally
|
|
373
|
+
* auto-merge when validation is clean. Flag-gated server-side
|
|
374
|
+
* (`--enable-observe`). A write — carries an Idempotency-Key.
|
|
375
|
+
*/
|
|
376
|
+
observe(body, opts = {}) {
|
|
377
|
+
return this.request("POST", "/v1/memory/observe", {
|
|
378
|
+
body,
|
|
379
|
+
idempotencyKey: opts.idempotencyKey ?? this.idempotencyKey("observe"),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
336
382
|
/**
|
|
337
383
|
* Delete every object under the scoped graph/branch — a destructive reset.
|
|
338
384
|
* `confirm` must equal the scoped graph id; the next commit re-initializes the
|
|
339
385
|
* graph. Branch-scoped: sibling branches are untouched.
|
|
340
386
|
*/
|
|
341
387
|
deleteGraph(opts) {
|
|
342
|
-
return this.request("POST", "/v1/graph/delete", {
|
|
388
|
+
return this.request("POST", "/v1/graph/delete", {
|
|
389
|
+
query: { confirm: opts.confirm },
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
// --- models as runs (training-run registry + eval machinery) ---
|
|
393
|
+
/**
|
|
394
|
+
* The graph's grounding vocabulary as byte-sorted, deduped string sections —
|
|
395
|
+
* the canonical input for a decoder-side automaton (FST/trie) and the
|
|
396
|
+
* vocabulary half of an export bundle.
|
|
397
|
+
*/
|
|
398
|
+
vocabExport(opts = {}) {
|
|
399
|
+
return this.request("GET", "/v1/search/vocab", {
|
|
400
|
+
query: { sections: opts.sections?.join(","), limit: opts.limit },
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Captured signals by flush-seq range, oldest first — the model-training
|
|
405
|
+
* feed. The `seq` on each signal is the temporal-split coordinate (train ≤ T,
|
|
406
|
+
* eval > T).
|
|
407
|
+
*/
|
|
408
|
+
readSignals(opts = {}) {
|
|
409
|
+
return this.request("GET", "/v1/signals", {
|
|
410
|
+
query: { from: opts.from, to: opts.to, limit: opts.limit },
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Record one immutable model-as-run manifest; runs number sequentially per
|
|
415
|
+
* kind. Trainers MUST train on data ≤ `trained_at_commit_seq` and evaluate
|
|
416
|
+
* past it — `modelSplitAudit` verifies the recorded lineage.
|
|
417
|
+
*/
|
|
418
|
+
recordModelRun(body) {
|
|
419
|
+
return this.request("POST", "/v1/models/record", { body });
|
|
420
|
+
}
|
|
421
|
+
/** CAS-promote a recorded run to CURRENT for its kind (replay is a no-op). */
|
|
422
|
+
promoteModelRun(opts) {
|
|
423
|
+
return this.request("POST", "/v1/models/promote", {
|
|
424
|
+
query: { kind: opts.kind, run: opts.run },
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
/** A kind's model runs, newest first, with effective promotion state. */
|
|
428
|
+
modelRegistry(opts) {
|
|
429
|
+
return this.request("GET", "/v1/models/registry", {
|
|
430
|
+
query: { kind: opts.kind },
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
/** GC run prefixes beyond the promoted run + the last `keep`; reports deletions. */
|
|
434
|
+
modelRegistryGc(opts) {
|
|
435
|
+
return this.request("POST", "/v1/models/registry/gc", {
|
|
436
|
+
query: { kind: opts.kind, keep: opts.keep },
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Verify a run's temporal-split obligation from its recorded lineage. */
|
|
440
|
+
modelSplitAudit(opts) {
|
|
441
|
+
return this.request("GET", "/v1/models/split-audit", {
|
|
442
|
+
query: { kind: opts.kind, run: opts.run },
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Champion vs challenger retrieval over one pinned snapshot. Returns
|
|
447
|
+
* promotion evidence (hit-rate@k, latency, overlap); never promotes.
|
|
448
|
+
*/
|
|
449
|
+
shadowEval(body) {
|
|
450
|
+
return this.request("POST", "/v1/models/shadow-eval", { body });
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Execution-verified QA probes generated from the graph's current edges —
|
|
454
|
+
* labels are the executed projections, so they are verified by construction.
|
|
455
|
+
* Feeds `shadowEval` directly.
|
|
456
|
+
*/
|
|
457
|
+
syntheticEval(opts = {}) {
|
|
458
|
+
return this.request("GET", "/v1/models/synthetic-eval", {
|
|
459
|
+
query: { limit: opts.limit },
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/** The doubling retrain policy: is a retrain due for this model kind? */
|
|
463
|
+
modelCadence(opts) {
|
|
464
|
+
return this.request("GET", "/v1/models/cadence", {
|
|
465
|
+
query: { kind: opts.kind },
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* One deterministic trainer tick: build a probe set (execution-verified
|
|
470
|
+
* synthetic pairs, or bring your own), search a bounded candidate space on
|
|
471
|
+
* the train slice, gate the winner against the champion on the held-out
|
|
472
|
+
* eval slice, record the run either way, and promote only when the gate
|
|
473
|
+
* passes. The same tick the `auto_train` cadence fires — always safe to
|
|
474
|
+
* call by hand.
|
|
475
|
+
*/
|
|
476
|
+
trainTick(body) {
|
|
477
|
+
return this.request("POST", "/v1/models/train-tick", { body });
|
|
478
|
+
}
|
|
479
|
+
/** The graph's automatic-training configuration (default: off). */
|
|
480
|
+
trainingConfig() {
|
|
481
|
+
return this.request("GET", "/v1/models/training-config", {});
|
|
482
|
+
}
|
|
483
|
+
/** Set the automatic-training configuration (`auto_train` toggle + kinds). */
|
|
484
|
+
setTrainingConfig(body) {
|
|
485
|
+
return this.request("POST", "/v1/models/training-config", { body });
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Verdict on an ask (`accepted` | `rejected` | `corrected` + the right
|
|
489
|
+
* plan), joined to the ask's trace by `ask_id` — the planner fine-tune's
|
|
490
|
+
* explicit feedback capture. `accepted: false` in the response means
|
|
491
|
+
* signal capture is off on this deployment (the contract is identical).
|
|
492
|
+
*/
|
|
493
|
+
askFeedback(body) {
|
|
494
|
+
return this.request("POST", "/v1/ask/feedback", { body });
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The planner fine-tune's training feed: accepted/corrected feedback
|
|
498
|
+
* joined to its traces (signals ≤ the split pin), topped up with
|
|
499
|
+
* execution-verified synthetic plans.
|
|
500
|
+
*/
|
|
501
|
+
plannerDataset(opts = {}) {
|
|
502
|
+
return this.request("GET", "/v1/models/planner-dataset", {
|
|
503
|
+
query: { limit: opts.limit, split_seq: opts.splitSeq },
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* The DPO pass's training feed: preference pairs from corrected verdicts,
|
|
508
|
+
* paired rejections, and synthetic corrupted-slot pairs.
|
|
509
|
+
*/
|
|
510
|
+
plannerPreferenceDataset(opts = {}) {
|
|
511
|
+
return this.request("GET", "/v1/models/planner-preference-dataset", {
|
|
512
|
+
query: { limit: opts.limit, split_seq: opts.splitSeq },
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The suggest-ranker trainer's probe feed: `suggestion_adopted` signals
|
|
517
|
+
* (typed prefix + adopted text) ≤ the split pin, topped up with
|
|
518
|
+
* execution-verified synthetic vocabulary pairs.
|
|
519
|
+
*/
|
|
520
|
+
suggestDataset(opts = {}) {
|
|
521
|
+
return this.request("GET", "/v1/models/suggest-dataset", {
|
|
522
|
+
query: { limit: opts.limit, split_seq: opts.splitSeq },
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The extractor fine-tune's training feed: EPISODE transcripts joined to
|
|
527
|
+
* the facts the observe pipeline committed from them.
|
|
528
|
+
*/
|
|
529
|
+
extractorDataset(opts = {}) {
|
|
530
|
+
return this.request("GET", "/v1/models/extractor-dataset", {
|
|
531
|
+
query: { limit: opts.limit, split_seq: opts.splitSeq },
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Promote a finished `extractor_lora` training run: gated on held-out fact
|
|
536
|
+
* F1, recorded as a `kind=extractor` training run whose adapter resident
|
|
537
|
+
* extraction then serves.
|
|
538
|
+
*/
|
|
539
|
+
promoteExtractor(opts) {
|
|
540
|
+
return this.request("POST", "/v1/models/promote-extractor", {
|
|
541
|
+
query: { run_id: opts.runId, allow_regression: opts.allowRegression },
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Promote a finished `planner_lora` training run: gated on held-out slot
|
|
546
|
+
* exactness, recorded as a `kind=planner` training run whose adapter `/v1/ask`
|
|
547
|
+
* then serves.
|
|
548
|
+
*/
|
|
549
|
+
promotePlanner(opts) {
|
|
550
|
+
return this.request("POST", "/v1/models/promote-planner", {
|
|
551
|
+
query: { run_id: opts.runId, allow_regression: opts.allowRegression },
|
|
552
|
+
});
|
|
343
553
|
}
|
|
344
554
|
// --- search ---
|
|
345
555
|
/** Full semantic hybrid search from a request body (`POST /v1/graph/search`). */
|
|
@@ -351,7 +561,48 @@ export class LbbClient {
|
|
|
351
561
|
return this.request("POST", "/v1/search/multi", { body });
|
|
352
562
|
}
|
|
353
563
|
/**
|
|
354
|
-
*
|
|
564
|
+
* Grounded prefix completion from the index vocabulary + ontology. Optionally
|
|
565
|
+
* narrow relation completions by a type-signature `context` — a type
|
|
566
|
+
* pair that admits a single relation flags `signature_forced`.
|
|
567
|
+
*/
|
|
568
|
+
suggest(body) {
|
|
569
|
+
return this.request("POST", "/v1/search/suggest", { body });
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Snap free text to the nearest real vocabulary item. Embedding cosine
|
|
573
|
+
* on a managed graph, else lexical; never fabricates a term.
|
|
574
|
+
*/
|
|
575
|
+
resolveTerm(body) {
|
|
576
|
+
return this.request("POST", "/v1/search/resolve-term", { body });
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Ground a natural-language question to the graph's real vocabulary, retrieve
|
|
580
|
+
* against the pinned snapshot, and answer with citations (`/v1/ask`).
|
|
581
|
+
*/
|
|
582
|
+
ask(body) {
|
|
583
|
+
return this.request("POST", "/v1/ask", { body });
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Name the relation between two entities (`/v1/decode`): the DB narrows the
|
|
587
|
+
* candidates to the type pair's admissible relations, answers alone
|
|
588
|
+
* when the pair forces a single relation, and otherwise decodes it with the
|
|
589
|
+
* graph-native fine-tuned model — the "DB narrows, cheap model decodes" call.
|
|
590
|
+
*/
|
|
591
|
+
decode(body) {
|
|
592
|
+
return this.request("POST", "/v1/decode", { body });
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Report which completion mechanisms will carry on this graph:
|
|
596
|
+
* signature sparsity, name semantics, sampled narrowing recall, and a
|
|
597
|
+
* narrow / narrow+finetune / lexical-first recommendation.
|
|
598
|
+
*/
|
|
599
|
+
groundability(opts = {}) {
|
|
600
|
+
return this.request("GET", "/v1/graph/groundability", {
|
|
601
|
+
query: opts.sample != null ? { sample: String(opts.sample) } : undefined,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Append relevance labels for a set of search results — how little big brain
|
|
355
606
|
* gathers customer-specific qrels. Grade results (3 ideal/good, 1 partial,
|
|
356
607
|
* 0 bad), referencing the search response's `search_id` so labels tie back to
|
|
357
608
|
* that ranking. Stored apart from customer facts and exported via
|
|
@@ -549,7 +800,7 @@ export class LbbClient {
|
|
|
549
800
|
return this.request("GET", "/v1/inference/rules");
|
|
550
801
|
}
|
|
551
802
|
/**
|
|
552
|
-
*
|
|
803
|
+
* Derive edges from calibrated retrieval matches (preview): each
|
|
553
804
|
* candidate scored `P >= threshold` becomes a derived edge `(anchor, relation,
|
|
554
805
|
* matched)` with a typed `Retrieval` provenance leaf. Pass either explicit
|
|
555
806
|
* `candidates` or a `query` the server runs as BM25 entity retrieval.
|
|
@@ -609,7 +860,9 @@ export class LbbClient {
|
|
|
609
860
|
* timeout (a 504), then poll `metadata()` for completion.
|
|
610
861
|
*/
|
|
611
862
|
indexBuild(opts = {}) {
|
|
612
|
-
return this.request("POST", "/v1/index/build", {
|
|
863
|
+
return this.request("POST", "/v1/index/build", {
|
|
864
|
+
query: { background: opts.background || undefined },
|
|
865
|
+
});
|
|
613
866
|
}
|
|
614
867
|
/**
|
|
615
868
|
* Build BM25, ANN/vector, and adjacency index families. With
|
|
@@ -618,7 +871,9 @@ export class LbbClient {
|
|
|
618
871
|
* exceed a fronting gateway's timeout, then poll `metadata()` for completion.
|
|
619
872
|
*/
|
|
620
873
|
indexRun(opts = {}) {
|
|
621
|
-
return this.request("POST", "/v1/index/run", {
|
|
874
|
+
return this.request("POST", "/v1/index/run", {
|
|
875
|
+
query: { background: opts.background || undefined },
|
|
876
|
+
});
|
|
622
877
|
}
|
|
623
878
|
/** Append a BM25 delta segment for the unindexed WAL tail. */
|
|
624
879
|
indexDelta() {
|
|
@@ -633,7 +888,10 @@ export class LbbClient {
|
|
|
633
888
|
/** Fold the WAL tail into snapshot segments. */
|
|
634
889
|
compact(opts = {}) {
|
|
635
890
|
return this.request("POST", "/v1/graph/compact", {
|
|
636
|
-
query: {
|
|
891
|
+
query: {
|
|
892
|
+
min_tail_commits: opts.minTailCommits,
|
|
893
|
+
max_segments: opts.maxSegments,
|
|
894
|
+
},
|
|
637
895
|
});
|
|
638
896
|
}
|
|
639
897
|
// --- inspection ---
|
|
@@ -653,226 +911,9 @@ export class LbbClient {
|
|
|
653
911
|
listGraphs() {
|
|
654
912
|
return this.request("GET", "/v1/graphs");
|
|
655
913
|
}
|
|
656
|
-
// ---
|
|
657
|
-
/** Create a database stack and return its one-time stack API key. */
|
|
658
|
-
adminCreateStack(body) {
|
|
659
|
-
return this.request("POST", "/api/admin/stacks", { body });
|
|
660
|
-
}
|
|
661
|
-
/** Inspect a database stack without returning secret key material. */
|
|
662
|
-
adminStack(slug) {
|
|
663
|
-
return this.request("GET", "/api/admin/stacks", { query: { stack: slug } });
|
|
664
|
-
}
|
|
665
|
-
/** Rotate a database stack key and return the new one-time API key. */
|
|
666
|
-
adminRotateStackKey(slug) {
|
|
667
|
-
return this.request("POST", "/api/admin/stacks/rotate-key", { query: { stack: slug } });
|
|
668
|
-
}
|
|
669
|
-
/** Delete a database stack after confirming the slug. */
|
|
670
|
-
adminDeleteStack(slug) {
|
|
671
|
-
return this.request("DELETE", "/api/admin/stacks", { query: { stack: slug, confirm: slug } });
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Mint a short-lived `lbb_ses_…` session token for an account. A trusted
|
|
675
|
-
* co-located service uses it (with `?stack=<slug>`) to call the data plane on
|
|
676
|
-
* the account's behalf without handling the stack's mode-bearing stack key.
|
|
677
|
-
*/
|
|
678
|
-
adminMintSession(accountId) {
|
|
679
|
-
return this.request("POST", "/api/admin/sessions", { body: { account_id: accountId } });
|
|
680
|
-
}
|
|
681
|
-
/** Customer-visible activity for one database stack. */
|
|
682
|
-
adminStackActivity(slug, window = "24h") {
|
|
683
|
-
return this.request("GET", "/api/admin/stacks/activity", { query: { stack: slug, window } });
|
|
684
|
-
}
|
|
914
|
+
// --- stack activity ---
|
|
685
915
|
/** Activity for the stack selected by the bearer stack key or session. */
|
|
686
916
|
stackActivity(window = "24h") {
|
|
687
917
|
return this.request("GET", "/v1/stack/activity", { query: { window } });
|
|
688
918
|
}
|
|
689
919
|
}
|
|
690
|
-
export class GraphNamespace {
|
|
691
|
-
client;
|
|
692
|
-
facts;
|
|
693
|
-
constructor(client) {
|
|
694
|
-
this.client = client;
|
|
695
|
-
this.facts = new FactsNamespace(client);
|
|
696
|
-
}
|
|
697
|
-
branch(name) {
|
|
698
|
-
return new GraphNamespace(this.client.withScope({ branch: name }));
|
|
699
|
-
}
|
|
700
|
-
create() {
|
|
701
|
-
return this.client.createGraph();
|
|
702
|
-
}
|
|
703
|
-
delete(opts) {
|
|
704
|
-
return this.client.deleteGraph(opts);
|
|
705
|
-
}
|
|
706
|
-
/** Retract edges/entities from the scoped graph. See {@link LbbClient.retract}. */
|
|
707
|
-
retract(body, opts = {}) {
|
|
708
|
-
return this.client.retract(body, opts);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
export class FactsNamespace {
|
|
712
|
-
client;
|
|
713
|
-
constructor(client) {
|
|
714
|
-
this.client = client;
|
|
715
|
-
}
|
|
716
|
-
create(body, opts = {}) {
|
|
717
|
-
return this.client.request("POST", "/v1/graph/commit", {
|
|
718
|
-
body,
|
|
719
|
-
idempotencyKey: opts.idempotencyKey ?? this.client.idempotencyKey("facts.create"),
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
/** Bulk-load a dataset as NDJSON. See {@link LbbClient.import}. */
|
|
723
|
-
import(lines, opts = {}) {
|
|
724
|
-
return this.client.import(lines, opts);
|
|
725
|
-
}
|
|
726
|
-
/**
|
|
727
|
-
* Bulk-load N-Triples through the native RDF import endpoint.
|
|
728
|
-
*
|
|
729
|
-
* Statements are committed through the fixed RDF_TRIPLE relation; source RDF
|
|
730
|
-
* predicates and literal term details are preserved as edge metadata.
|
|
731
|
-
*/
|
|
732
|
-
importRdf(ntriples, opts = {}) {
|
|
733
|
-
return this.client.importRdf(ntriples, opts);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
export class SearchNamespace {
|
|
737
|
-
client;
|
|
738
|
-
constructor(client) {
|
|
739
|
-
this.client = client;
|
|
740
|
-
}
|
|
741
|
-
hybrid(input, opts = {}) {
|
|
742
|
-
if (typeof input !== "string") {
|
|
743
|
-
return this.client.request("POST", "/v1/graph/search", { body: input });
|
|
744
|
-
}
|
|
745
|
-
return this.client.request("GET", "/v1/search", {
|
|
746
|
-
query: {
|
|
747
|
-
query: input,
|
|
748
|
-
top_k: opts.topK,
|
|
749
|
-
source: opts.source,
|
|
750
|
-
consistency: opts.consistency,
|
|
751
|
-
lexical: opts.lexical,
|
|
752
|
-
bm25: opts.bm25,
|
|
753
|
-
vector: opts.vector,
|
|
754
|
-
targets: opts.targets?.join(","),
|
|
755
|
-
profile: opts.profile,
|
|
756
|
-
log_impression: opts.logImpression,
|
|
757
|
-
},
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
multi(body) {
|
|
761
|
-
return this.client.multiSearch(body);
|
|
762
|
-
}
|
|
763
|
-
feedback(body, opts = {}) {
|
|
764
|
-
return this.client.searchFeedback(body, opts);
|
|
765
|
-
}
|
|
766
|
-
feedbackExport() {
|
|
767
|
-
return this.client.searchFeedbackExport();
|
|
768
|
-
}
|
|
769
|
-
fullText(body) {
|
|
770
|
-
return this.client.fullTextSearch(body);
|
|
771
|
-
}
|
|
772
|
-
vector(body) {
|
|
773
|
-
return this.client.embeddingSearch(body);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
export class SchemaNamespace {
|
|
777
|
-
client;
|
|
778
|
-
constructor(client) {
|
|
779
|
-
this.client = client;
|
|
780
|
-
}
|
|
781
|
-
/** Active graph schema bundle: ontology plus activated SHACL shapes. */
|
|
782
|
-
view(opts = {}) {
|
|
783
|
-
return this.client.request("GET", "/v1/schema", {
|
|
784
|
-
query: { audit: opts.audit || undefined },
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
/** Preview a proposed RDF/SHACL schema bundle and audit current data. */
|
|
788
|
-
preview(body) {
|
|
789
|
-
return this.client.request("POST", "/v1/schema/preview", { body });
|
|
790
|
-
}
|
|
791
|
-
/** Activate a previewed SHACL schema bundle for this graph branch. */
|
|
792
|
-
publish(body) {
|
|
793
|
-
return this.client.request("POST", "/v1/schema/publish", { body });
|
|
794
|
-
}
|
|
795
|
-
/** Audit current data against the active SHACL schema bundle. */
|
|
796
|
-
audit() {
|
|
797
|
-
return this.client.request("POST", "/v1/schema/audit");
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
export class IndexNamespace {
|
|
801
|
-
client;
|
|
802
|
-
constructor(client) {
|
|
803
|
-
this.client = client;
|
|
804
|
-
}
|
|
805
|
-
run(opts = {}) {
|
|
806
|
-
const background = opts.background ?? (opts.wait === false ? true : undefined);
|
|
807
|
-
return this.client.request("POST", "/v1/index/run", {
|
|
808
|
-
query: { background },
|
|
809
|
-
body: opts.body,
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
build() {
|
|
813
|
-
return this.client.indexBuild();
|
|
814
|
-
}
|
|
815
|
-
delta() {
|
|
816
|
-
return this.client.indexDelta();
|
|
817
|
-
}
|
|
818
|
-
gc(opts = {}) {
|
|
819
|
-
return this.client.indexGc(opts);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
export class EntityNamespace {
|
|
823
|
-
client;
|
|
824
|
-
constructor(client) {
|
|
825
|
-
this.client = client;
|
|
826
|
-
}
|
|
827
|
-
/**
|
|
828
|
-
* Browse entities as the unified list envelope. Pass `fields` (names or `*`)
|
|
829
|
-
* to inline each row's typed attributes as native JSON (under `attributes`) —
|
|
830
|
-
* "list entities and their titles" in one call instead of a list plus N point
|
|
831
|
-
* lookups — or `ids`
|
|
832
|
-
* to fetch a specific set. Page with `cursor` from the previous `next_cursor`.
|
|
833
|
-
*/
|
|
834
|
-
list(opts = {}) {
|
|
835
|
-
const csv = (v) => Array.isArray(v) ? v.join(",") : v;
|
|
836
|
-
return this.client.request("GET", "/v1/graph/entities", {
|
|
837
|
-
query: {
|
|
838
|
-
type: opts.type,
|
|
839
|
-
limit: opts.limit,
|
|
840
|
-
cursor: opts.cursor,
|
|
841
|
-
offset: opts.offset,
|
|
842
|
-
q: opts.query,
|
|
843
|
-
fields: csv(opts.fields),
|
|
844
|
-
ids: csv(opts.ids),
|
|
845
|
-
},
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
get(opts) {
|
|
849
|
-
return this.client.entityMetadata(opts);
|
|
850
|
-
}
|
|
851
|
-
detail(opts) {
|
|
852
|
-
return this.client.entityDetail(opts);
|
|
853
|
-
}
|
|
854
|
-
/**
|
|
855
|
-
* Filter entities already bound by relation patterns using typed attributes,
|
|
856
|
-
* without writing RDF property IRIs by hand. This is a convenience wrapper over
|
|
857
|
-
* the structured SPARQL route: relation `patterns` bind variables, and `where`
|
|
858
|
-
* compares ontology property fields on those bound variables.
|
|
859
|
-
*/
|
|
860
|
-
filterByAttributes(opts) {
|
|
861
|
-
const defaultVar = firstPatternVariable(opts.patterns);
|
|
862
|
-
const where = Array.isArray(opts.where) ? opts.where : [opts.where];
|
|
863
|
-
return this.client.sparql({
|
|
864
|
-
patterns: opts.patterns,
|
|
865
|
-
filters: [...(opts.filters ?? []), ...where.map((filter) => attributeFilter(filter, defaultVar))],
|
|
866
|
-
select: opts.select,
|
|
867
|
-
limit: opts.limit,
|
|
868
|
-
offset: opts.offset,
|
|
869
|
-
as_of_valid_time: opts.asOfValidTime,
|
|
870
|
-
as_of_commit_seq: opts.asOfCommitSeq,
|
|
871
|
-
order_by: opts.orderBy,
|
|
872
|
-
reason: opts.reason,
|
|
873
|
-
max_solutions: opts.maxSolutions,
|
|
874
|
-
max_object_reads: opts.maxObjectReads,
|
|
875
|
-
max_fetched_bytes: opts.maxFetchedBytes,
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
}
|