graphwise-reactodia 0.0.1-TR10 → 0.0.1-TR11

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.
@@ -774,7 +774,7 @@ var global$1 = (typeof global !== "undefined" ? global :
774
774
  typeof self !== "undefined" ? self :
775
775
  typeof window !== "undefined" ? window : {});
776
776
 
777
- var lookup = [];
777
+ var lookup$1 = [];
778
778
  var revLookup = [];
779
779
  var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array;
780
780
  var inited = false;
@@ -782,7 +782,7 @@ function init () {
782
782
  inited = true;
783
783
  var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
784
784
  for (var i = 0, len = code.length; i < len; ++i) {
785
- lookup[i] = code[i];
785
+ lookup$1[i] = code[i];
786
786
  revLookup[code.charCodeAt(i)] = i;
787
787
  }
788
788
 
@@ -836,7 +836,7 @@ function toByteArray (b64) {
836
836
  }
837
837
 
838
838
  function tripletToBase64 (num) {
839
- return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
839
+ return lookup$1[num >> 18 & 0x3F] + lookup$1[num >> 12 & 0x3F] + lookup$1[num >> 6 & 0x3F] + lookup$1[num & 0x3F]
840
840
  }
841
841
 
842
842
  function encodeChunk (uint8, start, end) {
@@ -868,14 +868,14 @@ function fromByteArray (uint8) {
868
868
  // pad the end with zeros, but make sure to not forget the extra bytes
869
869
  if (extraBytes === 1) {
870
870
  tmp = uint8[len - 1];
871
- output += lookup[tmp >> 2];
872
- output += lookup[(tmp << 4) & 0x3F];
871
+ output += lookup$1[tmp >> 2];
872
+ output += lookup$1[(tmp << 4) & 0x3F];
873
873
  output += '==';
874
874
  } else if (extraBytes === 2) {
875
875
  tmp = (uint8[len - 2] << 8) + (uint8[len - 1]);
876
- output += lookup[tmp >> 10];
877
- output += lookup[(tmp >> 4) & 0x3F];
878
- output += lookup[(tmp << 2) & 0x3F];
876
+ output += lookup$1[tmp >> 10];
877
+ output += lookup$1[(tmp >> 4) & 0x3F];
878
+ output += lookup$1[(tmp << 2) & 0x3F];
879
879
  output += '=';
880
880
  }
881
881
 
@@ -6398,6 +6398,567 @@ function escapeRegexp(token) {
6398
6398
  return token.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
6399
6399
  }
6400
6400
  //#endregion
6401
+ //#region src/data/sparql/sparqlModels.ts
6402
+ function mapSparqlResponseIntoRdfJs(response, factory) {
6403
+ function mapSparqlBinding(binding) {
6404
+ const mapped = {};
6405
+ for (const key in binding) if (Object.prototype.hasOwnProperty.call(binding, key)) {
6406
+ const term = binding[key];
6407
+ switch (term.type) {
6408
+ case "uri":
6409
+ mapped[key] = factory.namedNode(term.value);
6410
+ break;
6411
+ case "bnode":
6412
+ mapped[key] = factory.blankNode(term.value);
6413
+ break;
6414
+ case "literal":
6415
+ case "typed-literal":
6416
+ mapped[key] = factory.literal(term.value, term["xml:lang"] ? term["xml:lang"] : term.datatype ? factory.namedNode(term.datatype) : void 0);
6417
+ break;
6418
+ default: throw new Error(`Unexpected SPARQL term type: "${term.type}"`);
6419
+ }
6420
+ }
6421
+ return mapped;
6422
+ }
6423
+ return {
6424
+ ...response,
6425
+ results: {
6426
+ ...response.results,
6427
+ bindings: response.results.bindings.map(mapSparqlBinding)
6428
+ }
6429
+ };
6430
+ }
6431
+ function isRdfIri(term) {
6432
+ return Boolean(term && term.termType === "NamedNode");
6433
+ }
6434
+ function isRdfBlank(term) {
6435
+ return Boolean(term && term.termType === "BlankNode");
6436
+ }
6437
+ function isRdfLiteral(term) {
6438
+ return Boolean(term && term.termType === "Literal");
6439
+ }
6440
+ function isBlankBinding(binding) {
6441
+ const blank = binding;
6442
+ return blank.blankTrgProp !== void 0 || blank.blankTrg !== void 0 || blank.blankSrcProp !== void 0 || blank.blankSrc !== void 0;
6443
+ }
6444
+ /**
6445
+ * IRI prefix for the encoded blank node subtype produced by this module.
6446
+ *
6447
+ * @see {@link isEncodedBlank}
6448
+ */
6449
+ var ENCODED_PREFIX = "urn:reactodia:blank:sparql:";
6450
+ /**
6451
+ * There is no way to refer to an existing blank node from a SPARQL query, and the labels
6452
+ * an endpoint returns for one are local to the response they came in. Instead of trying to
6453
+ * address a blank node, the whole statement set which describes it is encoded into the
6454
+ * element IRI, which makes the identity content-addressed: the same surrounding graph always
6455
+ * yields the same IRI, and every request about such an element can be answered locally by
6456
+ * decoding it back, without another round-trip to the endpoint.
6457
+ *
6458
+ * The trade-off is size: the IRI grows linearly with the number of statements attached to
6459
+ * the blank node, at roughly 300 characters per statement. {@link MAX_RECURSION_DEEP} bounds
6460
+ * how deep nesting can go, but nothing bounds how wide a single blank node can be, so a long
6461
+ * RDF list produces a correspondingly long IRI (measured against the W3C Organization
6462
+ * ontology: 380-1650 characters for its OWL axioms; a synthetic 100-member list: ~28 KB).
6463
+ */
6464
+ function isEncodedBlank$1(iri) {
6465
+ return iri.startsWith(ENCODED_PREFIX);
6466
+ }
6467
+ /**
6468
+ * Additional output bindings which {@link BLANK_NODE_QUERY} produces, to be added
6469
+ * to the outer projection of the lookup query.
6470
+ */
6471
+ var BLANK_NODE_QUERY_PARAMETERS = "?blankType ?blankTrgProp ?blankTrg ?blankSrc ?blankSrcProp";
6472
+ /**
6473
+ * Lookup query addition which describes every blank node in `?inst` by its outgoing
6474
+ * statements and the statement pointing at it.
6475
+ *
6476
+ * The three alternatives handle a plain blank node, an RDF list referenced directly
6477
+ * and a cell in the middle of an RDF list (which is resolved to the list head, so that
6478
+ * a list appears as a single element rather than one element per cell).
6479
+ */
6480
+ var BLANK_NODE_QUERY = `
6481
+ OPTIONAL {
6482
+ FILTER (ISBLANK(?inst)).
6483
+ {
6484
+ ?inst ?blankTrgProp ?blankTrg.
6485
+ ?blankSrc ?blankSrcProp ?inst.
6486
+ FILTER NOT EXISTS { ?inst rdf:first _:smth1 }.
6487
+ BIND("blankNode" as ?blankType)
6488
+ } UNION {
6489
+ ?inst rdf:rest*/rdf:first ?blankTrg.
6490
+ ?blankSrc ?blankSrcProp ?inst.
6491
+ _:smth2 rdf:first ?blankTrg.
6492
+ BIND(?blankSrcProp as ?blankTrgProp)
6493
+ BIND("listHead" as ?blankType)
6494
+ FILTER NOT EXISTS { _:smth3 rdf:rest ?inst }.
6495
+ } UNION {
6496
+ ?listHead rdf:rest* ?inst.
6497
+ FILTER NOT EXISTS { _:smth4 rdf:rest ?listHead }.
6498
+
6499
+ ?listHead rdf:rest*/rdf:first ?blankTrg.
6500
+ ?blankSrc ?blankSrcProp ?listHead.
6501
+ _:smth5 rdf:first ?blankTrg.
6502
+ BIND(?blankSrcProp as ?blankTrgProp)
6503
+ BIND("listHead" as ?blankType)
6504
+ }
6505
+ }
6506
+ `;
6507
+ var XSD_STRING = "http://www.w3.org/2001/XMLSchema#string";
6508
+ function encodeTerm(term) {
6509
+ switch (term.termType) {
6510
+ case "NamedNode": return ["i", term.value];
6511
+ case "BlankNode": return ["b", term.value];
6512
+ case "Literal":
6513
+ if (term.language) return [
6514
+ "l",
6515
+ term.value,
6516
+ term.language
6517
+ ];
6518
+ else if (term.datatype && term.datatype.value !== XSD_STRING) return [
6519
+ "d",
6520
+ term.value,
6521
+ term.datatype.value
6522
+ ];
6523
+ return ["l", term.value];
6524
+ }
6525
+ }
6526
+ function decodeTerm(encoded, factory) {
6527
+ switch (encoded[0]) {
6528
+ case "i": return factory.namedNode(encoded[1]);
6529
+ case "b": return factory.blankNode(encoded[1]);
6530
+ case "l": return factory.literal(encoded[1], encoded[2]);
6531
+ case "d": return factory.literal(encoded[1], factory.namedNode(encoded[2]));
6532
+ default: throw new Error(`Unexpected encoded blank node term: ${JSON.stringify(encoded)}`);
6533
+ }
6534
+ }
6535
+ /**
6536
+ * Encodes the statement set which describes a single blank node into an element IRI.
6537
+ *
6538
+ * Bindings are deduplicated and sorted, so that the same statement set always produces
6539
+ * the same IRI regardless of the order the endpoint returned it in. The blank node label
6540
+ * itself is deliberately left out: it is local to the response it came from and would make
6541
+ * the IRI change between otherwise identical requests.
6542
+ */
6543
+ function encodeId(blankBindings, statementLimit) {
6544
+ const bindingSet = /* @__PURE__ */ new Map();
6545
+ for (const binding of blankBindings) {
6546
+ const encoded = {
6547
+ t: binding.blankType.value,
6548
+ p: encodeTerm(binding.blankTrgProp),
6549
+ o: encodeTerm(binding.blankTrg),
6550
+ s: binding.blankSrc ? encodeTerm(binding.blankSrc) : void 0,
6551
+ sp: binding.blankSrcProp ? encodeTerm(binding.blankSrcProp) : void 0,
6552
+ c: binding.class ? encodeTerm(binding.class) : void 0
6553
+ };
6554
+ bindingSet.set(JSON.stringify(encoded), encoded);
6555
+ }
6556
+ const orderedKeys = Array.from(bindingSet.keys()).sort();
6557
+ const normalizedBindings = (typeof statementLimit === "number" ? orderedKeys.slice(0, Math.max(statementLimit, 0)) : orderedKeys).map((key) => bindingSet.get(key));
6558
+ return ENCODED_PREFIX + toBase64Url(JSON.stringify(normalizedBindings));
6559
+ }
6560
+ /**
6561
+ * Encodes the payload as base64url rather than percent-escaping it.
6562
+ *
6563
+ * A nested blank node is embedded into its parent by its own encoded IRI, and base64url
6564
+ * output contains only characters which need no escaping, so nesting costs a flat 4/3 per
6565
+ * level. Percent-escaping would escape the escapes, roughly doubling the payload at every
6566
+ * level instead.
6567
+ */
6568
+ function toBase64Url(text) {
6569
+ const bytes = new TextEncoder().encode(text);
6570
+ let binary = "";
6571
+ for (const byte of bytes) binary += String.fromCharCode(byte);
6572
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6573
+ }
6574
+ function fromBase64Url(encoded) {
6575
+ const binary = atob(encoded.replace(/-/g, "+").replace(/_/g, "/"));
6576
+ const bytes = new Uint8Array(binary.length);
6577
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
6578
+ return new TextDecoder().decode(bytes);
6579
+ }
6580
+ /**
6581
+ * Decodes an element IRI produced by {@link encodeId} back into the statement set it
6582
+ * was made from, or returns `undefined` if the IRI is not an encoded blank node or
6583
+ * cannot be parsed.
6584
+ */
6585
+ function decodeId(id, factory) {
6586
+ if (!isEncodedBlank$1(id)) return;
6587
+ let parsed;
6588
+ try {
6589
+ const encodedPart = id.substring(27);
6590
+ parsed = JSON.parse(fromBase64Url(encodedPart));
6591
+ if (!Array.isArray(parsed)) return;
6592
+ } catch {
6593
+ return;
6594
+ }
6595
+ try {
6596
+ const inst = factory.namedNode(id);
6597
+ return parsed.map((encoded) => {
6598
+ const binding = {
6599
+ inst,
6600
+ blankType: factory.literal(encoded.t),
6601
+ blankTrgProp: decodeTerm(encoded.p, factory),
6602
+ blankTrg: decodeTerm(encoded.o, factory),
6603
+ blankSrc: encoded.s ? decodeTerm(encoded.s, factory) : void 0,
6604
+ blankSrcProp: encoded.sp ? decodeTerm(encoded.sp, factory) : void 0,
6605
+ class: encoded.c ? decodeTerm(encoded.c, factory) : void 0
6606
+ };
6607
+ binding.label = createLabelForBlankBinding(binding, factory);
6608
+ return binding;
6609
+ });
6610
+ } catch {
6611
+ return;
6612
+ }
6613
+ }
6614
+ function createLabelForBlankBinding(binding, factory) {
6615
+ if (binding.blankType.value === "listHead") return factory.literal("RDFList");
6616
+ const { class: classTerm } = binding;
6617
+ return factory.literal(classTerm ? getLocalName(classTerm.value) ?? classTerm.value : "anonymous");
6618
+ }
6619
+ /**
6620
+ * Deduplicates identical in-flight queries, since the chains being followed
6621
+ * often overlap on their leading statements.
6622
+ */
6623
+ var QueryExecutor = class {
6624
+ queryFunction;
6625
+ running = /* @__PURE__ */ new Map();
6626
+ constructor(queryFunction) {
6627
+ this.queryFunction = queryFunction;
6628
+ }
6629
+ executeQuery(query) {
6630
+ const existing = this.running.get(query);
6631
+ if (existing) return existing;
6632
+ const execution = this.queryFunction(query).then((response) => {
6633
+ this.running.delete(query);
6634
+ return response;
6635
+ }, (error) => {
6636
+ this.running.delete(query);
6637
+ throw error;
6638
+ });
6639
+ this.running.set(query, execution);
6640
+ return execution;
6641
+ }
6642
+ };
6643
+ /**
6644
+ * Replaces the raw blank node bindings in a lookup response with bindings whose `inst`
6645
+ * is an encoded IRI, resolving nested blank nodes along the way.
6646
+ */
6647
+ async function updateLookupResults(result, queryFunction, settings, factory) {
6648
+ const completeBindings = [];
6649
+ const blankBindings = [];
6650
+ for (const binding of result.results.bindings) if (isBlankBinding(binding)) blankBindings.push(binding);
6651
+ else completeBindings.push(binding);
6652
+ if (blankBindings.length === 0) return result;
6653
+ const processedBindings = await processBlankBindings(blankBindings, queryFunction, settings, factory);
6654
+ return {
6655
+ ...result,
6656
+ results: {
6657
+ ...result.results,
6658
+ bindings: completeBindings.concat(processedBindings)
6659
+ }
6660
+ };
6661
+ }
6662
+ /**
6663
+ * Groups raw blank node bindings by the blank node they describe, resolves any nested
6664
+ * blank nodes they point at and rewrites both the instance and the nested targets into
6665
+ * encoded IRIs.
6666
+ *
6667
+ * The bindings are mutated in place and returned.
6668
+ */
6669
+ async function processBlankBindings(blankBindings, queryFunction, settings, factory) {
6670
+ const bindingGroupsById = /* @__PURE__ */ new Map();
6671
+ for (const binding of blankBindings) {
6672
+ let group = bindingGroupsById.get(binding.inst.value);
6673
+ if (!group) {
6674
+ group = [];
6675
+ bindingGroupsById.set(binding.inst.value, group);
6676
+ }
6677
+ group.push(binding);
6678
+ }
6679
+ const nestedChains = [];
6680
+ for (const binding of blankBindings) if (isRdfBlank(binding.blankTrg)) nestedChains.push([binding]);
6681
+ const loadedGroups = await loadRelatedBlankNodes(nestedChains, new QueryExecutor(queryFunction), settings, factory);
6682
+ const { blankNodeStatementLimit } = settings;
6683
+ const idsMap = getEncodedIdDictionary(loadedGroups, factory, blankNodeStatementLimit);
6684
+ for (const group of bindingGroupsById.values()) {
6685
+ for (const binding of group) {
6686
+ const encodedNestedId = idsMap.get(binding.blankTrg.value);
6687
+ if (encodedNestedId !== void 0) binding.blankTrg = factory.namedNode(encodedNestedId);
6688
+ if (!binding.label) binding.label = createLabelForBlankBinding(binding, factory);
6689
+ }
6690
+ updateGroupIds(group, encodeId(group, blankNodeStatementLimit), factory);
6691
+ }
6692
+ return blankBindings.slice();
6693
+ }
6694
+ function getEncodedIdDictionary(blankBindingGroups, factory, statementLimit) {
6695
+ const idDictionary = /* @__PURE__ */ new Map();
6696
+ for (const [key, group] of blankBindingGroups) {
6697
+ const encodedId = encodeId(group, statementLimit);
6698
+ idDictionary.set(key, encodedId);
6699
+ updateGroupIds(group, encodedId, factory);
6700
+ }
6701
+ return idDictionary;
6702
+ }
6703
+ function updateGroupIds(group, newId, factory) {
6704
+ const inst = factory.namedNode(newId);
6705
+ for (const binding of group) binding.inst = inst;
6706
+ }
6707
+ async function loadRelatedBlankNodes(blankChains, queryExecutor, settings, factory, recursionDeep = 1) {
6708
+ const loadedBlankBindings = /* @__PURE__ */ new Map();
6709
+ if (recursionDeep > 3 || blankChains.length === 0) return loadedBlankBindings;
6710
+ const results = await Promise.all(blankChains.map(async (chain) => ({
6711
+ chain,
6712
+ response: await queryExecutor.executeQuery(getQueryForChain(chain, settings))
6713
+ })));
6714
+ await Promise.all(results.map(async ({ chain, response }) => {
6715
+ const bindings = response.results.bindings;
6716
+ if (bindings.length === 0) return;
6717
+ const nestedChains = [];
6718
+ for (const binding of bindings) if (isRdfBlank(binding.blankTrg)) nestedChains.push(chain.concat([binding]));
6719
+ const idsMap = getEncodedIdDictionary(await loadRelatedBlankNodes(nestedChains, queryExecutor, settings, factory, recursionDeep + 1), factory, settings.blankNodeStatementLimit);
6720
+ for (const binding of bindings) {
6721
+ const encodedNestedId = idsMap.get(binding.blankTrg.value);
6722
+ if (encodedNestedId !== void 0) binding.blankTrg = factory.namedNode(encodedNestedId);
6723
+ binding.label = createLabelForBlankBinding(binding, factory);
6724
+ let group = loadedBlankBindings.get(binding.inst.value);
6725
+ if (!group) {
6726
+ group = [];
6727
+ loadedBlankBindings.set(binding.inst.value, group);
6728
+ }
6729
+ group.push(binding);
6730
+ }
6731
+ }));
6732
+ return loadedBlankBindings;
6733
+ }
6734
+ /**
6735
+ * Builds a query which walks the chain of statements from the named node the chain starts at
6736
+ * down to the blank node at its end, and describes that blank node.
6737
+ *
6738
+ * The chain is expressed entirely in IRIs and predicates, since blank node labels from
6739
+ * a previous response cannot be used to address anything in a new query.
6740
+ */
6741
+ function getQueryForChain(blankNodes, settings) {
6742
+ function getQueryBlock(blankNode, index, maxIndex) {
6743
+ const trustableTrgProp = index === 0 || blankNode.blankType.value !== "listHead";
6744
+ const sourceId = index > 0 ? `?inst${index - 1}` : formatChainTerm(blankNode.blankSrc, `?blankAnySrc${index}`);
6745
+ const sourcePropId = trustableTrgProp ? index > 0 ? `?blankTrgProp${index - 1}` : formatChainTerm(blankNode.blankSrcProp, `?blankAnySrcProp${index}`) : `?anyType${index}`;
6746
+ const instPostfix = index === maxIndex ? "" : index.toString();
6747
+ const targetPropId = trustableTrgProp ? formatChainTerm(blankNode.blankTrgProp, `?blankAnyTrgProp${index}`) : `?anyType0${index}`;
6748
+ return `
6749
+ # ======================
6750
+ ${sourceId} ${sourcePropId} ?blankSrc${index}.
6751
+ ${index === 0 && blankNode.blankType.value === "listHead" ? `?blankSrc${index} rdf:rest*/rdf:first ?inst${instPostfix}.` : `?blankSrc${index} ${targetPropId} ?inst${instPostfix}.`}
6752
+ BIND (${formatChainTerm(blankNode.blankTrgProp, `?blankAnyBind${index}`)} as ?blankSrcProp${index}).
6753
+ FILTER (ISBLANK(?inst${instPostfix})).
6754
+ {
6755
+ ?inst${instPostfix} ?blankTrgProp${instPostfix} ?blankTrg${instPostfix}.
6756
+ BIND("blankNode" as ?blankType${instPostfix}).
6757
+ FILTER NOT EXISTS { ?inst${instPostfix} rdf:first _:smth1${index} }.
6758
+ } UNION {
6759
+ ?inst${instPostfix} rdf:rest*/rdf:first ?blankTrg${instPostfix}.
6760
+ ?blankSrc${index} ?blankSrcProp${index} ?inst${instPostfix}.
6761
+ _:smth2${index} rdf:first ?blankTrg${instPostfix}.
6762
+ BIND(?blankSrcProp${index} as ?blankTrgProp${instPostfix})
6763
+ BIND("listHead" as ?blankType${instPostfix})
6764
+ FILTER NOT EXISTS { _:smth3${index} rdf:rest ?inst${instPostfix} }.
6765
+ }
6766
+ OPTIONAL {
6767
+ ?inst${instPostfix} rdf:type ?class${instPostfix}.
6768
+ }
6769
+ `;
6770
+ }
6771
+ const body = blankNodes.map((bn, index) => getQueryBlock(bn, index, blankNodes.length - 1)).join("\n");
6772
+ return `${settings.defaultPrefix}
6773
+ SELECT ?inst ?class ?label ?blankTrgProp ?blankTrg ?blankType
6774
+ WHERE {
6775
+ ${body}
6776
+ }
6777
+ `;
6778
+ }
6779
+ /**
6780
+ * Formats a chain term as an IRI, falling back to a fresh variable when the term is
6781
+ * missing or is itself a blank node, neither of which can be referenced from a query.
6782
+ */
6783
+ function formatChainTerm(term, fallbackVariable) {
6784
+ return isRdfIri(term) ? `<${term.value}>` : fallbackVariable;
6785
+ }
6786
+ /**
6787
+ * Answers {@link DataProvider.elements} for encoded blank nodes without querying the endpoint.
6788
+ *
6789
+ * Literal targets of the blank node are surfaced as element properties; targets which are
6790
+ * entities of their own become links instead, see {@link links}.
6791
+ */
6792
+ function elements(elementIds, factory) {
6793
+ const bindings = [];
6794
+ for (const id of elementIds) {
6795
+ const blankBindings = decodeId(id, factory);
6796
+ if (!blankBindings) continue;
6797
+ for (const binding of blankBindings) if (isRdfLiteral(binding.blankTrg)) bindings.push({
6798
+ ...binding,
6799
+ propType: binding.blankTrgProp,
6800
+ propValue: binding.blankTrg
6801
+ });
6802
+ else bindings.push(binding);
6803
+ }
6804
+ return bindings;
6805
+ }
6806
+ /**
6807
+ * Answers {@link DataProvider.links} for encoded blank nodes without querying the endpoint.
6808
+ *
6809
+ * Only links where both ends are among the requested elements are returned, matching
6810
+ * the behavior of the endpoint-backed query.
6811
+ */
6812
+ function links(elementIds, factory) {
6813
+ const requested = new Set(elementIds);
6814
+ const bindings = [];
6815
+ for (const id of elementIds) {
6816
+ const blankBindings = decodeId(id, factory);
6817
+ if (!blankBindings) continue;
6818
+ for (const binding of blankBindings) {
6819
+ const { inst, blankSrc, blankSrcProp, blankTrg, blankTrgProp } = binding;
6820
+ if (blankSrc && blankSrcProp && requested.has(blankSrc.value)) bindings.push({
6821
+ source: blankSrc,
6822
+ type: blankSrcProp,
6823
+ target: inst
6824
+ });
6825
+ if (!isRdfLiteral(blankTrg) && requested.has(blankTrg.value)) bindings.push({
6826
+ source: inst,
6827
+ type: blankTrgProp,
6828
+ target: blankTrg
6829
+ });
6830
+ }
6831
+ }
6832
+ return bindings;
6833
+ }
6834
+ /**
6835
+ * Answers {@link DataProvider.connectedLinkStats} for an encoded blank node without
6836
+ * querying the endpoint.
6837
+ */
6838
+ function connectedLinkStats(elementId, factory) {
6839
+ const blankBindings = decodeId(elementId, factory);
6840
+ if (!blankBindings) return [];
6841
+ const stats = /* @__PURE__ */ new Map();
6842
+ function getOrCreate(linkTypeId) {
6843
+ let count = stats.get(linkTypeId);
6844
+ if (!count) {
6845
+ count = {
6846
+ id: linkTypeId,
6847
+ inCount: 0,
6848
+ outCount: 0
6849
+ };
6850
+ stats.set(linkTypeId, count);
6851
+ }
6852
+ return count;
6853
+ }
6854
+ for (const binding of blankBindings) {
6855
+ const { blankSrc, blankSrcProp, blankTrg, blankTrgProp } = binding;
6856
+ if (isRdfLiteral(blankTrg)) continue;
6857
+ if (blankSrc && blankSrcProp) {
6858
+ const incoming = getOrCreate(blankSrcProp.value);
6859
+ incoming.inCount += 1;
6860
+ }
6861
+ const outgoing = getOrCreate(blankTrgProp.value);
6862
+ outgoing.outCount += 1;
6863
+ }
6864
+ return Array.from(stats.values());
6865
+ }
6866
+ /**
6867
+ * Answers {@link DataProvider.lookup} for navigation from an encoded blank node without
6868
+ * querying the endpoint, or returns `undefined` when the lookup is not about one.
6869
+ */
6870
+ function lookup(params, factory) {
6871
+ let bindings;
6872
+ if (params.elementTypeId) return;
6873
+ else if (params.refElementId && params.refElementLinkId) bindings = getAllRelatedByLinkTypeElements(params.refElementId, params.refElementLinkId, params.linkDirection, factory);
6874
+ else if (params.refElementId) bindings = getAllRelatedElements(params.refElementId, factory);
6875
+ else return;
6876
+ if (bindings.length === 0) return;
6877
+ if (params.text) {
6878
+ const text = params.text.toLowerCase();
6879
+ bindings = bindings.filter((binding) => binding.inst.value.toLowerCase().includes(text));
6880
+ }
6881
+ return {
6882
+ head: { vars: [] },
6883
+ results: { bindings }
6884
+ };
6885
+ }
6886
+ /**
6887
+ * Answers the element type query for encoded blank nodes without querying the endpoint.
6888
+ */
6889
+ function collectElementTypes$1(elementIds, factory, result) {
6890
+ for (const id of elementIds) {
6891
+ const blankBindings = decodeId(id, factory);
6892
+ if (!blankBindings) continue;
6893
+ for (const binding of blankBindings) if (binding.class) {
6894
+ let types = result.get(id);
6895
+ if (!types) {
6896
+ types = /* @__PURE__ */ new Set();
6897
+ result.set(id, types);
6898
+ }
6899
+ types.add(binding.class.value);
6900
+ }
6901
+ }
6902
+ }
6903
+ /**
6904
+ * Answers navigation from an encoded blank node over a single link type.
6905
+ *
6906
+ * The direction is relative to the blank node: `in` collects the entities which point at it,
6907
+ * `out` the entities it points at.
6908
+ */
6909
+ function getAllRelatedByLinkTypeElements(refElementId, refElementLinkId, linkDirection, factory) {
6910
+ const blankBindings = decodeId(refElementId, factory);
6911
+ if (!blankBindings) return [];
6912
+ const linkType = factory.namedNode(refElementLinkId);
6913
+ const bindings = [];
6914
+ for (const binding of blankBindings) {
6915
+ const { blankSrc, blankSrcProp, blankTrg, blankTrgProp } = binding;
6916
+ if (linkDirection !== "out" && blankSrc && blankSrcProp && blankSrcProp.value === refElementLinkId) pushRelated(bindings, blankSrc, linkType, "in", factory);
6917
+ if (linkDirection !== "in" && blankTrgProp.value === refElementLinkId && !isRdfLiteral(blankTrg)) pushRelated(bindings, blankTrg, linkType, "out", factory);
6918
+ }
6919
+ return bindings;
6920
+ }
6921
+ /**
6922
+ * Answers navigation from an encoded blank node over every link type at once.
6923
+ *
6924
+ * Literal targets are left out: they are the blank node's own properties, not connections.
6925
+ */
6926
+ function getAllRelatedElements(refElementId, factory) {
6927
+ const blankBindings = decodeId(refElementId, factory);
6928
+ if (!blankBindings) return [];
6929
+ const bindings = [];
6930
+ for (const binding of blankBindings) {
6931
+ const { blankSrc, blankSrcProp, blankTrg, blankTrgProp } = binding;
6932
+ if (blankSrc && blankSrcProp) pushRelated(bindings, blankSrc, blankSrcProp, "in", factory);
6933
+ if (!isRdfLiteral(blankTrg)) pushRelated(bindings, blankTrg, blankTrgProp, "out", factory);
6934
+ }
6935
+ return bindings;
6936
+ }
6937
+ /**
6938
+ * Adds the element at the far end of a statement to the result: a named node as-is,
6939
+ * an encoded blank node expanded back into the statements which describe it.
6940
+ *
6941
+ * The link type and the direction are carried over so that the result describes the
6942
+ * connection the same way an endpoint-backed lookup does. A named node is returned bare,
6943
+ * without a label or a type: {@link SparqlDataProvider.lookup} fills those in, since only
6944
+ * the endpoint knows them.
6945
+ */
6946
+ function pushRelated(bindings, term, linkType, direction, factory) {
6947
+ const connection = {
6948
+ link: linkType,
6949
+ direction: factory.literal(direction)
6950
+ };
6951
+ const nested = isRdfIri(term) ? decodeId(term.value, factory) : void 0;
6952
+ if (nested) for (const binding of nested) bindings.push({
6953
+ ...binding,
6954
+ ...connection
6955
+ });
6956
+ else bindings.push({
6957
+ inst: term,
6958
+ ...connection
6959
+ });
6960
+ }
6961
+ //#endregion
6401
6962
  //#region src/data/sparql/requestChunking.ts
6402
6963
  function* chunkArray(items, measure, maxChunkSize) {
6403
6964
  if (!Number.isFinite(maxChunkSize)) {
@@ -6460,46 +7021,6 @@ function* chunkDirectedCrossProduct(from, to, measure, maxChunkSize, excludedTar
6460
7021
  }
6461
7022
  }
6462
7023
  //#endregion
6463
- //#region src/data/sparql/sparqlModels.ts
6464
- function mapSparqlResponseIntoRdfJs(response, factory) {
6465
- function mapSparqlBinding(binding) {
6466
- const mapped = {};
6467
- for (const key in binding) if (Object.prototype.hasOwnProperty.call(binding, key)) {
6468
- const term = binding[key];
6469
- switch (term.type) {
6470
- case "uri":
6471
- mapped[key] = factory.namedNode(term.value);
6472
- break;
6473
- case "bnode":
6474
- mapped[key] = factory.blankNode(term.value);
6475
- break;
6476
- case "literal":
6477
- case "typed-literal":
6478
- mapped[key] = factory.literal(term.value, term["xml:lang"] ? term["xml:lang"] : term.datatype ? factory.namedNode(term.datatype) : void 0);
6479
- break;
6480
- default: throw new Error(`Unexpected SPARQL term type: "${term.type}"`);
6481
- }
6482
- }
6483
- return mapped;
6484
- }
6485
- return {
6486
- ...response,
6487
- results: {
6488
- ...response.results,
6489
- bindings: response.results.bindings.map(mapSparqlBinding)
6490
- }
6491
- };
6492
- }
6493
- function isRdfIri(term) {
6494
- return Boolean(term && term.termType === "NamedNode");
6495
- }
6496
- function isRdfBlank(term) {
6497
- return Boolean(term && term.termType === "BlankNode");
6498
- }
6499
- function isRdfLiteral(term) {
6500
- return Boolean(term && term.termType === "Literal");
6501
- }
6502
- //#endregion
6503
7024
  //#region src/data/sparql/responseHandler.ts
6504
7025
  var TYPE_PREDICATE = "urn:reactodia:sparql:type";
6505
7026
  var LABEL_PREDICATE = "urn:reactodia:sparql:label";
@@ -6778,7 +7299,7 @@ function typeMatchesDomain(config, types) {
6778
7299
  function enrichElement(element, binding, labelPredicate) {
6779
7300
  if (!element) return;
6780
7301
  if (binding.label) appendProperty(element.properties, labelPredicate, binding.label);
6781
- if (binding.class && element.types.indexOf(binding.class.value) < 0) element.types.push(binding.class.value);
7302
+ if (isRdfIri(binding.class) && element.types.indexOf(binding.class.value) < 0) element.types.push(binding.class.value);
6782
7303
  if (binding.propType && binding.propValue && binding.propType.value !== LABEL_PREDICATE) appendProperty(element.properties, binding.propType.value, binding.propValue);
6783
7304
  }
6784
7305
  function appendLabel(container, newLabel) {
@@ -7034,7 +7555,7 @@ var SparqlDataProvider = class {
7034
7555
  options;
7035
7556
  settings;
7036
7557
  queryFunction;
7037
- acceptBlankNodes = false;
7558
+ acceptBlankNodes;
7038
7559
  chunkMeasure;
7039
7560
  linkByPredicate = /* @__PURE__ */ new Map();
7040
7561
  linkById = /* @__PURE__ */ new Map();
@@ -7049,6 +7570,7 @@ var SparqlDataProvider = class {
7049
7570
  this.options = options;
7050
7571
  this.settings = settings;
7051
7572
  this.queryFunction = queryFunction;
7573
+ this.acceptBlankNodes = Boolean(settings.acceptBlankNodes);
7052
7574
  const { chunk, queryMethod = "GET", prepareLabelPredicate, prepareImagePredicate } = this.options;
7053
7575
  if (chunk) {
7054
7576
  if (!Number.isSafeInteger(chunk.maxSize)) throw new Error("SparqlDataProviderOptions.chunk.maxSize should be a non-negative number");
@@ -7172,8 +7694,9 @@ var SparqlDataProvider = class {
7172
7694
  }
7173
7695
  async elements(params) {
7174
7696
  const { elementIds, signal } = params;
7697
+ const queriedIds = this.acceptBlankNodes ? elementIds.filter((iri) => !isEncodedBlank$1(iri)) : elementIds;
7175
7698
  const triples = [];
7176
- if (elementIds.length > 0) await this.queryChunked(elementIds, async (batch) => {
7699
+ if (queriedIds.length > 0) await this.queryChunked(queriedIds, async (batch) => {
7177
7700
  const ids = batch.map(escapeIri).map((id) => ` (${id})`).join(" ");
7178
7701
  const { defaultPrefix, dataLabelProperty, filterOnlyLanguages, elementInfoQuery } = this.settings;
7179
7702
  const query = defaultPrefix + resolveTemplate(elementInfoQuery, {
@@ -7187,15 +7710,18 @@ var SparqlDataProvider = class {
7187
7710
  for (const triple of response) triples.push(triple);
7188
7711
  });
7189
7712
  const types = this.queryManyElementTypes(this.settings.propertyConfigurations.length > 0 ? elementIds : [], signal);
7190
- const elementModels = getElementsInfo(triplesToElementBinding(triples), await types, this.propertyByPredicate, this.labelPredicate, this.openWorldProperties);
7191
- if (this.options.prepareLabels) await attachProperties(Array.from(elementModels.values()), this.options.prepareLabels, this.labelPredicate, signal);
7192
- if (this.options.prepareImages) await prepareElementImages(elementModels, this.options.prepareImages, this.imagePredicate, this.factory, signal);
7713
+ const bindings = triplesToElementBinding(triples);
7714
+ if (this.acceptBlankNodes) bindings.results.bindings.push(...elements(elementIds, this.factory));
7715
+ const elementModels = getElementsInfo(bindings, await types, this.propertyByPredicate, this.labelPredicate, this.openWorldProperties);
7716
+ if (this.options.prepareLabels) await attachProperties(Array.from(elementModels.values()), this.options.prepareLabels, this.labelPredicate, this.acceptBlankNodes, signal);
7717
+ if (this.options.prepareImages) await prepareElementImages(elementModels, this.options.prepareImages, this.imagePredicate, this.factory, this.acceptBlankNodes, signal);
7193
7718
  else if (this.options.imagePropertyUris && this.options.imagePropertyUris.length) await this.attachImages(elementModels, this.options.imagePropertyUris, signal);
7194
7719
  return elementModels;
7195
7720
  }
7196
7721
  async attachImages(elements, imagePropertyIris, signal) {
7197
7722
  const imageProperties = imagePropertyIris.map(escapeIri).map((id) => ` ( ${id} )`).join(" ");
7198
- await this.queryChunked(Array.from(elements.keys()), async (batch) => {
7723
+ const imageableIds = Array.from(elements.keys()).filter((iri) => !(this.acceptBlankNodes && isEncodedBlank$1(iri)));
7724
+ await this.queryChunked(imageableIds, async (batch) => {
7199
7725
  const ids = batch.map((id) => ` ( ${escapeIri(id)} )`).join(" ");
7200
7726
  const query = this.settings.defaultPrefix + `
7201
7727
  SELECT ?inst ?linkType ?image
@@ -7217,12 +7743,14 @@ var SparqlDataProvider = class {
7217
7743
  const { filterOnlyLanguages, linksInfoQuery } = this.settings;
7218
7744
  const propLanguageFilter = formatLanguageFilter("?propValue", filterOnlyLanguages);
7219
7745
  const linkConfigurations = this.formatLinkLinks();
7746
+ const queriedPrimary = this.acceptBlankNodes ? primary.filter((iri) => !isEncodedBlank$1(iri)) : primary;
7747
+ const queriedSecondary = this.acceptBlankNodes ? secondary.filter((iri) => !isEncodedBlank$1(iri)) : secondary;
7220
7748
  let bindings;
7221
- if (primary.length > 0 && secondary.length > 0) if (linksInfoQuery.includes("${ids}")) bindings = this.queryUndirectedLinks(primary, secondary, {
7749
+ if (queriedPrimary.length > 0 && queriedSecondary.length > 0) if (linksInfoQuery.includes("${ids}")) bindings = this.queryUndirectedLinks(queriedPrimary, queriedSecondary, {
7222
7750
  propLanguageFilter,
7223
7751
  linkConfigurations
7224
7752
  }, signal);
7225
- else bindings = this.queryDirectedLinks(primary, secondary, {
7753
+ else bindings = this.queryDirectedLinks(queriedPrimary, queriedSecondary, {
7226
7754
  propLanguageFilter,
7227
7755
  linkConfigurations
7228
7756
  }, signal);
@@ -7233,7 +7761,13 @@ var SparqlDataProvider = class {
7233
7761
  for (const iri of secondary) elementsForTypes.add(iri);
7234
7762
  }
7235
7763
  const types = this.queryManyElementTypes(elementsForTypes ? Array.from(elementsForTypes) : [], signal);
7236
- let linksInfo = getLinksInfo(await bindings, await types, this.linkByPredicate, this.openWorldLinks);
7764
+ let allBindings = await bindings;
7765
+ if (this.acceptBlankNodes) {
7766
+ const connected = new Set(primary);
7767
+ for (const iri of secondary) connected.add(iri);
7768
+ allBindings = [...allBindings, ...links(Array.from(connected), this.factory)];
7769
+ }
7770
+ let linksInfo = getLinksInfo(allBindings, await types, this.linkByPredicate, this.openWorldLinks);
7237
7771
  if (linkTypeIds) {
7238
7772
  const allowedLinkTypes = new Set(linkTypeIds);
7239
7773
  linksInfo = linksInfo.filter((link) => allowedLinkTypes.has(link.linkTypeId));
@@ -7272,6 +7806,7 @@ var SparqlDataProvider = class {
7272
7806
  }
7273
7807
  async connectedLinkStats(params) {
7274
7808
  const { elementId, inexactCount, signal } = params;
7809
+ if (this.acceptBlankNodes && isEncodedBlank$1(elementId)) return connectedLinkStats(elementId, this.factory);
7275
7810
  const { defaultPrefix, linkTypesOfQuery, linkTypesStatisticsQuery, filterTypePattern } = this.settings;
7276
7811
  const bindDirection = /\?direction\b/.test(linkTypesOfQuery);
7277
7812
  const elementIri = escapeIri(elementId);
@@ -7337,11 +7872,48 @@ var SparqlDataProvider = class {
7337
7872
  limit: baseParams.limit === void 0 ? 100 : baseParams.limit
7338
7873
  };
7339
7874
  const types = this.querySingleElementTypes(params.refElementId && this.settings.linkConfigurations.length > 0 ? params.refElementId : void 0, signal);
7340
- const filterQuery = this.createFilterQuery(params);
7341
- const linkedElements = getFilteredData(await this.executeSparqlSelect(filterQuery, { signal }), await types, this.linkByPredicate, this.labelPredicate, this.openWorldLinks);
7342
- if (this.options.prepareLabels) await attachProperties(linkedElements.map((linked) => linked.element), this.options.prepareLabels, this.labelPredicate, signal);
7875
+ let bindings;
7876
+ let navigatedFromBlank = false;
7877
+ if (this.acceptBlankNodes) {
7878
+ const fromBlank = lookup(params, this.factory);
7879
+ if (fromBlank) {
7880
+ bindings = fromBlank;
7881
+ navigatedFromBlank = true;
7882
+ } else bindings = await updateLookupResults(await this.executeSparqlSelect(this.createFilterQuery(params), { signal }), (blankQuery) => this.executeSparqlSelect(blankQuery, { signal }), this.settings, this.factory);
7883
+ } else {
7884
+ const filterQuery = this.createFilterQuery(params);
7885
+ bindings = await this.executeSparqlSelect(filterQuery, { signal });
7886
+ }
7887
+ const linkedElements = getFilteredData(bindings, await types, this.linkByPredicate, this.labelPredicate, this.openWorldLinks);
7888
+ if (navigatedFromBlank) return this.attachNeighborInfo(linkedElements, signal);
7889
+ if (this.options.prepareLabels) await attachProperties(linkedElements.map((linked) => linked.element), this.options.prepareLabels, this.labelPredicate, this.acceptBlankNodes, signal);
7343
7890
  return linkedElements;
7344
7891
  }
7892
+ /**
7893
+ * Fills in the data of the elements found by navigating from a blank node.
7894
+ *
7895
+ * The navigation is answered by decoding the blank node IRI, which knows only the term
7896
+ * at the far end of each statement, so everything else about a named neighbor - its
7897
+ * label, types and properties - still has to be read from the endpoint. Without this
7898
+ * the neighbor would render as its bare IRI.
7899
+ *
7900
+ * {@link elements} applies the {@link SparqlDataProviderOptions.prepareLabels} hook
7901
+ * itself, so the caller must not apply it again.
7902
+ */
7903
+ async attachNeighborInfo(items, signal) {
7904
+ if (items.length === 0) return [];
7905
+ const elementModels = await this.elements({
7906
+ elementIds: items.map((item) => item.element.id),
7907
+ signal
7908
+ });
7909
+ return items.map((item) => {
7910
+ const element = elementModels.get(item.element.id);
7911
+ return element ? {
7912
+ ...item,
7913
+ element
7914
+ } : item;
7915
+ });
7916
+ }
7345
7917
  createFilterQuery(params) {
7346
7918
  if (!params.refElementId && params.refElementLinkId) throw new Error("Cannot execute refElementLink filter without refElement");
7347
7919
  let outerProjection = "?inst ?class ?label";
@@ -7367,10 +7939,14 @@ var SparqlDataProvider = class {
7367
7939
  filterByType = this.settings.filterTypePattern.replace(/[?$]class\b/g, elementTypeIri);
7368
7940
  }
7369
7941
  const { defaultPrefix, dataLabelProperty, lookupQuery, filterOnlyLanguages, filterElementInfoPattern, fullTextSearch } = this.settings;
7370
- const queryElementInfo = resolveTemplate(filterElementInfoPattern, {
7942
+ let queryElementInfo = resolveTemplate(filterElementInfoPattern, {
7371
7943
  dataLabelProperty,
7372
7944
  labelLanguageFilter: formatLanguageFilter("?label", filterOnlyLanguages)
7373
7945
  });
7946
+ if (this.acceptBlankNodes) {
7947
+ outerProjection += ` ${BLANK_NODE_QUERY_PARAMETERS}`;
7948
+ queryElementInfo += BLANK_NODE_QUERY;
7949
+ }
7374
7950
  let filterByText = "";
7375
7951
  let orderBy = "";
7376
7952
  if (params.text) {
@@ -7498,7 +8074,12 @@ var SparqlDataProvider = class {
7498
8074
  if (elements.length === 0) return /* @__PURE__ */ new Map();
7499
8075
  const { filterTypePattern } = this.settings;
7500
8076
  const elementTypes = /* @__PURE__ */ new Map();
7501
- await this.queryChunked(elements, async (batch) => {
8077
+ let queriedElements = elements;
8078
+ if (this.acceptBlankNodes) {
8079
+ queriedElements = elements.filter((iri) => !isEncodedBlank$1(iri));
8080
+ collectElementTypes$1(elements, this.factory, elementTypes);
8081
+ }
8082
+ await this.queryChunked(queriedElements, async (batch) => {
7502
8083
  const query = resolveTemplate("SELECT ?inst ?class { VALUES(?inst) { ${ids} } ${filterTypePattern} }", {
7503
8084
  ids: batch.map((iri) => `(${escapeIri(iri)})`).join(" "),
7504
8085
  filterTypePattern
@@ -7521,15 +8102,16 @@ async function attachLabels(items, fetchLabels, signal) {
7521
8102
  if (itemLabels) item.label = itemLabels;
7522
8103
  }
7523
8104
  }
7524
- async function attachProperties(items, fetchProperties, propertyIri, signal) {
7525
- const properties = await fetchProperties(new Set(items.map((item) => item.id)), signal);
7526
- for (const item of items) {
8105
+ async function attachProperties(items, fetchProperties, propertyIri, acceptBlankNodes, signal) {
8106
+ const resolvable = acceptBlankNodes ? items.filter((item) => !isEncodedBlank(item.id)) : items;
8107
+ const properties = await fetchProperties(new Set(resolvable.map((item) => item.id)), signal);
8108
+ for (const item of resolvable) {
7527
8109
  const itemValues = properties.get(item.id);
7528
8110
  if (itemValues) item.properties[propertyIri] = itemValues;
7529
8111
  }
7530
8112
  }
7531
- function prepareElementImages(elements, fetchImages, imagePropertyIri, factory, signal) {
7532
- return fetchImages(elements.values(), signal).then((images) => {
8113
+ function prepareElementImages(elements, fetchImages, imagePropertyIri, factory, acceptBlankNodes, signal) {
8114
+ return fetchImages(acceptBlankNodes ? Array.from(elements.values()).filter((element) => !isEncodedBlank(element.id)) : elements.values(), signal).then((images) => {
7533
8115
  for (const [iri, image] of images) {
7534
8116
  const entity = elements.get(iri);
7535
8117
  if (entity) appendProperty(entity.properties, imagePropertyIri, factory.namedNode(image));