rete-graph 0.3.6 → 0.4.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 +8 -4
- package/dist/index.js +242 -7
- package/dist/rete-graph.js +243 -8
- package/dist/rete-graph.min.js +74 -7
- package/dist/rete_wasm_bg.wasm +0 -0
- package/index.d.ts +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ for (const row of g.query("SELECT ?s ?label WHERE { ?s rdfs:label ?label } LIMIT
|
|
|
28
28
|
One self-contained file, engine included — via any npm CDN:
|
|
29
29
|
|
|
30
30
|
```html
|
|
31
|
-
<script src="https://cdn.jsdelivr.net/npm/rete-graph@0.
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/rete-graph@0.4.0/dist/rete-graph.min.js"></script>
|
|
32
32
|
<script>
|
|
33
33
|
(async () => {
|
|
34
34
|
const g = await rete.open(await rete.build("<urn:a> <urn:knows> <urn:b> ."));
|
|
@@ -49,7 +49,9 @@ g.stats(); // { fileLength, bytes, requests } — a LIMIT query fetches KBs–MB
|
|
|
49
49
|
|
|
50
50
|
Remote opens use synchronous XHR range reads, so they work:
|
|
51
51
|
|
|
52
|
-
- **in Node** (≥18) — out of the box, via a built-in sync-fetch bridge
|
|
52
|
+
- **in Node** (≥18) — out of the box, via a built-in sync-fetch bridge, which
|
|
53
|
+
also fetches the engine's batched reads in parallel (16 at once; set
|
|
54
|
+
`globalThis.reteReadMany = null` before the first `open()` to opt out);
|
|
53
55
|
- **in browser web workers** — where sync binary XHR is allowed (this is how
|
|
54
56
|
the playground runs its queries).
|
|
55
57
|
|
|
@@ -61,7 +63,8 @@ worker. The host serving the file must send CORS headers and honor `Range`.
|
|
|
61
63
|
`query()` returns `{var: Term}` rows for SELECT, a boolean for ASK, and
|
|
62
64
|
`[s, p, o]` Term triples for CONSTRUCT/DESCRIBE. A `Term` carries `.kind` /
|
|
63
65
|
`.value` / `.datatype` / `.lang`, plus `.toJS()` (number/boolean/BigInt for
|
|
64
|
-
common XSD types) and `.n3`. Also: `
|
|
66
|
+
common XSD types) and `.n3`. Also: `partitionPlan()` + `queryRaw(q, { partition })` (a long query as
|
|
67
|
+
resumable chunks split by one variable), `queryRaw`, `lastWarnings()` (why a FILTER silently dropped rows:
|
|
65
68
|
CONTAINS on an IRI, STRSTARTS on a number, … — see the docs), `query(q, {reason: true})`
|
|
66
69
|
(OWL 2 QL entailment), `prefixSearch`, `textSearch`, `schema`, `graphNames`,
|
|
67
70
|
`info`, `card()` / `examples()` (the file's embedded Dataset Card and its
|
|
@@ -164,4 +167,5 @@ npm install && npm test
|
|
|
164
167
|
```
|
|
165
168
|
|
|
166
169
|
Releases are published to npm by `.github/workflows/js-client-publish.yml`
|
|
167
|
-
on
|
|
170
|
+
on the `v*` tag that releases every rete channel.
|
|
171
|
+
Docs: <https://caviri.github.io/rete/javascript.html>.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rete-graph v0.
|
|
1
|
+
/*! rete-graph v0.4.0 | Apache-2.0 | https://github.com/caviri/rete */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -16,14 +16,17 @@ __export(node_sync_xhr_exports, {
|
|
|
16
16
|
install: () => install
|
|
17
17
|
});
|
|
18
18
|
import { MessageChannel, Worker, receiveMessageOnPort } from "node:worker_threads";
|
|
19
|
-
function
|
|
19
|
+
function fetchWorker() {
|
|
20
20
|
if (!worker) {
|
|
21
21
|
worker = new Worker(WORKER_SRC, { eval: true });
|
|
22
22
|
worker.unref();
|
|
23
23
|
}
|
|
24
|
+
return worker;
|
|
25
|
+
}
|
|
26
|
+
function syncFetch(method, url, headers, body) {
|
|
24
27
|
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
25
28
|
const { port1, port2 } = new MessageChannel();
|
|
26
|
-
|
|
29
|
+
fetchWorker().postMessage({ port: port2, signal, method, url, headers, body }, [port2]);
|
|
27
30
|
Atomics.wait(signal, 0, 0);
|
|
28
31
|
const msg = receiveMessageOnPort(port1);
|
|
29
32
|
port1.close();
|
|
@@ -31,12 +34,24 @@ function syncFetch(method, url, headers, body) {
|
|
|
31
34
|
if (msg.message.error) throw new Error(`sync fetch failed: ${msg.message.error}`);
|
|
32
35
|
return msg.message;
|
|
33
36
|
}
|
|
37
|
+
function readMany(url, offsets, lens) {
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (const len of lens) total += len;
|
|
40
|
+
const data = new SharedArrayBuffer(total);
|
|
41
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
42
|
+
fetchWorker().postMessage({ op: "many", signal, url, offsets, lens, data, fanout: FANOUT });
|
|
43
|
+
Atomics.wait(signal, 0, 0);
|
|
44
|
+
return signal[0] === 1 ? new Uint8Array(data) : null;
|
|
45
|
+
}
|
|
34
46
|
function install() {
|
|
35
47
|
if (typeof globalThis.XMLHttpRequest === "undefined") {
|
|
36
48
|
globalThis.XMLHttpRequest = NodeSyncXMLHttpRequest;
|
|
37
49
|
}
|
|
50
|
+
if (globalThis.XMLHttpRequest === NodeSyncXMLHttpRequest && globalThis.reteReadMany === void 0) {
|
|
51
|
+
globalThis.reteReadMany = readMany;
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
|
-
var WORKER_SRC, worker, NodeSyncXMLHttpRequest;
|
|
54
|
+
var WORKER_SRC, FANOUT, worker, NodeSyncXMLHttpRequest;
|
|
40
55
|
var init_node_sync_xhr = __esm({
|
|
41
56
|
"src/node-sync-xhr.js"() {
|
|
42
57
|
WORKER_SRC = `
|
|
@@ -88,7 +103,74 @@ function readFile(method, url, headers, result) {
|
|
|
88
103
|
result.body = buf.buffer.slice(buf.byteOffset, buf.byteOffset + got);
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
|
|
106
|
+
// One batch for reteReadMany: every range read at once (at most \`fanout\` in
|
|
107
|
+
// flight) and copied straight into the caller's shared buffer at its place in
|
|
108
|
+
// the concatenation. A range that is not exactly what was asked for fails the
|
|
109
|
+
// whole batch \u2014 the caller then returns null and the engine re-reads it one
|
|
110
|
+
// range at a time, under its own validation.
|
|
111
|
+
async function readMany({ url, offsets, lens, data, fanout }) {
|
|
112
|
+
const out = new Uint8Array(data);
|
|
113
|
+
const at = [];
|
|
114
|
+
let pos = 0;
|
|
115
|
+
for (const len of lens) {
|
|
116
|
+
at.push(pos);
|
|
117
|
+
pos += len;
|
|
118
|
+
}
|
|
119
|
+
if (url.startsWith("file:")) {
|
|
120
|
+
const fd = fdFor(fileURLToPath(url));
|
|
121
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
122
|
+
let got = 0;
|
|
123
|
+
while (got < lens[i]) {
|
|
124
|
+
const n = fs.readSync(fd, out, at[i] + got, lens[i] - got, offsets[i] + got);
|
|
125
|
+
if (n <= 0) throw new Error("short read at offset " + offsets[i]);
|
|
126
|
+
got += n;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
let next = 0;
|
|
132
|
+
let failure = null;
|
|
133
|
+
// Each lane pulls the next range until the batch is done or one has failed;
|
|
134
|
+
// waiting for every lane (not just the first rejection) means nothing is
|
|
135
|
+
// still writing into the buffer once the caller is told the outcome.
|
|
136
|
+
const lane = async () => {
|
|
137
|
+
while (failure === null && next < offsets.length) {
|
|
138
|
+
const i = next++;
|
|
139
|
+
const offset = offsets[i];
|
|
140
|
+
const len = lens[i];
|
|
141
|
+
if (len === 0) continue;
|
|
142
|
+
try {
|
|
143
|
+
const resp = await fetch(url, { headers: { Range: "bytes=" + offset + "-" + (offset + len - 1) } });
|
|
144
|
+
const range = resp.headers.get("content-range") || "";
|
|
145
|
+
if (resp.status !== 206 || !range.startsWith("bytes " + offset + "-")) {
|
|
146
|
+
await resp.body?.cancel();
|
|
147
|
+
throw new Error("status " + resp.status + " (" + range + ") for offset " + offset);
|
|
148
|
+
}
|
|
149
|
+
const body = new Uint8Array(await resp.arrayBuffer());
|
|
150
|
+
if (body.length < len) throw new Error("short range at offset " + offset);
|
|
151
|
+
out.set(body.subarray(0, len), at[i]);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
failure ??= e;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
await Promise.all(Array.from({ length: Math.min(fanout, offsets.length) }, lane));
|
|
158
|
+
if (failure) throw failure;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
parentPort.on("message", async (msg) => {
|
|
162
|
+
if (msg.op === "many") {
|
|
163
|
+
let outcome = 1;
|
|
164
|
+
try {
|
|
165
|
+
await readMany(msg);
|
|
166
|
+
} catch {
|
|
167
|
+
outcome = 2;
|
|
168
|
+
}
|
|
169
|
+
Atomics.store(msg.signal, 0, outcome);
|
|
170
|
+
Atomics.notify(msg.signal, 0);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const { port, signal, method, url, headers, body } = msg;
|
|
92
174
|
const result = { status: 0, headers: {}, body: null, error: null };
|
|
93
175
|
try {
|
|
94
176
|
if (url.startsWith("file:")) {
|
|
@@ -108,6 +190,7 @@ parentPort.on("message", async ({ port, signal, method, url, headers, body }) =>
|
|
|
108
190
|
Atomics.notify(signal, 0);
|
|
109
191
|
});
|
|
110
192
|
`;
|
|
193
|
+
FANOUT = 16;
|
|
111
194
|
worker = null;
|
|
112
195
|
NodeSyncXMLHttpRequest = class {
|
|
113
196
|
#method;
|
|
@@ -317,6 +400,38 @@ var Graph = class {
|
|
|
317
400
|
GraphFinalization.register(this, this.__wbg_ptr, this);
|
|
318
401
|
return this;
|
|
319
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Plan running `query` as chunks split by the values of `var` — see
|
|
405
|
+
* `rete_core::partition_plan`. JSON `{var, form, keys_query,
|
|
406
|
+
* keys_from_where, offset, limit, dedupe}`, or an error saying why the
|
|
407
|
+
* split would change the answer.
|
|
408
|
+
* @param {string} query
|
|
409
|
+
* @param {string} _var
|
|
410
|
+
* @returns {string}
|
|
411
|
+
*/
|
|
412
|
+
partition_plan(query2, _var) {
|
|
413
|
+
let deferred4_0;
|
|
414
|
+
let deferred4_1;
|
|
415
|
+
try {
|
|
416
|
+
const ptr0 = passStringToWasm0(query2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
417
|
+
const len0 = WASM_VECTOR_LEN;
|
|
418
|
+
const ptr1 = passStringToWasm0(_var, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
419
|
+
const len1 = WASM_VECTOR_LEN;
|
|
420
|
+
const ret = wasm.graph_partition_plan(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
421
|
+
var ptr3 = ret[0];
|
|
422
|
+
var len3 = ret[1];
|
|
423
|
+
if (ret[3]) {
|
|
424
|
+
ptr3 = 0;
|
|
425
|
+
len3 = 0;
|
|
426
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
427
|
+
}
|
|
428
|
+
deferred4_0 = ptr3;
|
|
429
|
+
deferred4_1 = len3;
|
|
430
|
+
return getStringFromWasm0(ptr3, len3);
|
|
431
|
+
} finally {
|
|
432
|
+
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
320
435
|
/**
|
|
321
436
|
* See [`prefix_search`].
|
|
322
437
|
* @param {string} prefix
|
|
@@ -482,6 +597,44 @@ var Graph = class {
|
|
|
482
597
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
483
598
|
}
|
|
484
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* One chunk of a split query: the solutions whose `var` is one of `keys`
|
|
602
|
+
* (term tokens, as the plan's keys query returned them), without the
|
|
603
|
+
* query's top-level OFFSET/LIMIT. Same envelope as [`Graph::query`].
|
|
604
|
+
* @param {string} query
|
|
605
|
+
* @param {string} format
|
|
606
|
+
* @param {boolean} reason
|
|
607
|
+
* @param {string} _var
|
|
608
|
+
* @param {string[]} keys
|
|
609
|
+
* @returns {string}
|
|
610
|
+
*/
|
|
611
|
+
query_partition(query2, format, reason2, _var, keys) {
|
|
612
|
+
let deferred6_0;
|
|
613
|
+
let deferred6_1;
|
|
614
|
+
try {
|
|
615
|
+
const ptr0 = passStringToWasm0(query2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
616
|
+
const len0 = WASM_VECTOR_LEN;
|
|
617
|
+
const ptr1 = passStringToWasm0(format, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
618
|
+
const len1 = WASM_VECTOR_LEN;
|
|
619
|
+
const ptr2 = passStringToWasm0(_var, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
620
|
+
const len2 = WASM_VECTOR_LEN;
|
|
621
|
+
const ptr3 = passArrayJsValueToWasm0(keys, wasm.__wbindgen_malloc);
|
|
622
|
+
const len3 = WASM_VECTOR_LEN;
|
|
623
|
+
const ret = wasm.graph_query_partition(this.__wbg_ptr, ptr0, len0, ptr1, len1, reason2, ptr2, len2, ptr3, len3);
|
|
624
|
+
var ptr5 = ret[0];
|
|
625
|
+
var len5 = ret[1];
|
|
626
|
+
if (ret[3]) {
|
|
627
|
+
ptr5 = 0;
|
|
628
|
+
len5 = 0;
|
|
629
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
630
|
+
}
|
|
631
|
+
deferred6_0 = ptr5;
|
|
632
|
+
deferred6_1 = len5;
|
|
633
|
+
return getStringFromWasm0(ptr5, len5);
|
|
634
|
+
} finally {
|
|
635
|
+
wasm.__wbindgen_free(deferred6_0, deferred6_1, 1);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
485
638
|
/**
|
|
486
639
|
* As [`query`], with OWL 2 QL entailment on (`rdfs:subClassOf` /
|
|
487
640
|
* `subPropertyOf` / `domain` / `range` reasoning by query rewriting).
|
|
@@ -1018,6 +1171,36 @@ var RemoteGraph = class {
|
|
|
1018
1171
|
RemoteGraphFinalization.register(this, this.__wbg_ptr, this);
|
|
1019
1172
|
return this;
|
|
1020
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* See [`Graph::partition_plan`]. Reads the dictionary entries of the
|
|
1176
|
+
* query's constants and the resident tile directory — no tile data.
|
|
1177
|
+
* @param {string} query
|
|
1178
|
+
* @param {string} _var
|
|
1179
|
+
* @returns {string}
|
|
1180
|
+
*/
|
|
1181
|
+
partition_plan(query2, _var) {
|
|
1182
|
+
let deferred4_0;
|
|
1183
|
+
let deferred4_1;
|
|
1184
|
+
try {
|
|
1185
|
+
const ptr0 = passStringToWasm0(query2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1186
|
+
const len0 = WASM_VECTOR_LEN;
|
|
1187
|
+
const ptr1 = passStringToWasm0(_var, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1188
|
+
const len1 = WASM_VECTOR_LEN;
|
|
1189
|
+
const ret = wasm.remotegraph_partition_plan(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
1190
|
+
var ptr3 = ret[0];
|
|
1191
|
+
var len3 = ret[1];
|
|
1192
|
+
if (ret[3]) {
|
|
1193
|
+
ptr3 = 0;
|
|
1194
|
+
len3 = 0;
|
|
1195
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
1196
|
+
}
|
|
1197
|
+
deferred4_0 = ptr3;
|
|
1198
|
+
deferred4_1 = len3;
|
|
1199
|
+
return getStringFromWasm0(ptr3, len3);
|
|
1200
|
+
} finally {
|
|
1201
|
+
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1021
1204
|
/**
|
|
1022
1205
|
* See [`prefix_search`] — over the resident, cached remote handle. Faults the
|
|
1023
1206
|
* pyramid (where the label index lives) on the first call, then serves the
|
|
@@ -1143,6 +1326,42 @@ var RemoteGraph = class {
|
|
|
1143
1326
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
1144
1327
|
}
|
|
1145
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* See [`Graph::query_partition`].
|
|
1331
|
+
* @param {string} query
|
|
1332
|
+
* @param {string} format
|
|
1333
|
+
* @param {boolean} reason
|
|
1334
|
+
* @param {string} _var
|
|
1335
|
+
* @param {string[]} keys
|
|
1336
|
+
* @returns {string}
|
|
1337
|
+
*/
|
|
1338
|
+
query_partition(query2, format, reason2, _var, keys) {
|
|
1339
|
+
let deferred6_0;
|
|
1340
|
+
let deferred6_1;
|
|
1341
|
+
try {
|
|
1342
|
+
const ptr0 = passStringToWasm0(query2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1343
|
+
const len0 = WASM_VECTOR_LEN;
|
|
1344
|
+
const ptr1 = passStringToWasm0(format, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1345
|
+
const len1 = WASM_VECTOR_LEN;
|
|
1346
|
+
const ptr2 = passStringToWasm0(_var, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1347
|
+
const len2 = WASM_VECTOR_LEN;
|
|
1348
|
+
const ptr3 = passArrayJsValueToWasm0(keys, wasm.__wbindgen_malloc);
|
|
1349
|
+
const len3 = WASM_VECTOR_LEN;
|
|
1350
|
+
const ret = wasm.remotegraph_query_partition(this.__wbg_ptr, ptr0, len0, ptr1, len1, reason2, ptr2, len2, ptr3, len3);
|
|
1351
|
+
var ptr5 = ret[0];
|
|
1352
|
+
var len5 = ret[1];
|
|
1353
|
+
if (ret[3]) {
|
|
1354
|
+
ptr5 = 0;
|
|
1355
|
+
len5 = 0;
|
|
1356
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
1357
|
+
}
|
|
1358
|
+
deferred6_0 = ptr5;
|
|
1359
|
+
deferred6_1 = len5;
|
|
1360
|
+
return getStringFromWasm0(ptr5, len5);
|
|
1361
|
+
} finally {
|
|
1362
|
+
wasm.__wbindgen_free(deferred6_0, deferred6_1, 1);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1146
1365
|
/**
|
|
1147
1366
|
* As [`query`], with OWL 2 QL entailment on (reason over the ontology while
|
|
1148
1367
|
* reading only the bytes the rewritten query touches).
|
|
@@ -3193,13 +3412,29 @@ var Graph2 = class {
|
|
|
3193
3412
|
* The engine's raw JSON result envelope. Carries a `warnings` array when the
|
|
3194
3413
|
* query raised type errors (see {@link Graph#lastWarnings}).
|
|
3195
3414
|
*/
|
|
3196
|
-
queryRaw(query2, { reason: reason2 = false } = {}) {
|
|
3415
|
+
queryRaw(query2, { reason: reason2 = false, partition } = {}) {
|
|
3197
3416
|
this.#warnings = [];
|
|
3198
|
-
const s = reason2 ? this.#g.query_reasoned(query2, "json") : this.#g.query(query2, "json");
|
|
3417
|
+
const s = partition ? this.#g.query_partition(query2, "json", reason2, partition.var, partition.keys) : reason2 ? this.#g.query_reasoned(query2, "json") : this.#g.query(query2, "json");
|
|
3199
3418
|
const env = JSON.parse(s);
|
|
3200
3419
|
this.#warnings = env.warnings ?? [];
|
|
3201
3420
|
return env;
|
|
3202
3421
|
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Plan running `query` as chunks split by the values of `variable` (`?w`
|
|
3424
|
+
* or `w`) — so a long extraction can go in short pieces, each retried or
|
|
3425
|
+
* resumed on its own. Throws, saying why, when the chunks' answers together
|
|
3426
|
+
* would not be the query's: ORDER BY, an aggregate not grouped by the
|
|
3427
|
+
* variable, a variable some row leaves unbound, ASK, DESCRIBE.
|
|
3428
|
+
*
|
|
3429
|
+
* `{var, form, keys_query, keys_from_where, offset, limit, dedupe}`: run
|
|
3430
|
+
* `keys_query` for the values, then each chunk of them with
|
|
3431
|
+
* `queryRaw(query, {partition: {var, keys}})`. The chunks' rows in key
|
|
3432
|
+
* order, deduplicated when `dedupe`, then sliced by `offset` / `limit`, are
|
|
3433
|
+
* the query's rows.
|
|
3434
|
+
*/
|
|
3435
|
+
partitionPlan(query2, variable) {
|
|
3436
|
+
return JSON.parse(this.#g.partition_plan(query2, variable));
|
|
3437
|
+
}
|
|
3203
3438
|
/**
|
|
3204
3439
|
* Diagnostics from the most recent `query()` / `queryRaw()` on this graph:
|
|
3205
3440
|
* the SPARQL type errors a FILTER silently turned into `false` (CONTAINS on
|