@oml/owl 0.7.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 +46 -0
- package/out/index.d.ts +10 -0
- package/out/index.js +12 -0
- package/out/index.js.map +1 -0
- package/out/owl/owl-abox.d.ts +79 -0
- package/out/owl/owl-abox.js +765 -0
- package/out/owl/owl-abox.js.map +1 -0
- package/out/owl/owl-imports.d.ts +9 -0
- package/out/owl/owl-imports.js +102 -0
- package/out/owl/owl-imports.js.map +1 -0
- package/out/owl/owl-interfaces.d.ts +121 -0
- package/out/owl/owl-interfaces.js +3 -0
- package/out/owl/owl-interfaces.js.map +1 -0
- package/out/owl/owl-mapper.d.ts +80 -0
- package/out/owl/owl-mapper.js +1217 -0
- package/out/owl/owl-mapper.js.map +1 -0
- package/out/owl/owl-service.d.ts +65 -0
- package/out/owl/owl-service.js +552 -0
- package/out/owl/owl-service.js.map +1 -0
- package/out/owl/owl-shacl.d.ts +28 -0
- package/out/owl/owl-shacl.js +337 -0
- package/out/owl/owl-shacl.js.map +1 -0
- package/out/owl/owl-sparql.d.ts +71 -0
- package/out/owl/owl-sparql.js +260 -0
- package/out/owl/owl-sparql.js.map +1 -0
- package/out/owl/owl-store.d.ts +32 -0
- package/out/owl/owl-store.js +142 -0
- package/out/owl/owl-store.js.map +1 -0
- package/out/owl/owl-tbox.d.ts +98 -0
- package/out/owl/owl-tbox.js +575 -0
- package/out/owl/owl-tbox.js.map +1 -0
- package/out/owl-module.d.ts +15 -0
- package/out/owl-module.js +22 -0
- package/out/owl-module.js.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +12 -0
- package/src/owl/owl-abox.ts +930 -0
- package/src/owl/owl-imports.ts +108 -0
- package/src/owl/owl-interfaces.ts +145 -0
- package/src/owl/owl-mapper.ts +1510 -0
- package/src/owl/owl-service.ts +642 -0
- package/src/owl/owl-shacl.ts +400 -0
- package/src/owl/owl-sparql.ts +317 -0
- package/src/owl/owl-store.ts +173 -0
- package/src/owl/owl-tbox.ts +727 -0
- package/src/owl-module.ts +52 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
// NOTE: Comunica's QueryEngine attempts to spawn workers during construction.
|
|
4
|
+
// This works in Node.js and browser main thread. For browser Web Worker contexts (language server),
|
|
5
|
+
// the Worker constructor is mocked in browser-server.ts to prevent nested worker spawning while
|
|
6
|
+
// allowing Comunica to initialize and execute queries successfully (without parallel execution).
|
|
7
|
+
import { QueryEngine } from '@comunica/query-sparql';
|
|
8
|
+
import type { Bindings } from '@comunica/types';
|
|
9
|
+
import { Store } from 'n3';
|
|
10
|
+
import type * as RDF from '@rdfjs/types';
|
|
11
|
+
import type { NamedNode } from 'n3';
|
|
12
|
+
import type { OwlABoxEntailmentState, OwlImportGraph, OwlInferenceRunner, OwlStore } from './owl-interfaces.js';
|
|
13
|
+
import { deriveSelectQueryFromShacl, type DerivedShaclSelectQuery } from './owl-shacl.js';
|
|
14
|
+
|
|
15
|
+
export interface RDFTerm {
|
|
16
|
+
termType: 'NamedNode' | 'Literal' | 'BlankNode';
|
|
17
|
+
value: string;
|
|
18
|
+
datatype?: string;
|
|
19
|
+
language?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type QueryRow = Map<string, RDFTerm>;
|
|
23
|
+
|
|
24
|
+
export interface QueryResult {
|
|
25
|
+
success: boolean;
|
|
26
|
+
rows: QueryRow[];
|
|
27
|
+
warnings: string[];
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ConstructResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
quads: RDF.Quad[];
|
|
34
|
+
warnings: string[];
|
|
35
|
+
error?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AskResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
result: boolean;
|
|
41
|
+
warnings: string[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface QueryContext {
|
|
46
|
+
graphs: NamedNode[];
|
|
47
|
+
missingModels: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type DerivedShapeSelectQuery = DerivedShaclSelectQuery;
|
|
51
|
+
export const deriveSelectQueryFromShape = deriveSelectQueryFromShacl;
|
|
52
|
+
|
|
53
|
+
export class SparqlService {
|
|
54
|
+
private readonly engine = new QueryEngine();
|
|
55
|
+
private readonly filteredStoreCache = new Map<string, Store>();
|
|
56
|
+
private readonly dirtyFilteredStores = new Set<string>();
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
private readonly reasoningStore: OwlStore,
|
|
60
|
+
private readonly importGraph: OwlImportGraph,
|
|
61
|
+
private readonly aboxEntailmentCache: OwlABoxEntailmentState,
|
|
62
|
+
private readonly reasoningService: OwlInferenceRunner,
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Executes a SPARQL query over the model's reasoned context.
|
|
67
|
+
* Queries must use explicit GRAPH patterns to access data.
|
|
68
|
+
* Available named graphs for a model context are:
|
|
69
|
+
* <modelNamespace> - asserted quads
|
|
70
|
+
* <modelNamespace__entailments> - inferred quads
|
|
71
|
+
* The same pattern applies to all models in the import closure.
|
|
72
|
+
*/
|
|
73
|
+
async query(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
74
|
+
try {
|
|
75
|
+
this.ensureFreshEntailments(modelUri);
|
|
76
|
+
return await this.querySelect(modelUri, sparql);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
let warnings: string[] = [];
|
|
79
|
+
try {
|
|
80
|
+
warnings = this.toMissingModelsWarnings(this.getQueryContext(modelUri).missingModels);
|
|
81
|
+
} catch {
|
|
82
|
+
warnings = [];
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
rows: [],
|
|
87
|
+
warnings,
|
|
88
|
+
error: error instanceof Error ? error.message : String(error),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Executes a SPARQL SELECT query over currently available store state.
|
|
95
|
+
* This method does not trigger inference refresh.
|
|
96
|
+
* The same named-graph contract applies as query(): explicit GRAPH patterns are required.
|
|
97
|
+
*/
|
|
98
|
+
async queryRaw(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
99
|
+
return this.querySelect(modelUri, sparql);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async construct(modelUri: string, sparql: string): Promise<ConstructResult> {
|
|
103
|
+
try {
|
|
104
|
+
this.ensureFreshEntailments(modelUri);
|
|
105
|
+
const queryContext = this.getQueryContext(modelUri);
|
|
106
|
+
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
107
|
+
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
108
|
+
const baseIRI = this.resolveBaseIri(modelUri);
|
|
109
|
+
const quadsStream = await this.engine.queryQuads(sparql, {
|
|
110
|
+
sources: [filteredStore],
|
|
111
|
+
unionDefaultGraph: true,
|
|
112
|
+
baseIRI,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const quads: RDF.Quad[] = [];
|
|
116
|
+
for await (const inferred of quadsStream) {
|
|
117
|
+
quads.push(inferred);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
quads,
|
|
123
|
+
warnings,
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
quads: [],
|
|
129
|
+
warnings: [],
|
|
130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async ask(modelUri: string, sparql: string): Promise<AskResult> {
|
|
136
|
+
try {
|
|
137
|
+
this.ensureFreshEntailments(modelUri);
|
|
138
|
+
const queryContext = this.getQueryContext(modelUri);
|
|
139
|
+
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
140
|
+
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
141
|
+
const baseIRI = this.resolveBaseIri(modelUri);
|
|
142
|
+
const result = await this.engine.queryBoolean(sparql, {
|
|
143
|
+
sources: [filteredStore],
|
|
144
|
+
unionDefaultGraph: true,
|
|
145
|
+
baseIRI,
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
result,
|
|
150
|
+
warnings,
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
result: false,
|
|
156
|
+
warnings: [],
|
|
157
|
+
error: error instanceof Error ? error.message : String(error),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getAvailableGraphs(modelUri: string): NamedNode[] {
|
|
163
|
+
return this.getQueryContext(modelUri).graphs;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
invalidateFilteredStore(modelUri: string): void {
|
|
167
|
+
this.filteredStoreCache.delete(modelUri);
|
|
168
|
+
this.dirtyFilteredStores.add(modelUri);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
removeFilteredStore(modelUri: string): void {
|
|
172
|
+
this.filteredStoreCache.delete(modelUri);
|
|
173
|
+
this.dirtyFilteredStores.delete(modelUri);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private async querySelect(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
177
|
+
try {
|
|
178
|
+
const queryContext = this.getQueryContext(modelUri);
|
|
179
|
+
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
180
|
+
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
181
|
+
const baseIRI = this.resolveBaseIri(modelUri);
|
|
182
|
+
const bindingsStream = await this.engine.queryBindings(sparql, {
|
|
183
|
+
sources: [filteredStore],
|
|
184
|
+
unionDefaultGraph: true,
|
|
185
|
+
baseIRI,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const rows: QueryRow[] = [];
|
|
189
|
+
for await (const binding of bindingsStream) {
|
|
190
|
+
const row = new Map<string, RDFTerm>();
|
|
191
|
+
(binding as Bindings).forEach((value, variable) => {
|
|
192
|
+
if (!this.isQueryValueTerm(value)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const variableName = variable.value;
|
|
196
|
+
row.set(variableName, this.toRdfTerm(value));
|
|
197
|
+
});
|
|
198
|
+
rows.push(row);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
success: true,
|
|
203
|
+
rows,
|
|
204
|
+
warnings,
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const queryContext = this.getQueryContext(modelUri);
|
|
208
|
+
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
rows: [],
|
|
212
|
+
warnings,
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private ensureFreshEntailments(modelUri: string): void {
|
|
219
|
+
const closure = [modelUri, ...this.importGraph.dependenciesOf(modelUri)];
|
|
220
|
+
const requiresRefresh = closure.some((uri) => this.aboxEntailmentCache.isDirty(uri) || this.aboxEntailmentCache.isAbsent(uri));
|
|
221
|
+
if (requiresRefresh) {
|
|
222
|
+
this.reasoningService.runInference(modelUri);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private resolveBaseIri(modelUri: string): string {
|
|
227
|
+
try {
|
|
228
|
+
return this.reasoningStore.graphs(modelUri).own.value;
|
|
229
|
+
} catch {
|
|
230
|
+
return modelUri;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private getQueryContext(modelUri: string): QueryContext {
|
|
235
|
+
const allUris = [modelUri, ...this.importGraph.dependenciesOf(modelUri)];
|
|
236
|
+
const seen = new Set<string>();
|
|
237
|
+
const graphs: NamedNode[] = [];
|
|
238
|
+
const missingModels: string[] = [];
|
|
239
|
+
for (const uri of allUris) {
|
|
240
|
+
let own: NamedNode;
|
|
241
|
+
let entailments: NamedNode;
|
|
242
|
+
try {
|
|
243
|
+
({ own, entailments } = this.reasoningStore.graphs(uri));
|
|
244
|
+
} catch {
|
|
245
|
+
missingModels.push(uri);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
for (const graph of [own, entailments]) {
|
|
249
|
+
if (seen.has(graph.value)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
seen.add(graph.value);
|
|
253
|
+
graphs.push(graph);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { graphs, missingModels };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private toRdfTerm(term: RDF.Term): RDFTerm {
|
|
260
|
+
if (term.termType === 'NamedNode') {
|
|
261
|
+
return { termType: 'NamedNode', value: term.value };
|
|
262
|
+
}
|
|
263
|
+
if (term.termType === 'BlankNode') {
|
|
264
|
+
return { termType: 'BlankNode', value: term.value };
|
|
265
|
+
}
|
|
266
|
+
const literal = term as RDF.Literal;
|
|
267
|
+
return {
|
|
268
|
+
termType: 'Literal',
|
|
269
|
+
value: term.value,
|
|
270
|
+
datatype: literal.datatype.value,
|
|
271
|
+
language: literal.language,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private isQueryValueTerm(term: RDF.Term): term is RDF.NamedNode | RDF.BlankNode | RDF.Literal {
|
|
276
|
+
return term.termType === 'NamedNode' || term.termType === 'BlankNode' || term.termType === 'Literal';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private toMissingModelsWarnings(missingModels: string[]): string[] {
|
|
280
|
+
return missingModels.map((modelUri) => `Model not loaded: ${modelUri}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private composeWarnings(modelUri: string, missingModels: string[]): string[] {
|
|
284
|
+
const closureWarnings = [modelUri, ...this.importGraph.dependenciesOf(modelUri)]
|
|
285
|
+
.flatMap((uri) => this.aboxEntailmentCache.getValidationWarnings(uri));
|
|
286
|
+
return [...new Set([...this.toMissingModelsWarnings(missingModels), ...closureWarnings])];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private createFilteredStore(graphs: NamedNode[]): Store {
|
|
290
|
+
const filteredStore = new Store();
|
|
291
|
+
const mainStore = this.reasoningStore.getStore();
|
|
292
|
+
const seenTriples = new Set<string>();
|
|
293
|
+
for (const graph of graphs) {
|
|
294
|
+
const uniqueQuads = mainStore.getQuads(null, null, null, graph).filter((quad) => {
|
|
295
|
+
const key = `${quad.subject.id}|${quad.predicate.id}|${quad.object.id}`;
|
|
296
|
+
if (seenTriples.has(key)) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
seenTriples.add(key);
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
302
|
+
filteredStore.addQuads(uniqueQuads);
|
|
303
|
+
}
|
|
304
|
+
return filteredStore;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private getOrCreateFilteredStore(modelUri: string, queryContext: QueryContext): Store {
|
|
308
|
+
const cached = this.filteredStoreCache.get(modelUri);
|
|
309
|
+
if (cached && !this.dirtyFilteredStores.has(modelUri)) {
|
|
310
|
+
return cached;
|
|
311
|
+
}
|
|
312
|
+
const store = this.createFilteredStore(queryContext.graphs);
|
|
313
|
+
this.filteredStoreCache.set(modelUri, store);
|
|
314
|
+
this.dirtyFilteredStores.delete(modelUri);
|
|
315
|
+
return store;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DataFactory, Store } from 'n3';
|
|
4
|
+
import type { NamedNode, Quad } from 'n3';
|
|
5
|
+
|
|
6
|
+
const OML_NAMESPACE = 'http://opencaesar.io/oml#namespace';
|
|
7
|
+
const { namedNode, quad } = DataFactory;
|
|
8
|
+
|
|
9
|
+
export interface ModelDiff {
|
|
10
|
+
isEmpty: boolean;
|
|
11
|
+
retracted: Quad[];
|
|
12
|
+
asserted: Quad[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ModelGraphs {
|
|
16
|
+
own: NamedNode;
|
|
17
|
+
entailments: NamedNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ReasoningStore {
|
|
21
|
+
private readonly store = new Store();
|
|
22
|
+
private readonly modelGraphs = new Map<string, ModelGraphs>();
|
|
23
|
+
|
|
24
|
+
// Load a model's quads into its named graph.
|
|
25
|
+
// Retracts any existing quads for that model first.
|
|
26
|
+
// Graph IRI is derived from the ontology namespace embedded in the quads.
|
|
27
|
+
loadModel(modelUri: string, quads: Quad[]): void {
|
|
28
|
+
const graphs = this.resolveGraphs(modelUri, quads);
|
|
29
|
+
this.retractModel(modelUri);
|
|
30
|
+
this.modelGraphs.set(modelUri, graphs);
|
|
31
|
+
this.store.addQuads(quads.map((q) => this.withGraph(q, graphs.own)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
loadEntailments(modelUri: string, quads: Quad[]): void {
|
|
35
|
+
const graphs = this.requireGraphs(modelUri);
|
|
36
|
+
this.removeGraph(graphs.entailments);
|
|
37
|
+
if (quads.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.store.addQuads(quads.map((q) => this.withGraph(q, graphs.entailments)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
applyModelDelta(modelUri: string, quads: Quad[], retracted: Quad[], asserted: Quad[]): void {
|
|
44
|
+
const graphs = this.resolveGraphs(modelUri, quads, true);
|
|
45
|
+
this.modelGraphs.set(modelUri, graphs);
|
|
46
|
+
if (retracted.length > 0) {
|
|
47
|
+
this.store.removeQuads(retracted.map((q) => this.withGraph(q, graphs.own)));
|
|
48
|
+
}
|
|
49
|
+
if (asserted.length > 0) {
|
|
50
|
+
this.store.addQuads(asserted.map((q) => this.withGraph(q, graphs.own)));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Retract all quads in all named graphs for a model
|
|
55
|
+
// (own quads + entailment quads)
|
|
56
|
+
retractModel(modelUri: string): void {
|
|
57
|
+
const graphs = this.modelGraphs.get(modelUri);
|
|
58
|
+
if (!graphs) return;
|
|
59
|
+
this.removeGraph(graphs.own);
|
|
60
|
+
this.removeGraph(graphs.entailments);
|
|
61
|
+
this.modelGraphs.delete(modelUri);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Compare newly mapped quads against what is currently in the store.
|
|
65
|
+
// Returns the semantic difference — what was added and what was removed.
|
|
66
|
+
// If the diff is empty, nothing downstream needs to run.
|
|
67
|
+
diffModel(modelUri: string, newQuads: Quad[]): ModelDiff {
|
|
68
|
+
const graphs = this.resolveGraphs(modelUri, newQuads, true);
|
|
69
|
+
const existing = this.store.getQuads(null, null, null, graphs.own);
|
|
70
|
+
const next = newQuads.map((q) => this.withGraph(q, graphs.own));
|
|
71
|
+
|
|
72
|
+
const existingByKey = new Map(existing.map((q) => [this.quadKey(q), q]));
|
|
73
|
+
const nextByKey = new Map(next.map((q) => [this.quadKey(q), q]));
|
|
74
|
+
|
|
75
|
+
const retracted = [...existingByKey.entries()]
|
|
76
|
+
.filter(([key]) => !nextByKey.has(key))
|
|
77
|
+
.map(([, value]) => value)
|
|
78
|
+
.sort((a, b) => this.quadKey(a).localeCompare(this.quadKey(b)));
|
|
79
|
+
|
|
80
|
+
const asserted = [...nextByKey.entries()]
|
|
81
|
+
.filter(([key]) => !existingByKey.has(key))
|
|
82
|
+
.map(([, value]) => value)
|
|
83
|
+
.sort((a, b) => this.quadKey(a).localeCompare(this.quadKey(b)));
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
isEmpty: retracted.length === 0 && asserted.length === 0,
|
|
87
|
+
retracted,
|
|
88
|
+
asserted,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Clear only the entailment named graph for a model.
|
|
93
|
+
// Called before re-running TBox or ABox chaining.
|
|
94
|
+
clearEntailments(modelUri: string): void {
|
|
95
|
+
const graphs = this.requireGraphs(modelUri);
|
|
96
|
+
this.removeGraph(graphs.entailments);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Returns the named graph IRIs for a model.
|
|
100
|
+
// Used by TBoxChainer, ABoxChainer, and SparqlService
|
|
101
|
+
// to know which graphs to read from and write to.
|
|
102
|
+
graphs(modelUri: string): ModelGraphs {
|
|
103
|
+
return this.requireGraphs(modelUri);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Expose the underlying N3 Store for direct use by
|
|
107
|
+
// TBoxChainer, ABoxChainer, and Comunica.
|
|
108
|
+
getStore(): Store {
|
|
109
|
+
return this.store;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private resolveGraphs(modelUri: string, quads: Quad[], allowExisting = false): ModelGraphs {
|
|
113
|
+
const namespace = this.namespaceFromQuads(quads);
|
|
114
|
+
if (namespace) {
|
|
115
|
+
const graphs = this.graphsFromNamespace(namespace);
|
|
116
|
+
this.modelGraphs.set(modelUri, graphs);
|
|
117
|
+
return graphs;
|
|
118
|
+
}
|
|
119
|
+
if (allowExisting) {
|
|
120
|
+
const existing = this.modelGraphs.get(modelUri);
|
|
121
|
+
if (existing) return existing;
|
|
122
|
+
}
|
|
123
|
+
// Keep reasoning resilient during transient states (e.g. non-mappable documents):
|
|
124
|
+
// if namespace is missing, fall back to a stable graph derived from the model URI.
|
|
125
|
+
const fallback = this.graphsFromModelUri(modelUri);
|
|
126
|
+
this.modelGraphs.set(modelUri, fallback);
|
|
127
|
+
return fallback;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private graphsFromNamespace(namespace: string): ModelGraphs {
|
|
131
|
+
const ontologyIri = this.ontologyIriFromNamespace(namespace);
|
|
132
|
+
return {
|
|
133
|
+
own: namedNode(ontologyIri),
|
|
134
|
+
entailments: namedNode(`${ontologyIri}__entailments`),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private graphsFromModelUri(modelUri: string): ModelGraphs {
|
|
139
|
+
return {
|
|
140
|
+
own: namedNode(modelUri),
|
|
141
|
+
entailments: namedNode(`${modelUri}__entailments`)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private namespaceFromQuads(quads: Quad[]): string | undefined {
|
|
146
|
+
const namespaceQuad = quads.find((q) => q.predicate.value === OML_NAMESPACE && q.object.termType === 'NamedNode');
|
|
147
|
+
return namespaceQuad?.object.value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private ontologyIriFromNamespace(namespace: string): string {
|
|
151
|
+
return namespace.replace(/[\/#]+$/, '');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private requireGraphs(modelUri: string): ModelGraphs {
|
|
155
|
+
const graphs = this.modelGraphs.get(modelUri);
|
|
156
|
+
if (!graphs) {
|
|
157
|
+
throw new Error(`Unknown model '${modelUri}'. Load the model first.`);
|
|
158
|
+
}
|
|
159
|
+
return graphs;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private withGraph(q: Quad, graph: NamedNode): Quad {
|
|
163
|
+
return quad(q.subject, q.predicate, q.object, graph);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private removeGraph(graph: NamedNode): void {
|
|
167
|
+
this.store.removeQuads(this.store.getQuads(null, null, null, graph));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private quadKey(q: Quad): string {
|
|
171
|
+
return `${q.subject.id}|${q.predicate.id}|${q.object.id}|${q.graph.id}`;
|
|
172
|
+
}
|
|
173
|
+
}
|