@oml/owl 0.19.3 → 0.20.1
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/out/index.d.ts +3 -0
- package/out/index.js +3 -0
- package/out/index.js.map +1 -1
- package/out/owl/owl-abox.d.ts +16 -5
- package/out/owl/owl-abox.js +365 -188
- package/out/owl/owl-abox.js.map +1 -1
- package/out/owl/owl-imports.js +4 -2
- package/out/owl/owl-imports.js.map +1 -1
- package/out/owl/owl-interfaces.d.ts +26 -1
- package/out/owl/owl-mapper.d.ts +2 -0
- package/out/owl/owl-mapper.js +77 -38
- package/out/owl/owl-mapper.js.map +1 -1
- package/out/owl/owl-service.d.ts +4 -0
- package/out/owl/owl-service.js +139 -44
- package/out/owl/owl-service.js.map +1 -1
- package/out/owl/owl-shacl.d.ts +8 -9
- package/out/owl/owl-shacl.js +43 -117
- package/out/owl/owl-shacl.js.map +1 -1
- package/out/owl/owl-sparql-engine.d.ts +6 -0
- package/out/owl/owl-sparql-engine.js +11 -0
- package/out/owl/owl-sparql-engine.js.map +1 -0
- package/out/owl/owl-sparql-oxigraph.d.ts +34 -0
- package/out/owl/owl-sparql-oxigraph.js +436 -0
- package/out/owl/owl-sparql-oxigraph.js.map +1 -0
- package/out/owl/owl-sparql.d.ts +0 -44
- package/out/owl/owl-sparql.js +0 -320
- package/out/owl/owl-sparql.js.map +1 -1
- package/out/owl/owl-store-lean.d.ts +29 -0
- package/out/owl/owl-store-lean.js +445 -0
- package/out/owl/owl-store-lean.js.map +1 -0
- package/out/owl/owl-store.d.ts +11 -1
- package/out/owl/owl-store.js +80 -6
- package/out/owl/owl-store.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/owl/owl-abox.ts +401 -200
- package/src/owl/owl-imports.ts +4 -2
- package/src/owl/owl-interfaces.ts +28 -1
- package/src/owl/owl-mapper.ts +79 -41
- package/src/owl/owl-service.ts +149 -49
- package/src/owl/owl-shacl.ts +55 -132
- package/src/owl/owl-sparql-engine.ts +34 -0
- package/src/owl/owl-sparql-oxigraph.ts +527 -0
- package/src/owl/owl-sparql.ts +3 -366
- package/src/owl/owl-store-lean.ts +438 -0
- package/src/owl/owl-store.ts +86 -7
package/src/owl/owl-imports.ts
CHANGED
|
@@ -68,9 +68,11 @@ export class ImportGraph {
|
|
|
68
68
|
dependentsOf(modelUri: string): string[] {
|
|
69
69
|
const visited = new Set<string>();
|
|
70
70
|
const worklist = [modelUri];
|
|
71
|
+
let cursor = 0;
|
|
71
72
|
|
|
72
|
-
while (worklist.length
|
|
73
|
-
const current = worklist
|
|
73
|
+
while (cursor < worklist.length) {
|
|
74
|
+
const current = worklist[cursor];
|
|
75
|
+
cursor += 1;
|
|
74
76
|
if (!current || visited.has(current)) continue;
|
|
75
77
|
visited.add(current);
|
|
76
78
|
const importers = this.importedBy.get(current) ?? new Set<string>();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
|
-
import type { NamedNode, Quad, Store } from 'n3';
|
|
3
|
+
import type { NamedNode, Quad, Store, Term } from 'n3';
|
|
4
4
|
import type * as RDF from '@rdfjs/types';
|
|
5
5
|
import type { Ontology } from '@oml/language';
|
|
6
6
|
import type { ChainingResult } from './owl-abox.js';
|
|
@@ -11,14 +11,26 @@ export interface OwlMapper {
|
|
|
11
11
|
toQuads(ontology: Ontology): Quad[];
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Visits a model's stored facts as (subject, predicate, object, key) — `key` is the dedup key
|
|
16
|
+
* `${subject.id}|${predicate.id}|${object.id}`. Lets the ABox chainer read scope facts without the
|
|
17
|
+
* store materializing per-model `Quad` arrays (the lean store yields them straight from its columns).
|
|
18
|
+
*/
|
|
19
|
+
export type ScopeFactVisitor = (subject: Term, predicate: NamedNode, object: Term, key: string) => void;
|
|
20
|
+
|
|
14
21
|
export interface OwlStore {
|
|
15
22
|
loadModel(modelUri: string, quads: Quad[]): void;
|
|
16
23
|
loadEntailments(modelUri: string, quads: Quad[]): void;
|
|
24
|
+
addEntailment(modelUri: string, quad: Quad): void;
|
|
25
|
+
removeEntailment(modelUri: string, quad: Quad): void;
|
|
17
26
|
applyModelDelta(modelUri: string, quads: Quad[], retracted: Quad[], asserted: Quad[]): void;
|
|
18
27
|
retractModel(modelUri: string): void;
|
|
19
28
|
diffModel(modelUri: string, newQuads: Quad[]): ModelDiff;
|
|
20
29
|
clearEntailments(modelUri: string): void;
|
|
21
30
|
graphs(modelUri: string): ModelGraphs;
|
|
31
|
+
forEachAssertedFact(modelUri: string, visit: ScopeFactVisitor): void;
|
|
32
|
+
forEachEntailedFact(modelUri: string, visit: ScopeFactVisitor): void;
|
|
33
|
+
getModelDataGen(modelUri: string): number;
|
|
22
34
|
getStore(): Store;
|
|
23
35
|
}
|
|
24
36
|
|
|
@@ -115,6 +127,21 @@ export interface OwlSparqlService {
|
|
|
115
127
|
query(modelUri: string, sparql: string): Promise<OwlQueryResult>;
|
|
116
128
|
construct(modelUri: string, sparql: string): Promise<OwlConstructResult>;
|
|
117
129
|
ask(modelUri: string, sparql: string): Promise<OwlAskResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Make `quads` visible to queries run inside `action`, then drop them. Used to chain SHACL
|
|
132
|
+
* `sh:rule` CONSTRUCT outputs (each rule's quads feed the next). Each engine reflects the
|
|
133
|
+
* quads wherever its queries read: Comunica into the live N3 store, Oxigraph into its mirror.
|
|
134
|
+
*/
|
|
135
|
+
withMaterializedQuads<T>(modelUri: string, quads: RDF.Quad[], action: () => Promise<T>): Promise<T>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A swappable SPARQL engine: an OwlSparqlService plus the construction-time wiring the
|
|
140
|
+
* ReasoningService needs. Concrete engines (Comunica, Oxigraph) implement this so they are
|
|
141
|
+
* fully interchangeable behind the factory in owl-sparql-engine.ts.
|
|
142
|
+
*/
|
|
143
|
+
export interface OwlSparqlEngine extends OwlSparqlService {
|
|
144
|
+
setOntologyIriResolver(resolver: (modelUri: string) => string): void;
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
export interface OwlShaclValidationIssue {
|
package/src/owl/owl-mapper.ts
CHANGED
|
@@ -48,7 +48,6 @@ import {
|
|
|
48
48
|
isAnonymousConceptInstance,
|
|
49
49
|
isAnonymousRelationInstance,
|
|
50
50
|
isAspect,
|
|
51
|
-
isBooleanLiteral,
|
|
52
51
|
isBuiltInPredicate,
|
|
53
52
|
isBuiltIn,
|
|
54
53
|
isConcept,
|
|
@@ -67,7 +66,6 @@ import {
|
|
|
67
66
|
isPropertyValueRestrictionAxiom,
|
|
68
67
|
isQuantity,
|
|
69
68
|
isQuantityProperty,
|
|
70
|
-
isQuotedLiteral,
|
|
71
69
|
isRelation,
|
|
72
70
|
isRelationEntityPredicate,
|
|
73
71
|
isRelationEntity,
|
|
@@ -284,9 +282,20 @@ const BUILT_IN_ONTOLOGIES = new Set([
|
|
|
284
282
|
'http://www.w3.org/2003/11/swrlb',
|
|
285
283
|
]);
|
|
286
284
|
|
|
287
|
-
const { blankNode,
|
|
285
|
+
const { blankNode, literal, quad } = DataFactory;
|
|
288
286
|
type TermNode = ReturnType<typeof namedNode> | ReturnType<typeof blankNode>;
|
|
289
287
|
|
|
288
|
+
const namedNodeCache = new Map<string, ReturnType<typeof DataFactory.namedNode>>();
|
|
289
|
+
|
|
290
|
+
function namedNode(iri: string): ReturnType<typeof DataFactory.namedNode> {
|
|
291
|
+
let node = namedNodeCache.get(iri);
|
|
292
|
+
if (!node) {
|
|
293
|
+
node = DataFactory.namedNode(iri);
|
|
294
|
+
namedNodeCache.set(iri, node);
|
|
295
|
+
}
|
|
296
|
+
return node;
|
|
297
|
+
}
|
|
298
|
+
|
|
290
299
|
function fnv1a(input: string): string {
|
|
291
300
|
let hash = 2166136261;
|
|
292
301
|
for (let i = 0; i < input.length; i += 1) {
|
|
@@ -354,6 +363,9 @@ export class Oml2OwlMapper {
|
|
|
354
363
|
});
|
|
355
364
|
}
|
|
356
365
|
|
|
366
|
+
if (isDescription(ontology)) {
|
|
367
|
+
return triples;
|
|
368
|
+
}
|
|
357
369
|
return this.deduplicateQuads(triples);
|
|
358
370
|
}
|
|
359
371
|
|
|
@@ -593,12 +605,14 @@ export class Oml2OwlMapper {
|
|
|
593
605
|
}
|
|
594
606
|
|
|
595
607
|
private mapConceptInstance(instance: ConceptInstance, ctx: MappingContext, triples: Quad[]): void {
|
|
596
|
-
const iri = this.elementIri(instance, ctx);
|
|
608
|
+
const iri = (instance as any).ref ? this.elementIri(instance, ctx) : this.ownedElementIri(instance, ctx);
|
|
597
609
|
if (!iri) return;
|
|
598
610
|
const node = namedNode(iri);
|
|
599
611
|
const isDefinition = !(instance as any).ref;
|
|
600
612
|
|
|
601
|
-
instance.ownedTypes?.forEach((typeAssertion) =>
|
|
613
|
+
instance.ownedTypes?.forEach((typeAssertion) => {
|
|
614
|
+
this.mapTypeAssertion(node, typeAssertion, ctx, triples);
|
|
615
|
+
});
|
|
602
616
|
if (isDefinition) {
|
|
603
617
|
triples.push(this.quadWithGraph(node, namedNode(OML.type), namedNode(OML.ConceptInstance)));
|
|
604
618
|
}
|
|
@@ -607,7 +621,7 @@ export class Oml2OwlMapper {
|
|
|
607
621
|
}
|
|
608
622
|
|
|
609
623
|
private mapRelationInstance(instance: RelationInstance, ctx: MappingContext, triples: Quad[]): void {
|
|
610
|
-
const iri = this.elementIri(instance, ctx);
|
|
624
|
+
const iri = (instance as any).ref ? this.elementIri(instance, ctx) : this.ownedElementIri(instance, ctx);
|
|
611
625
|
if (!iri) return;
|
|
612
626
|
const node = namedNode(iri);
|
|
613
627
|
const isDefinition = !(instance as any).ref;
|
|
@@ -873,14 +887,16 @@ export class Oml2OwlMapper {
|
|
|
873
887
|
const propertyIri = this.resolveIri(assertion.property, ctx);
|
|
874
888
|
if (!propertyIri) return;
|
|
875
889
|
const propertyNode = namedNode(propertyIri);
|
|
876
|
-
|
|
877
|
-
const propertyRef = assertion.property?.ref as any;
|
|
878
|
-
const isQuantityProp = propertyRef && isQuantityProperty(propertyRef);
|
|
890
|
+
let isQuantityProp: boolean | undefined;
|
|
879
891
|
|
|
880
892
|
assertion.literalValues?.forEach((lit, index) => {
|
|
881
893
|
const isNumeric = isIntegerLiteral(lit) || isDecimalLiteral(lit) || isDoubleLiteral(lit);
|
|
882
894
|
const unitRef = (lit as any)?.unit;
|
|
883
895
|
const explicitUnitIri = unitRef ? this.resolveIri(unitRef, ctx) : undefined;
|
|
896
|
+
if (isNumeric && explicitUnitIri === undefined && isQuantityProp === undefined) {
|
|
897
|
+
const propertyRef = assertion.property?.ref as any;
|
|
898
|
+
isQuantityProp = propertyRef && isQuantityProperty(propertyRef);
|
|
899
|
+
}
|
|
884
900
|
// Always wrap numeric literals on quantity properties in a QuantityLiteral
|
|
885
901
|
// (the property's range is oml:QuantityLiteral). Plain literals only when
|
|
886
902
|
// the property isn't a quantity property.
|
|
@@ -915,7 +931,6 @@ export class Oml2OwlMapper {
|
|
|
915
931
|
triples.push(this.quadWithGraph(subject, propertyNode, valueNode));
|
|
916
932
|
this.mapAnonymousInstance(contained, valueNode, ctx, triples, subject);
|
|
917
933
|
});
|
|
918
|
-
|
|
919
934
|
}
|
|
920
935
|
|
|
921
936
|
private mapAnnotations(
|
|
@@ -1450,9 +1465,14 @@ export class Oml2OwlMapper {
|
|
|
1450
1465
|
map[prefix] = ns;
|
|
1451
1466
|
}
|
|
1452
1467
|
ontology.ownedImports?.forEach((imp: Import) => {
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
1468
|
+
const refText = (imp.imported as any)?.$refText ?? (imp.imported as any)?.refText;
|
|
1469
|
+
let importedNs = refText ? this.resolveFromRefText(refText, { ...this.emptyContext(), prefixes: map }) : undefined;
|
|
1470
|
+
let importedPrefix = imp.prefix;
|
|
1471
|
+
if (!importedNs || !importedPrefix) {
|
|
1472
|
+
const imported = imp.imported?.ref as Ontology | undefined;
|
|
1473
|
+
importedNs = importedNs ?? (imported ? this.normalizeNamespace((imported as any).namespace ?? '') : undefined);
|
|
1474
|
+
importedPrefix = importedPrefix ?? (imported as any)?.prefix;
|
|
1475
|
+
}
|
|
1456
1476
|
if (importedPrefix && importedNs) {
|
|
1457
1477
|
map[importedPrefix] = importedNs;
|
|
1458
1478
|
}
|
|
@@ -1469,13 +1489,13 @@ export class Oml2OwlMapper {
|
|
|
1469
1489
|
}
|
|
1470
1490
|
|
|
1471
1491
|
private importedOntologyIri(imp: Import, ctx: MappingContext): string | undefined {
|
|
1472
|
-
if (imp.imported?.ref && isOntology(imp.imported.ref)) {
|
|
1473
|
-
return this.normalizeNamespace((imp.imported.ref as any).namespace ?? '').replace(/[\/#]+$/, '');
|
|
1474
|
-
}
|
|
1475
1492
|
const refText = (imp.imported as any)?.$refText ?? (imp.imported as any)?.refText;
|
|
1476
1493
|
if (refText) {
|
|
1477
1494
|
return this.resolveFromRefText(refText, ctx)?.replace(/[\/#]+$/, '');
|
|
1478
1495
|
}
|
|
1496
|
+
if (imp.imported?.ref && isOntology(imp.imported.ref)) {
|
|
1497
|
+
return this.normalizeNamespace((imp.imported.ref as any).namespace ?? '').replace(/[\/#]+$/, '');
|
|
1498
|
+
}
|
|
1479
1499
|
return undefined;
|
|
1480
1500
|
}
|
|
1481
1501
|
|
|
@@ -1505,6 +1525,12 @@ export class Oml2OwlMapper {
|
|
|
1505
1525
|
return `${namespace}${name.replace(/^\^/, '')}`;
|
|
1506
1526
|
}
|
|
1507
1527
|
|
|
1528
|
+
private ownedElementIri(element: { name?: string }, ctx: MappingContext): string | undefined {
|
|
1529
|
+
const name = (element as any)?.name;
|
|
1530
|
+
if (!name) return undefined;
|
|
1531
|
+
return `${ctx.namespace}${name.replace(/^\^/, '')}`;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1508
1534
|
private getOntology(element: any): Ontology | undefined {
|
|
1509
1535
|
let current: any = element;
|
|
1510
1536
|
while (current && !isOntology(current)) {
|
|
@@ -1518,19 +1544,28 @@ export class Oml2OwlMapper {
|
|
|
1518
1544
|
if (typeof ref === 'string') {
|
|
1519
1545
|
return this.resolveFromRefText(ref, ctx);
|
|
1520
1546
|
}
|
|
1547
|
+
const refText = ref.$refText ?? ref.refText;
|
|
1548
|
+
if (refText) {
|
|
1549
|
+
return this.resolveFromRefText(refText, ctx);
|
|
1550
|
+
}
|
|
1521
1551
|
if (ref.ref && (ref.ref as any).name) {
|
|
1522
1552
|
return this.elementIri(ref.ref as any, ctx);
|
|
1523
1553
|
}
|
|
1524
1554
|
if (ref.name) {
|
|
1525
1555
|
return this.elementIri(ref, ctx);
|
|
1526
1556
|
}
|
|
1527
|
-
const refText = ref.$refText ?? ref.refText;
|
|
1528
|
-
if (refText) {
|
|
1529
|
-
return this.resolveFromRefText(refText, ctx);
|
|
1530
|
-
}
|
|
1531
1557
|
return undefined;
|
|
1532
1558
|
}
|
|
1533
1559
|
|
|
1560
|
+
private emptyContext(): MappingContext {
|
|
1561
|
+
return {
|
|
1562
|
+
ontologyIri: '',
|
|
1563
|
+
namespace: '',
|
|
1564
|
+
prefix: '',
|
|
1565
|
+
prefixes: {},
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1534
1569
|
private resolveFromRefText(refText: string, ctx: MappingContext): string | undefined {
|
|
1535
1570
|
if (refText.startsWith('<') && refText.endsWith('>')) {
|
|
1536
1571
|
return refText.slice(1, -1);
|
|
@@ -1543,6 +1578,9 @@ export class Oml2OwlMapper {
|
|
|
1543
1578
|
return `${ns}${local}`;
|
|
1544
1579
|
}
|
|
1545
1580
|
}
|
|
1581
|
+
if (!refText.includes(':') && ctx.namespace) {
|
|
1582
|
+
return `${ctx.namespace}${refText.replace(/^\^/, '')}`;
|
|
1583
|
+
}
|
|
1546
1584
|
return undefined;
|
|
1547
1585
|
}
|
|
1548
1586
|
|
|
@@ -1561,29 +1599,29 @@ export class Oml2OwlMapper {
|
|
|
1561
1599
|
}
|
|
1562
1600
|
|
|
1563
1601
|
private toLiteral(value: Literal, ctx: MappingContext): Quad_Object | undefined {
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
return literal(
|
|
1602
|
+
switch ((value as any).$type) {
|
|
1603
|
+
case 'BooleanLiteral':
|
|
1604
|
+
return literal(String(value.value), namedNode(XSD.boolean));
|
|
1605
|
+
case 'IntegerLiteral':
|
|
1606
|
+
return literal(String(value.value), namedNode(XSD.integer));
|
|
1607
|
+
case 'DecimalLiteral':
|
|
1608
|
+
return literal(String(value.value), namedNode(XSD.decimal));
|
|
1609
|
+
case 'DoubleLiteral':
|
|
1610
|
+
return literal(formatDoubleLiteralValue(value.value), namedNode(XSD.double));
|
|
1611
|
+
case 'QuotedLiteral': {
|
|
1612
|
+
const quoted = value as any;
|
|
1613
|
+
if (quoted.type) {
|
|
1614
|
+
const dt = this.resolveIri(quoted.type, ctx);
|
|
1615
|
+
return literal(quoted.value, dt ? namedNode(dt) : undefined);
|
|
1616
|
+
}
|
|
1617
|
+
if (quoted.langTag) {
|
|
1618
|
+
return literal(quoted.value, quoted.langTag);
|
|
1619
|
+
}
|
|
1620
|
+
return literal(quoted.value);
|
|
1583
1621
|
}
|
|
1584
|
-
|
|
1622
|
+
default:
|
|
1623
|
+
return undefined;
|
|
1585
1624
|
}
|
|
1586
|
-
return undefined;
|
|
1587
1625
|
}
|
|
1588
1626
|
|
|
1589
1627
|
private toFirstUpper(name: string): string {
|
package/src/owl/owl-service.ts
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { DocumentState, URI, type LangiumDocument } from 'langium';
|
|
4
4
|
import { DataFactory, type NamedNode, type Quad } from 'n3';
|
|
5
|
+
import type * as RDF from '@rdfjs/types';
|
|
5
6
|
import { OmlIndex, getOntologyModelIndex, isDescription, isDescriptionBundle, isOntology, isVocabulary, isVocabularyBundle, type Import, type OmlServices, type Ontology } from '@oml/language';
|
|
6
7
|
import { Oml2OwlMapper } from './owl-mapper.js';
|
|
7
8
|
import { VocabularyBundleClosureBuilder } from './owl-closure.js';
|
|
8
|
-
import {
|
|
9
|
+
import { LeanReasoningStore } from './owl-store-lean.js';
|
|
9
10
|
import { ABoxChainer, ABoxEntailmentCache, type ChainingResult } from './owl-abox.js';
|
|
10
11
|
import { ImportGraph } from './owl-imports.js';
|
|
11
|
-
import {
|
|
12
|
+
import { createSparqlEngine, resolveSparqlEngineKind } from './owl-sparql-engine.js';
|
|
12
13
|
import { TBoxChainer, TBoxIndexBuilder, TBoxIndexCache } from './owl-tbox.js';
|
|
13
14
|
import type {
|
|
14
15
|
OwlABoxChainer,
|
|
@@ -38,6 +39,7 @@ export class ReasoningService {
|
|
|
38
39
|
private readonly preparedModelAliases = new Map<string, string>();
|
|
39
40
|
private readonly activeDocuments = new Set<string>();
|
|
40
41
|
private readonly modelLoadInFlight = new Map<string, Promise<void>>();
|
|
42
|
+
private readonly importModelUriResolutionCache = new Map<string, string | undefined>();
|
|
41
43
|
private readonly semanticChangeListeners = new Set<(modelUris: string[]) => void>();
|
|
42
44
|
private readonly pendingSemanticChangedUris = new Set<string>();
|
|
43
45
|
private semanticChangeSuppressionDepth = 0;
|
|
@@ -54,8 +56,9 @@ export class ReasoningService {
|
|
|
54
56
|
this.aboxChainer = resolved.aboxChainer;
|
|
55
57
|
this.aboxEntailmentCache = resolved.aboxEntailmentCache;
|
|
56
58
|
this.sparqlService = resolved.createSparqlService(this);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const sparqlWithResolver = this.sparqlService as { setOntologyIriResolver?: (resolver: (modelUri: string) => string) => void };
|
|
60
|
+
if (typeof sparqlWithResolver.setOntologyIriResolver === 'function') {
|
|
61
|
+
sparqlWithResolver.setOntologyIriResolver((modelUri) => this.resolveOntologyIriForModelUri(modelUri));
|
|
59
62
|
}
|
|
60
63
|
this.ontologyModelIndex = getOntologyModelIndex(services.shared);
|
|
61
64
|
|
|
@@ -69,6 +72,9 @@ export class ReasoningService {
|
|
|
69
72
|
this.notifySemanticChanged(uris);
|
|
70
73
|
}
|
|
71
74
|
});
|
|
75
|
+
services.shared.workspace.DocumentBuilder.onUpdate(() => {
|
|
76
|
+
this.importModelUriResolutionCache.clear();
|
|
77
|
+
});
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
onDocumentValidated(document: LangiumDocument): void {
|
|
@@ -83,20 +89,24 @@ export class ReasoningService {
|
|
|
83
89
|
|
|
84
90
|
const quads = this.mapOntologyToQuads(ontology as Ontology);
|
|
85
91
|
const wasModelLoaded = this.isModelLoaded(modelUri);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
if (wasModelLoaded) {
|
|
93
|
+
const diff = this.reasoningStore.diffModel(modelUri, quads);
|
|
94
|
+
if (diff.isEmpty) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.reasoningStore.retractModel(modelUri);
|
|
89
98
|
}
|
|
90
99
|
this.tboxIndexCache.invalidateMerged(modelUri);
|
|
91
|
-
this.reasoningStore.retractModel(modelUri);
|
|
92
100
|
this.reasoningStore.loadModel(modelUri, quads);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
if (wasModelLoaded) {
|
|
102
|
+
const impactedByLoad = [modelUri, ...this.importGraph.dependentsOf(modelUri)];
|
|
103
|
+
for (const uri of impactedByLoad) {
|
|
104
|
+
this.sparqlService.invalidateFilteredStore(uri);
|
|
105
|
+
if (this.semanticChangeSuppressionDepth > 0) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
this.pendingSemanticChangedUris.add(uri);
|
|
98
109
|
}
|
|
99
|
-
this.pendingSemanticChangedUris.add(uri);
|
|
100
110
|
}
|
|
101
111
|
if (isVocabulary(ontology)) {
|
|
102
112
|
this.tboxChainer.chain(modelUri);
|
|
@@ -158,7 +168,16 @@ export class ReasoningService {
|
|
|
158
168
|
let merged = this.tboxIndexCache.getMerged(chainedModelUri);
|
|
159
169
|
if (!merged || this.tboxIndexCache.isMergedDirty(chainedModelUri)) {
|
|
160
170
|
merged = this.tboxIndexBuilder.buildMerged(chainedModelUri);
|
|
161
|
-
|
|
171
|
+
// Retain the merged TBox index only for active (open/edited) documents — matching
|
|
172
|
+
// the policy in onDocumentValidated. For the rest it is just a transient input to
|
|
173
|
+
// this chain pass; caching it for all models keeps thousands of largely-duplicate
|
|
174
|
+
// merged vocabulary TBoxes resident. getMerged's only consumer is right here and it
|
|
175
|
+
// rebuilds on demand, so skipping the cache is correctness-neutral and costs one
|
|
176
|
+
// buildMerged on the next chain of that model (cheap — the per-vocabulary own
|
|
177
|
+
// indexes it merges stay cached).
|
|
178
|
+
if (this.isActiveDocument(chainedModelUri)) {
|
|
179
|
+
this.tboxIndexCache.setMerged(chainedModelUri, merged);
|
|
180
|
+
}
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
const readScope = this.resolveLoadedDependencyOrder(chainedModelUri);
|
|
@@ -236,6 +255,9 @@ export class ReasoningService {
|
|
|
236
255
|
ask: (modelUri: string, sparql: string) => {
|
|
237
256
|
return this.sparqlService.ask(this.resolveQueryModelUri(modelUri), sparql);
|
|
238
257
|
},
|
|
258
|
+
withMaterializedQuads: <T>(modelUri: string, quads: RDF.Quad[], action: () => Promise<T>) => {
|
|
259
|
+
return this.sparqlService.withMaterializedQuads(this.resolveQueryModelUri(modelUri), quads, action);
|
|
260
|
+
},
|
|
239
261
|
};
|
|
240
262
|
}
|
|
241
263
|
|
|
@@ -312,28 +334,54 @@ export class ReasoningService {
|
|
|
312
334
|
}
|
|
313
335
|
|
|
314
336
|
private resolveImportedModelUri(ownedImport: Import, prefixes: Map<string, string>, referencingModelUri?: string): string | undefined {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
if (
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
const importedDocumentUri = (ownedImport.imported.ref as any)?.$document?.uri?.toString();
|
|
322
|
-
if (importedDocumentUri) {
|
|
323
|
-
return importedDocumentUri;
|
|
337
|
+
const refText = this.getImportRefText(ownedImport);
|
|
338
|
+
if (refText) {
|
|
339
|
+
const ontologyIdentifier = this.resolveFromRefText(refText, prefixes);
|
|
340
|
+
if (!ontologyIdentifier || BUILT_IN_ONTOLOGIES.has(ontologyIdentifier)) {
|
|
341
|
+
return undefined;
|
|
324
342
|
}
|
|
325
|
-
return this.
|
|
343
|
+
return this.resolveImportedOntologyIdentifier(ontologyIdentifier, referencingModelUri);
|
|
326
344
|
}
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
|
|
346
|
+
const imported = ownedImport.imported?.ref;
|
|
347
|
+
if (!imported || !isOntology(imported)) {
|
|
329
348
|
return undefined;
|
|
330
349
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
350
|
+
const importedNamespace = this.normalizeNamespace((imported as any).namespace ?? '');
|
|
351
|
+
const viaIndex = this.ontologyModelIndex.resolveModelUri(importedNamespace, referencingModelUri);
|
|
352
|
+
if (viaIndex) {
|
|
353
|
+
return viaIndex;
|
|
354
|
+
}
|
|
355
|
+
const importedDocumentUri = (imported as any)?.$document?.uri?.toString();
|
|
356
|
+
if (importedDocumentUri) {
|
|
357
|
+
return importedDocumentUri;
|
|
358
|
+
}
|
|
359
|
+
return this.resolveModelUriFromOntologyIdentifier(importedNamespace);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private resolveImportedOntologyIdentifier(identifier: string, referencingModelUri?: string): string | undefined {
|
|
363
|
+
const globalCacheKey = `|${identifier}`;
|
|
364
|
+
if (this.importModelUriResolutionCache.has(globalCacheKey)) {
|
|
365
|
+
const resolved = this.importModelUriResolutionCache.get(globalCacheKey);
|
|
366
|
+
if (resolved) {
|
|
367
|
+
return resolved;
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
const globallyResolved = this.resolveModelUriFromOntologyIdentifier(identifier);
|
|
371
|
+
this.importModelUriResolutionCache.set(globalCacheKey, globallyResolved);
|
|
372
|
+
if (globallyResolved) {
|
|
373
|
+
return globallyResolved;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const cacheKey = `${referencingModelUri ?? ''}|${identifier}`;
|
|
378
|
+
if (this.importModelUriResolutionCache.has(cacheKey)) {
|
|
379
|
+
return this.importModelUriResolutionCache.get(cacheKey);
|
|
334
380
|
}
|
|
335
|
-
|
|
336
|
-
?? this.resolveModelUriFromOntologyIdentifier(
|
|
381
|
+
const resolved = this.ontologyModelIndex.resolveModelUri(identifier, referencingModelUri)
|
|
382
|
+
?? this.resolveModelUriFromOntologyIdentifier(identifier);
|
|
383
|
+
this.importModelUriResolutionCache.set(cacheKey, resolved);
|
|
384
|
+
return resolved;
|
|
337
385
|
}
|
|
338
386
|
|
|
339
387
|
private buildPrefixMap(ontology: Ontology): Map<string, string> {
|
|
@@ -344,9 +392,12 @@ export class ReasoningService {
|
|
|
344
392
|
map.set(ontologyPrefix, ontologyNamespace);
|
|
345
393
|
}
|
|
346
394
|
for (const ownedImport of ontology.ownedImports ?? []) {
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
395
|
+
const importedPrefix = ownedImport.prefix;
|
|
396
|
+
if (!importedPrefix) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const refText = this.getImportRefText(ownedImport);
|
|
400
|
+
const importedNamespace = refText ? this.resolveFromRefText(refText, map) : undefined;
|
|
350
401
|
if (importedPrefix && importedNamespace) {
|
|
351
402
|
map.set(importedPrefix, importedNamespace);
|
|
352
403
|
}
|
|
@@ -354,6 +405,11 @@ export class ReasoningService {
|
|
|
354
405
|
return map;
|
|
355
406
|
}
|
|
356
407
|
|
|
408
|
+
private getImportRefText(ownedImport: Import): string | undefined {
|
|
409
|
+
const imported = ownedImport.imported as any;
|
|
410
|
+
return imported?.$refText ?? imported?.refText;
|
|
411
|
+
}
|
|
412
|
+
|
|
357
413
|
private resolveFromRefText(refText: string, prefixes: Map<string, string>): string | undefined {
|
|
358
414
|
if (refText.startsWith('<') && refText.endsWith('>')) {
|
|
359
415
|
return refText.slice(1, -1);
|
|
@@ -418,8 +474,12 @@ export class ReasoningService {
|
|
|
418
474
|
}
|
|
419
475
|
}
|
|
420
476
|
await this.withSemanticChangeSuppressed(async () => {
|
|
421
|
-
await this.services.shared.workspace.DocumentBuilder.build([document], { validation:
|
|
477
|
+
await this.services.shared.workspace.DocumentBuilder.build([document], { validation: false, eagerLinking: false });
|
|
422
478
|
});
|
|
479
|
+
const root = document.parseResult.value;
|
|
480
|
+
if (isOntology(root)) {
|
|
481
|
+
this.onDocumentValidated(document);
|
|
482
|
+
}
|
|
423
483
|
})().finally(() => {
|
|
424
484
|
this.modelLoadInFlight.delete(modelUri);
|
|
425
485
|
});
|
|
@@ -462,8 +522,10 @@ export class ReasoningService {
|
|
|
462
522
|
|
|
463
523
|
private async ensureContextDatasetReady(modelUri: string): Promise<void> {
|
|
464
524
|
const attemptedLoads = new Set<string>();
|
|
525
|
+
let pass = 0;
|
|
465
526
|
|
|
466
527
|
while (true) {
|
|
528
|
+
pass += 1;
|
|
467
529
|
const allUris = [modelUri, ...this.importGraph.dependenciesOf(modelUri)];
|
|
468
530
|
const unresolvedIdentifiers: string[] = [];
|
|
469
531
|
const resolvedModelUris: string[] = [];
|
|
@@ -487,17 +549,17 @@ export class ReasoningService {
|
|
|
487
549
|
|
|
488
550
|
const loadFailures: string[] = [];
|
|
489
551
|
let loadedAny = false;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
552
|
+
const loadTargets = unloadedModelUris.filter((uri) => !attemptedLoads.has(uri));
|
|
553
|
+
for (const uri of loadTargets) {
|
|
494
554
|
attemptedLoads.add(uri);
|
|
555
|
+
}
|
|
556
|
+
if (loadTargets.length > 0) {
|
|
495
557
|
try {
|
|
496
|
-
await this.
|
|
558
|
+
await this.ensureModelsLoaded(loadTargets);
|
|
497
559
|
loadedAny = true;
|
|
498
560
|
} catch (error) {
|
|
499
561
|
const message = error instanceof Error ? error.message : String(error);
|
|
500
|
-
loadFailures.push(`${
|
|
562
|
+
loadFailures.push(`${loadTargets.length} model(s) (${message})`);
|
|
501
563
|
}
|
|
502
564
|
}
|
|
503
565
|
if (loadFailures.length > 0) {
|
|
@@ -526,6 +588,46 @@ export class ReasoningService {
|
|
|
526
588
|
}
|
|
527
589
|
}
|
|
528
590
|
|
|
591
|
+
private async ensureModelsLoaded(modelUris: ReadonlyArray<string>): Promise<void> {
|
|
592
|
+
const uniqueUris = [...new Set(modelUris)].filter((uri) => !this.isModelLoaded(uri));
|
|
593
|
+
if (uniqueUris.length === 0) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const documents: LangiumDocument[] = [];
|
|
598
|
+
for (const modelUri of uniqueUris) {
|
|
599
|
+
const inFlight = this.modelLoadInFlight.get(modelUri);
|
|
600
|
+
if (inFlight) {
|
|
601
|
+
await inFlight;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (this.isModelLoaded(modelUri)) {
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const uri = URI.parse(modelUri);
|
|
608
|
+
let document = this.services.shared.workspace.LangiumDocuments.getDocument(uri);
|
|
609
|
+
if (!document) {
|
|
610
|
+
document = await this.services.shared.workspace.LangiumDocuments.getOrCreateDocument(uri);
|
|
611
|
+
}
|
|
612
|
+
documents.push(document);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (documents.length === 0) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
await this.withSemanticChangeSuppressed(async () => {
|
|
620
|
+
await this.services.shared.workspace.DocumentBuilder.build(documents, { validation: false, eagerLinking: false });
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
for (const document of documents) {
|
|
624
|
+
const root = document.parseResult.value;
|
|
625
|
+
if (isOntology(root)) {
|
|
626
|
+
this.onDocumentValidated(document);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
529
631
|
async countContextDatasetQuads(modelUri: string): Promise<number> {
|
|
530
632
|
const resolvedModelUri = this.resolveQueryModelUri(modelUri);
|
|
531
633
|
await this.services.shared.workspace.WorkspaceManager.ready;
|
|
@@ -744,19 +846,17 @@ const BUILT_IN_ONTOLOGIES = new Set([
|
|
|
744
846
|
|
|
745
847
|
function createDefaultDependencies(overrides: Partial<OwlReasoningDependencies>): OwlReasoningDependencies {
|
|
746
848
|
const mapper = overrides.mapper ?? new Oml2OwlMapper();
|
|
747
|
-
|
|
849
|
+
// LeanReasoningStore is term-interned with packed columns — ~9x less memory than the
|
|
850
|
+
// n3-backed ReasoningStore, and faster scans.
|
|
851
|
+
const reasoningStore = overrides.reasoningStore ?? new LeanReasoningStore();
|
|
748
852
|
const importGraph = overrides.importGraph ?? new ImportGraph();
|
|
749
853
|
const tboxIndexCache = overrides.tboxIndexCache ?? new TBoxIndexCache();
|
|
750
854
|
const tboxChainer = overrides.tboxChainer ?? new TBoxChainer(reasoningStore);
|
|
751
855
|
const tboxIndexBuilder = overrides.tboxIndexBuilder ?? new TBoxIndexBuilder(reasoningStore, tboxIndexCache, importGraph);
|
|
752
856
|
const aboxChainer = overrides.aboxChainer ?? new ABoxChainer(reasoningStore);
|
|
753
857
|
const aboxEntailmentCache = overrides.aboxEntailmentCache ?? new ABoxEntailmentCache();
|
|
754
|
-
const createSparqlService = overrides.createSparqlService ?? ((runner) =>
|
|
755
|
-
reasoningStore,
|
|
756
|
-
importGraph,
|
|
757
|
-
aboxEntailmentCache,
|
|
758
|
-
runner,
|
|
759
|
-
));
|
|
858
|
+
const createSparqlService = overrides.createSparqlService ?? ((runner) =>
|
|
859
|
+
createSparqlEngine(resolveSparqlEngineKind(), reasoningStore, importGraph, aboxEntailmentCache, runner));
|
|
760
860
|
return {
|
|
761
861
|
mapper,
|
|
762
862
|
reasoningStore,
|