@oml/owl 0.19.3 → 0.20.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/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-sparql.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
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 { DataFactory, Store } from 'n3';
|
|
3
|
+
// Shared, engine-agnostic SPARQL result types + query-kind detection. The SPARQL engine itself
|
|
4
|
+
// is Oxigraph (owl-sparql-oxigraph.ts); these helpers are used by the REST layer and the engine.
|
|
5
|
+
|
|
10
6
|
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
7
|
import { deriveSelectQueryFromShacl, type DerivedShaclSelectQuery } from './owl-shacl.js';
|
|
14
8
|
|
|
15
9
|
export interface RDFTerm {
|
|
@@ -115,362 +109,5 @@ export function detectSparqlKind(sparql: string): SparqlQueryKind {
|
|
|
115
109
|
return 'unknown';
|
|
116
110
|
}
|
|
117
111
|
|
|
118
|
-
interface QueryContext {
|
|
119
|
-
mappings: {
|
|
120
|
-
ownGraph: NamedNode;
|
|
121
|
-
entailmentsGraph: NamedNode;
|
|
122
|
-
targetOwnGraph: NamedNode;
|
|
123
|
-
targetEntailmentsGraph: NamedNode;
|
|
124
|
-
}[];
|
|
125
|
-
missingModels: string[];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
112
|
export type DerivedShapeSelectQuery = DerivedShaclSelectQuery;
|
|
129
113
|
export const deriveSelectQueryFromShape = deriveSelectQueryFromShacl;
|
|
130
|
-
|
|
131
|
-
// Zero-copy view over the main reasoning store with graph remapping.
|
|
132
|
-
// Instead of eagerly copying all quads into a new Store per ontology context,
|
|
133
|
-
// this delegates match() calls to the main store at query time, translating
|
|
134
|
-
// target graph names back to their source graph names on the fly.
|
|
135
|
-
// Memory cost is O(mappings) instead of O(total quads).
|
|
136
|
-
class MappedStoreView {
|
|
137
|
-
private readonly reverseEntries: Array<{ sourceGraph: NamedNode; targetGraph: NamedNode }>;
|
|
138
|
-
private readonly reverseMap: Map<string, { sourceGraph: NamedNode; targetGraph: NamedNode }>;
|
|
139
|
-
|
|
140
|
-
constructor(
|
|
141
|
-
private readonly mainStore: Store,
|
|
142
|
-
mappings: QueryContext['mappings'],
|
|
143
|
-
) {
|
|
144
|
-
this.reverseEntries = mappings.flatMap((m) => [
|
|
145
|
-
{ sourceGraph: m.ownGraph, targetGraph: m.targetOwnGraph },
|
|
146
|
-
{ sourceGraph: m.entailmentsGraph, targetGraph: m.targetEntailmentsGraph },
|
|
147
|
-
]);
|
|
148
|
-
this.reverseMap = new Map(this.reverseEntries.map((e) => [e.targetGraph.value, e]));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Comunica calls match() with specific graph terms from GRAPH patterns, or null for all graphs.
|
|
152
|
-
match(s?: RDF.Term | null, p?: RDF.Term | null, o?: RDF.Term | null, g?: RDF.Term | null): Store {
|
|
153
|
-
const sArg = s ?? null;
|
|
154
|
-
const pArg = p ?? null;
|
|
155
|
-
const oArg = o ?? null;
|
|
156
|
-
if (g && g.termType !== 'Variable' && g.termType !== 'DefaultGraph') {
|
|
157
|
-
const entry = this.reverseMap.get(g.value);
|
|
158
|
-
if (!entry) return new Store();
|
|
159
|
-
return new Store(
|
|
160
|
-
this.mainStore.getQuads(sArg, pArg, oArg, entry.sourceGraph)
|
|
161
|
-
.map((q) => DataFactory.quad(q.subject, q.predicate, q.object, entry.targetGraph)),
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
// All-graphs scan: deduplicate by s+p+o to match prior filtered-store behaviour.
|
|
165
|
-
const seen = new Set<string>();
|
|
166
|
-
const quads: RDF.Quad[] = [];
|
|
167
|
-
for (const entry of this.reverseEntries) {
|
|
168
|
-
for (const q of this.mainStore.getQuads(sArg, pArg, oArg, entry.sourceGraph)) {
|
|
169
|
-
const key = `${q.subject.id}|${q.predicate.id}|${q.object.id}`;
|
|
170
|
-
if (!seen.has(key)) {
|
|
171
|
-
seen.add(key);
|
|
172
|
-
quads.push(DataFactory.quad(q.subject, q.predicate, q.object, entry.targetGraph));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return new Store(quads);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
get size(): number {
|
|
180
|
-
return this.reverseEntries.reduce(
|
|
181
|
-
(n, e) => n + this.mainStore.countQuads(null, null, null, e.sourceGraph),
|
|
182
|
-
0,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
[Symbol.iterator](): Iterator<RDF.Quad> {
|
|
187
|
-
return this.match()[Symbol.iterator]();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export class SparqlService {
|
|
192
|
-
private readonly engine = new QueryEngine();
|
|
193
|
-
private readonly filteredStoreCache = new Map<string, MappedStoreView>();
|
|
194
|
-
private readonly dirtyFilteredStores = new Set<string>();
|
|
195
|
-
private ontologyIriResolver: (modelUri: string) => string;
|
|
196
|
-
|
|
197
|
-
constructor(
|
|
198
|
-
private readonly reasoningStore: OwlStore,
|
|
199
|
-
private readonly importGraph: OwlImportGraph,
|
|
200
|
-
private readonly aboxEntailmentCache: OwlABoxEntailmentState,
|
|
201
|
-
private readonly reasoningService: OwlInferenceRunner,
|
|
202
|
-
resolveOntologyIriForModelUri?: (modelUri: string) => string,
|
|
203
|
-
) {
|
|
204
|
-
this.ontologyIriResolver = resolveOntologyIriForModelUri ?? ((modelUri) => modelUri);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
setOntologyIriResolver(resolver: (modelUri: string) => string): void {
|
|
208
|
-
this.ontologyIriResolver = resolver;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Executes a SPARQL query over the model's reasoned context.
|
|
213
|
-
* Queries must use explicit GRAPH patterns to access data.
|
|
214
|
-
* Available named graphs for a model context are:
|
|
215
|
-
* <modelNamespace> - asserted quads
|
|
216
|
-
* <modelNamespace__entailments> - inferred quads
|
|
217
|
-
* The same pattern applies to all models in the import closure.
|
|
218
|
-
*/
|
|
219
|
-
async query(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
220
|
-
try {
|
|
221
|
-
this.ensureFreshEntailments(modelUri);
|
|
222
|
-
return await this.querySelect(modelUri, sparql);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
let warnings: string[] = [];
|
|
225
|
-
try {
|
|
226
|
-
warnings = this.toMissingModelsWarnings(this.getQueryContext(modelUri).missingModels);
|
|
227
|
-
} catch {
|
|
228
|
-
warnings = [];
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
success: false,
|
|
232
|
-
rows: [],
|
|
233
|
-
warnings,
|
|
234
|
-
error: error instanceof Error ? error.message : String(error),
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Executes a SPARQL SELECT query over currently available store state.
|
|
241
|
-
* This method does not trigger inference refresh.
|
|
242
|
-
* The same named-graph contract applies as query(): explicit GRAPH patterns are required.
|
|
243
|
-
*/
|
|
244
|
-
async queryRaw(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
245
|
-
return this.querySelect(modelUri, sparql);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async construct(modelUri: string, sparql: string): Promise<ConstructResult> {
|
|
249
|
-
try {
|
|
250
|
-
this.ensureFreshEntailments(modelUri);
|
|
251
|
-
const queryContext = this.getQueryContext(modelUri);
|
|
252
|
-
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
253
|
-
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
254
|
-
const baseIRI = this.resolveBaseIri(modelUri);
|
|
255
|
-
const quadsStream = await this.engine.queryQuads(sparql, {
|
|
256
|
-
sources: [filteredStore as unknown as Store],
|
|
257
|
-
unionDefaultGraph: true,
|
|
258
|
-
baseIRI,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const seen = new Set<string>();
|
|
262
|
-
const quads: RDF.Quad[] = [];
|
|
263
|
-
for await (const inferred of quadsStream) {
|
|
264
|
-
const key = `${inferred.subject.value}\x00${inferred.predicate.value}\x00${inferred.object.value}`;
|
|
265
|
-
if (!seen.has(key)) {
|
|
266
|
-
seen.add(key);
|
|
267
|
-
quads.push(inferred);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return {
|
|
272
|
-
success: true,
|
|
273
|
-
quads,
|
|
274
|
-
warnings,
|
|
275
|
-
};
|
|
276
|
-
} catch (error) {
|
|
277
|
-
return {
|
|
278
|
-
success: false,
|
|
279
|
-
quads: [],
|
|
280
|
-
warnings: [],
|
|
281
|
-
error: error instanceof Error ? error.message : String(error),
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async ask(modelUri: string, sparql: string): Promise<AskResult> {
|
|
287
|
-
try {
|
|
288
|
-
this.ensureFreshEntailments(modelUri);
|
|
289
|
-
const queryContext = this.getQueryContext(modelUri);
|
|
290
|
-
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
291
|
-
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
292
|
-
const baseIRI = this.resolveBaseIri(modelUri);
|
|
293
|
-
const result = await this.engine.queryBoolean(sparql, {
|
|
294
|
-
sources: [filteredStore as unknown as Store],
|
|
295
|
-
unionDefaultGraph: true,
|
|
296
|
-
baseIRI,
|
|
297
|
-
});
|
|
298
|
-
return {
|
|
299
|
-
success: true,
|
|
300
|
-
result,
|
|
301
|
-
warnings,
|
|
302
|
-
};
|
|
303
|
-
} catch (error) {
|
|
304
|
-
return {
|
|
305
|
-
success: false,
|
|
306
|
-
result: false,
|
|
307
|
-
warnings: [],
|
|
308
|
-
error: error instanceof Error ? error.message : String(error),
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
getAvailableGraphs(modelUri: string): NamedNode[] {
|
|
314
|
-
const seen = new Set<string>();
|
|
315
|
-
const graphs: NamedNode[] = [];
|
|
316
|
-
for (const mapping of this.getQueryContext(modelUri).mappings) {
|
|
317
|
-
for (const graph of [mapping.targetOwnGraph, mapping.targetEntailmentsGraph]) {
|
|
318
|
-
if (seen.has(graph.value)) {
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
seen.add(graph.value);
|
|
322
|
-
graphs.push(graph);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return graphs;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
invalidateFilteredStore(modelUri: string): void {
|
|
329
|
-
this.filteredStoreCache.delete(modelUri);
|
|
330
|
-
this.dirtyFilteredStores.add(modelUri);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
removeFilteredStore(modelUri: string): void {
|
|
334
|
-
this.filteredStoreCache.delete(modelUri);
|
|
335
|
-
this.dirtyFilteredStores.delete(modelUri);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private async querySelect(modelUri: string, sparql: string): Promise<QueryResult> {
|
|
339
|
-
try {
|
|
340
|
-
const queryContext = this.getQueryContext(modelUri);
|
|
341
|
-
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
342
|
-
const filteredStore = this.getOrCreateFilteredStore(modelUri, queryContext);
|
|
343
|
-
const baseIRI = this.resolveBaseIri(modelUri);
|
|
344
|
-
const bindingsStream = await this.engine.queryBindings(sparql, {
|
|
345
|
-
sources: [filteredStore as unknown as Store],
|
|
346
|
-
unionDefaultGraph: true,
|
|
347
|
-
baseIRI,
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
const rows: QueryRow[] = [];
|
|
351
|
-
for await (const binding of bindingsStream) {
|
|
352
|
-
const row = new Map<string, RDFTerm>();
|
|
353
|
-
(binding as Bindings).forEach((value, variable) => {
|
|
354
|
-
if (!this.isQueryValueTerm(value)) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const variableName = variable.value;
|
|
358
|
-
row.set(variableName, this.toRdfTerm(value));
|
|
359
|
-
});
|
|
360
|
-
rows.push(row);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return {
|
|
364
|
-
success: true,
|
|
365
|
-
rows,
|
|
366
|
-
warnings,
|
|
367
|
-
};
|
|
368
|
-
} catch (error) {
|
|
369
|
-
const queryContext = this.getQueryContext(modelUri);
|
|
370
|
-
const warnings = this.composeWarnings(modelUri, queryContext.missingModels);
|
|
371
|
-
return {
|
|
372
|
-
success: false,
|
|
373
|
-
rows: [],
|
|
374
|
-
warnings,
|
|
375
|
-
error: error instanceof Error ? error.message : String(error),
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
private ensureFreshEntailments(modelUri: string): void {
|
|
381
|
-
const closure = [modelUri, ...this.importGraph.dependenciesOf(modelUri)];
|
|
382
|
-
const requiresRefresh = closure.some((uri) => this.aboxEntailmentCache.isDirty(uri) || this.aboxEntailmentCache.isAbsent(uri));
|
|
383
|
-
if (requiresRefresh) {
|
|
384
|
-
this.reasoningService.runInference(modelUri);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private resolveBaseIri(modelUri: string): string {
|
|
389
|
-
try {
|
|
390
|
-
return this.reasoningStore.graphs(modelUri).own.value;
|
|
391
|
-
} catch {
|
|
392
|
-
return modelUri;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private getQueryContext(modelUri: string): QueryContext {
|
|
397
|
-
const allUris = [modelUri, ...this.importGraph.dependenciesOf(modelUri)];
|
|
398
|
-
const seen = new Set<string>();
|
|
399
|
-
const mappings: QueryContext['mappings'] = [];
|
|
400
|
-
const missingModels: string[] = [];
|
|
401
|
-
for (const uri of allUris) {
|
|
402
|
-
let own: NamedNode;
|
|
403
|
-
let entailments: NamedNode;
|
|
404
|
-
try {
|
|
405
|
-
({ own, entailments } = this.reasoningStore.graphs(uri));
|
|
406
|
-
} catch {
|
|
407
|
-
missingModels.push(uri);
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
if (seen.has(uri)) {
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
seen.add(uri);
|
|
414
|
-
const resolved = this.ontologyIriResolver(uri);
|
|
415
|
-
if (!resolved || resolved === uri) {
|
|
416
|
-
throw new Error(`Missing ontology IRI mapping for model '${uri}'.`);
|
|
417
|
-
}
|
|
418
|
-
const ontologyIri = this.toOntologyGraphIri(resolved);
|
|
419
|
-
mappings.push({
|
|
420
|
-
ownGraph: own,
|
|
421
|
-
entailmentsGraph: entailments,
|
|
422
|
-
targetOwnGraph: DataFactory.namedNode(ontologyIri),
|
|
423
|
-
targetEntailmentsGraph: DataFactory.namedNode(`${ontologyIri}__entailments`),
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
return { mappings, missingModels };
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
private toOntologyGraphIri(iri: string): string {
|
|
430
|
-
if (iri.endsWith('#')) {
|
|
431
|
-
return iri.slice(0, -1);
|
|
432
|
-
}
|
|
433
|
-
return iri;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
private toRdfTerm(term: RDF.Term): RDFTerm {
|
|
437
|
-
if (term.termType === 'NamedNode') {
|
|
438
|
-
return { termType: 'NamedNode', value: term.value };
|
|
439
|
-
}
|
|
440
|
-
if (term.termType === 'BlankNode') {
|
|
441
|
-
return { termType: 'BlankNode', value: term.value };
|
|
442
|
-
}
|
|
443
|
-
const literal = term as RDF.Literal;
|
|
444
|
-
return {
|
|
445
|
-
termType: 'Literal',
|
|
446
|
-
value: term.value,
|
|
447
|
-
datatype: literal.datatype.value,
|
|
448
|
-
language: literal.language,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private isQueryValueTerm(term: RDF.Term): term is RDF.NamedNode | RDF.BlankNode | RDF.Literal {
|
|
453
|
-
return term.termType === 'NamedNode' || term.termType === 'BlankNode' || term.termType === 'Literal';
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
private toMissingModelsWarnings(missingModels: string[]): string[] {
|
|
457
|
-
return missingModels.map((modelUri) => `Model not loaded: ${modelUri}`);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
private composeWarnings(modelUri: string, missingModels: string[]): string[] {
|
|
461
|
-
const closureWarnings = [modelUri, ...this.importGraph.dependenciesOf(modelUri)]
|
|
462
|
-
.flatMap((uri) => this.aboxEntailmentCache.getValidationWarnings(uri));
|
|
463
|
-
return [...new Set([...this.toMissingModelsWarnings(missingModels), ...closureWarnings])];
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
private getOrCreateFilteredStore(modelUri: string, queryContext: QueryContext): MappedStoreView {
|
|
467
|
-
const cached = this.filteredStoreCache.get(modelUri);
|
|
468
|
-
if (cached && !this.dirtyFilteredStores.has(modelUri)) {
|
|
469
|
-
return cached;
|
|
470
|
-
}
|
|
471
|
-
const view = new MappedStoreView(this.reasoningStore.getStore(), queryContext.mappings);
|
|
472
|
-
this.filteredStoreCache.set(modelUri, view);
|
|
473
|
-
this.dirtyFilteredStores.delete(modelUri);
|
|
474
|
-
return view;
|
|
475
|
-
}
|
|
476
|
-
}
|