rete-graph 0.3.6 → 0.3.7

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
@@ -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.3.6/dist/rete-graph.min.js"></script>
31
+ <script src="https://cdn.jsdelivr.net/npm/rete-graph@0.3.7/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: `queryRaw`, `lastWarnings()` (why a FILTER silently dropped rows:
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 a `js-v*` tag. Docs: <https://caviri.github.io/rete/javascript.html>.
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.3.6 | Apache-2.0 | https://github.com/caviri/rete */
1
+ /*! rete-graph v0.3.7 | 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 syncFetch(method, url, headers, body) {
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
- worker.postMessage({ port: port2, signal, method, url, headers, body }, [port2]);
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
- parentPort.on("message", async ({ port, signal, method, url, headers, body }) => {
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