@unrdf/knowledge-engine 5.0.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/LICENSE +21 -0
- package/README.md +84 -0
- package/package.json +64 -0
- package/src/browser-shims.mjs +343 -0
- package/src/browser.mjs +910 -0
- package/src/canonicalize.mjs +414 -0
- package/src/condition-cache.mjs +109 -0
- package/src/condition-evaluator.mjs +722 -0
- package/src/dark-matter-core.mjs +742 -0
- package/src/define-hook.mjs +213 -0
- package/src/effect-sandbox-browser.mjs +283 -0
- package/src/effect-sandbox-worker.mjs +170 -0
- package/src/effect-sandbox.mjs +517 -0
- package/src/engines/index.mjs +11 -0
- package/src/engines/rdf-engine.mjs +299 -0
- package/src/file-resolver.mjs +387 -0
- package/src/hook-executor-batching.mjs +277 -0
- package/src/hook-executor.mjs +870 -0
- package/src/hook-management.mjs +150 -0
- package/src/index.mjs +93 -0
- package/src/ken-parliment.mjs +119 -0
- package/src/ken.mjs +149 -0
- package/src/knowledge-engine/builtin-rules.mjs +190 -0
- package/src/knowledge-engine/inference-engine.mjs +418 -0
- package/src/knowledge-engine/knowledge-engine.mjs +317 -0
- package/src/knowledge-engine/pattern-dsl.mjs +142 -0
- package/src/knowledge-engine/pattern-matcher.mjs +215 -0
- package/src/knowledge-engine/rules.mjs +184 -0
- package/src/knowledge-engine.mjs +319 -0
- package/src/knowledge-hook-engine.mjs +360 -0
- package/src/knowledge-hook-manager.mjs +469 -0
- package/src/knowledge-substrate-core.mjs +927 -0
- package/src/lite.mjs +222 -0
- package/src/lockchain-writer-browser.mjs +414 -0
- package/src/lockchain-writer.mjs +602 -0
- package/src/monitoring/andon-signals.mjs +775 -0
- package/src/observability.mjs +531 -0
- package/src/parse.mjs +290 -0
- package/src/performance-optimizer.mjs +678 -0
- package/src/policy-pack.mjs +572 -0
- package/src/query-cache.mjs +116 -0
- package/src/query-optimizer.mjs +1051 -0
- package/src/query.mjs +306 -0
- package/src/reason.mjs +350 -0
- package/src/resolution-layer.mjs +506 -0
- package/src/schemas.mjs +1063 -0
- package/src/security/error-sanitizer.mjs +257 -0
- package/src/security/path-validator.mjs +194 -0
- package/src/security/sandbox-restrictions.mjs +331 -0
- package/src/security-validator.mjs +389 -0
- package/src/store-cache.mjs +137 -0
- package/src/telemetry.mjs +167 -0
- package/src/transaction.mjs +810 -0
- package/src/utils/adaptive-monitor.mjs +746 -0
- package/src/utils/circuit-breaker.mjs +513 -0
- package/src/utils/edge-case-handler.mjs +503 -0
- package/src/utils/memory-manager.mjs +498 -0
- package/src/utils/ring-buffer.mjs +282 -0
- package/src/validate.mjs +319 -0
- package/src/validators/index.mjs +338 -0
package/src/query.mjs
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file SPARQL querying utilities.
|
|
3
|
+
* @module query
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createStore } from '@unrdf/oxigraph';
|
|
7
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
|
8
|
+
|
|
9
|
+
const tracer = trace.getTracer('unrdf');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run a SPARQL query against a store.
|
|
13
|
+
* @param {Store} store - The n3.Store to query against
|
|
14
|
+
* @param {string} sparql - The SPARQL query string
|
|
15
|
+
* @param {Object} [options] - Query options (for compatibility)
|
|
16
|
+
* @param {number} [options.limit] - Maximum number of results
|
|
17
|
+
* @returns {Promise<any>} Promise resolving to the query result
|
|
18
|
+
*
|
|
19
|
+
* @throws {Error} If query execution fails
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const store = new Store();
|
|
23
|
+
* // ... add quads to store
|
|
24
|
+
*
|
|
25
|
+
* const results = await query(store, `
|
|
26
|
+
* SELECT ?s ?o WHERE {
|
|
27
|
+
* ?s <http://example.org/knows> ?o
|
|
28
|
+
* }
|
|
29
|
+
* `);
|
|
30
|
+
* console.log(results); // Array of binding objects
|
|
31
|
+
*/
|
|
32
|
+
export async function query(store, sparql, _options = {}) {
|
|
33
|
+
if (!store || typeof store.getQuads !== 'function') {
|
|
34
|
+
throw new TypeError('query: store must be a valid Store instance');
|
|
35
|
+
}
|
|
36
|
+
if (typeof sparql !== 'string' || !sparql.trim()) {
|
|
37
|
+
throw new TypeError('query: sparql must be a non-empty string');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return tracer.startActiveSpan('query.sparql', async span => {
|
|
41
|
+
const startTime = performance.now();
|
|
42
|
+
try {
|
|
43
|
+
const queryType = sparql.trim().split(/\s+/)[0].toUpperCase();
|
|
44
|
+
span.setAttributes({
|
|
45
|
+
'query.type': queryType,
|
|
46
|
+
'query.length': sparql.length,
|
|
47
|
+
'query.store_size': store.size,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Convert n3.Store quads to substrate store (synchronous)
|
|
51
|
+
const quads = store.getQuads();
|
|
52
|
+
const rdfStore = createStore(Array.from(quads));
|
|
53
|
+
|
|
54
|
+
// Execute query synchronously
|
|
55
|
+
let result;
|
|
56
|
+
try {
|
|
57
|
+
const queryResult = rdfStore.query(sparql);
|
|
58
|
+
|
|
59
|
+
// Determine result type and format accordingly
|
|
60
|
+
if (Array.isArray(queryResult)) {
|
|
61
|
+
// SELECT query result (array of bindings)
|
|
62
|
+
if (
|
|
63
|
+
queryResult.length > 0 &&
|
|
64
|
+
typeof queryResult[0] === 'object' &&
|
|
65
|
+
!queryResult[0].subject
|
|
66
|
+
) {
|
|
67
|
+
// Already formatted bindings
|
|
68
|
+
result = queryResult;
|
|
69
|
+
} else {
|
|
70
|
+
// Check query type to determine formatting
|
|
71
|
+
if (queryType === 'CONSTRUCT' || queryType === 'DESCRIBE') {
|
|
72
|
+
// Return Oxigraph store directly with quads
|
|
73
|
+
result = rdfStore;
|
|
74
|
+
span.setAttribute('query.result_count', result.size);
|
|
75
|
+
} else {
|
|
76
|
+
// SELECT - format as bindings array
|
|
77
|
+
result = queryResult.map(item => {
|
|
78
|
+
if (item instanceof Map) {
|
|
79
|
+
// Handle Map objects from Oxigraph
|
|
80
|
+
const bindings = {};
|
|
81
|
+
for (const [key, val] of item.entries()) {
|
|
82
|
+
bindings[key] =
|
|
83
|
+
val && val.value ? val.value : val && val.toString ? val.toString() : val;
|
|
84
|
+
}
|
|
85
|
+
return bindings;
|
|
86
|
+
} else if (item && typeof item === 'object') {
|
|
87
|
+
return Object.fromEntries(
|
|
88
|
+
Object.entries(item).map(([k, v]) => [k, v && v.value ? v.value : v])
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return item;
|
|
92
|
+
});
|
|
93
|
+
span.setAttribute('query.result_count', result.length);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else if (typeof queryResult === 'boolean') {
|
|
97
|
+
// ASK query
|
|
98
|
+
result = queryResult;
|
|
99
|
+
span.setAttribute('query.result_type', 'boolean');
|
|
100
|
+
span.setAttribute('query.result', result);
|
|
101
|
+
} else {
|
|
102
|
+
// CONSTRUCT/DESCRIBE - return rdfStore directly (it's already an Oxigraph store)
|
|
103
|
+
result = rdfStore;
|
|
104
|
+
span.setAttribute('query.result_count', result.size);
|
|
105
|
+
}
|
|
106
|
+
} catch (engineError) {
|
|
107
|
+
// If query execution fails, provide helpful error
|
|
108
|
+
throw new Error(`Query execution failed: ${engineError.message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
112
|
+
const duration = performance.now() - startTime;
|
|
113
|
+
span.setAttribute('query.duration_ms', duration);
|
|
114
|
+
return result;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
span.recordException(error);
|
|
117
|
+
span.setStatus({
|
|
118
|
+
code: SpanStatusCode.ERROR,
|
|
119
|
+
message: error.message,
|
|
120
|
+
});
|
|
121
|
+
throw new Error(`SPARQL query failed: ${error.message}`);
|
|
122
|
+
} finally {
|
|
123
|
+
span.end();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Execute a SELECT query and return bindings.
|
|
130
|
+
* @param {Store} store - The store to query against
|
|
131
|
+
* @param {string} sparql - The SPARQL SELECT query
|
|
132
|
+
* @param {Object} [options] - Query options
|
|
133
|
+
* @returns {Promise<Array<Object>>} Promise resolving to array of binding objects
|
|
134
|
+
*
|
|
135
|
+
* @throws {Error} If query execution fails
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* const bindings = await select(store, `
|
|
139
|
+
* SELECT ?name ?age WHERE {
|
|
140
|
+
* ?person <http://example.org/name> ?name ;
|
|
141
|
+
* <http://example.org/age> ?age .
|
|
142
|
+
* }
|
|
143
|
+
* `);
|
|
144
|
+
*/
|
|
145
|
+
export async function select(store, sparql, options = {}) {
|
|
146
|
+
const result = await query(store, sparql, options);
|
|
147
|
+
if (Array.isArray(result)) {
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
throw new Error('SELECT query did not return bindings');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Execute an ASK query and return boolean result.
|
|
155
|
+
* @param {Store} store - The store to query against
|
|
156
|
+
* @param {string} sparql - The SPARQL ASK query
|
|
157
|
+
* @param {Object} [options] - Query options
|
|
158
|
+
* @returns {Promise<boolean>} Promise resolving to boolean result
|
|
159
|
+
*
|
|
160
|
+
* @throws {Error} If query execution fails
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const hasData = await ask(store, `
|
|
164
|
+
* ASK WHERE {
|
|
165
|
+
* ?s ?p ?o .
|
|
166
|
+
* }
|
|
167
|
+
* `);
|
|
168
|
+
*/
|
|
169
|
+
export async function ask(store, sparql, options = {}) {
|
|
170
|
+
const result = await query(store, sparql, options);
|
|
171
|
+
if (typeof result === 'boolean') {
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
throw new Error('ASK query did not return boolean result');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Execute a CONSTRUCT query and return a new store.
|
|
179
|
+
* @param {Store} store - The store to query against
|
|
180
|
+
* @param {string} sparql - The SPARQL CONSTRUCT query
|
|
181
|
+
* @param {Object} [options] - Query options
|
|
182
|
+
* @returns {Promise<Store>} Promise resolving to a new store with constructed quads
|
|
183
|
+
*
|
|
184
|
+
* @throws {Error} If query execution fails
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* const constructed = await construct(store, `
|
|
188
|
+
* CONSTRUCT {
|
|
189
|
+
* ?person <http://example.org/type> <http://example.org/Person> .
|
|
190
|
+
* } WHERE {
|
|
191
|
+
* ?person <http://example.org/name> ?name .
|
|
192
|
+
* }
|
|
193
|
+
* `);
|
|
194
|
+
*/
|
|
195
|
+
export async function construct(store, sparql, options = {}) {
|
|
196
|
+
const result = await query(store, sparql, options);
|
|
197
|
+
if (result && typeof result.getQuads === 'function') {
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
throw new Error('CONSTRUCT query did not return a store');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Execute a DESCRIBE query and return a new store.
|
|
205
|
+
* @param {Store} store - The store to query against
|
|
206
|
+
* @param {string} sparql - The SPARQL DESCRIBE query
|
|
207
|
+
* @param {Object} [options] - Query options
|
|
208
|
+
* @returns {Promise<Store>} Promise resolving to a new store with described quads
|
|
209
|
+
*
|
|
210
|
+
* @throws {Error} If query execution fails
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* const described = await describe(store, `
|
|
214
|
+
* DESCRIBE <http://example.org/alice>
|
|
215
|
+
* `);
|
|
216
|
+
*/
|
|
217
|
+
export async function describe(store, sparql, options = {}) {
|
|
218
|
+
const result = await query(store, sparql, options);
|
|
219
|
+
if (result && typeof result.getQuads === 'function') {
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
throw new Error('DESCRIBE query did not return a store');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Execute a SPARQL UPDATE operation (INSERT, DELETE, etc.).
|
|
227
|
+
* @param {Store} store - The store to update
|
|
228
|
+
* @param {string} sparql - The SPARQL UPDATE query
|
|
229
|
+
* @param {Object} [options] - Update options
|
|
230
|
+
* @returns {Promise<Store>} Promise resolving to the updated store
|
|
231
|
+
*
|
|
232
|
+
* @throws {Error} If update execution fails
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* const updated = await update(store, `
|
|
236
|
+
* INSERT DATA {
|
|
237
|
+
* <http://example.org/alice> <http://example.org/age> "30" .
|
|
238
|
+
* }
|
|
239
|
+
* `);
|
|
240
|
+
*/
|
|
241
|
+
export async function update(store, sparql, _options = {}) {
|
|
242
|
+
if (!store || typeof store.getQuads !== 'function') {
|
|
243
|
+
throw new TypeError('update: store must be a valid Store instance');
|
|
244
|
+
}
|
|
245
|
+
if (typeof sparql !== 'string' || !sparql.trim()) {
|
|
246
|
+
throw new TypeError('update: sparql must be a non-empty string');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// Convert n3.Store to substrate store for update
|
|
251
|
+
const quads = store.getQuads();
|
|
252
|
+
const rdfStore = createStore(Array.from(quads));
|
|
253
|
+
|
|
254
|
+
// Execute update synchronously
|
|
255
|
+
rdfStore.query(sparql);
|
|
256
|
+
|
|
257
|
+
// Return the updated store
|
|
258
|
+
return store;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throw new Error(`SPARQL update failed: ${error.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get query execution statistics.
|
|
266
|
+
* @param {Store} store - The store to query against
|
|
267
|
+
* @param {string} sparql - The SPARQL query
|
|
268
|
+
* @param {Object} [options] - Query options
|
|
269
|
+
* @returns {Promise<Object>} Promise resolving to execution statistics
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* const stats = await getQueryStats(store, sparql);
|
|
273
|
+
* console.log(`Execution time: ${stats.duration}ms`);
|
|
274
|
+
* console.log(`Result count: ${stats.resultCount}`);
|
|
275
|
+
*/
|
|
276
|
+
export async function getQueryStats(store, sparql, options = {}) {
|
|
277
|
+
const startTime = Date.now();
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const result = await query(store, sparql, options);
|
|
281
|
+
const endTime = Date.now();
|
|
282
|
+
|
|
283
|
+
let resultCount = 0;
|
|
284
|
+
if (Array.isArray(result)) {
|
|
285
|
+
resultCount = result.length;
|
|
286
|
+
} else if (result && typeof result.getQuads === 'function') {
|
|
287
|
+
resultCount = result.size;
|
|
288
|
+
} else if (typeof result === 'boolean') {
|
|
289
|
+
resultCount = 1;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
duration: endTime - startTime,
|
|
294
|
+
resultCount,
|
|
295
|
+
success: true,
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const endTime = Date.now();
|
|
299
|
+
return {
|
|
300
|
+
duration: endTime - startTime,
|
|
301
|
+
resultCount: 0,
|
|
302
|
+
success: false,
|
|
303
|
+
error: error.message,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
package/src/reason.mjs
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Reasoning support using N3 rules.
|
|
3
|
+
* @module reason
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Parser, Writer } from '@unrdf/core/rdf/n3-justified-only';
|
|
7
|
+
import { createStore } from '@unrdf/oxigraph'; // TODO: Replace with Oxigraph Store
|
|
8
|
+
|
|
9
|
+
// Dynamic import to avoid top-level await issues
|
|
10
|
+
let basicQuery;
|
|
11
|
+
const loadBasicQuery = async () => {
|
|
12
|
+
if (!basicQuery) {
|
|
13
|
+
const eyereasoner = await import('eyereasoner');
|
|
14
|
+
basicQuery = eyereasoner.basicQuery;
|
|
15
|
+
}
|
|
16
|
+
return basicQuery;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run forward-chaining reasoning with N3 rules.
|
|
21
|
+
* @param {Store} store - The store containing data to reason over
|
|
22
|
+
* @param {Store|string} rules - The store or Turtle string containing N3 rules
|
|
23
|
+
* @param {Object} [options] - Reasoning options
|
|
24
|
+
* @param {boolean} [options.includeOriginal] - Include original data in result
|
|
25
|
+
* @param {number} [options.maxIterations] - Maximum number of reasoning iterations
|
|
26
|
+
* @param {boolean} [options.debug] - Enable debug output
|
|
27
|
+
* @returns {Promise<Store>} Promise resolving to a new store containing original and inferred quads
|
|
28
|
+
*
|
|
29
|
+
* @throws {Error} If reasoning fails
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const dataStore = createStore();
|
|
33
|
+
* // ... add data quads to store
|
|
34
|
+
*
|
|
35
|
+
* const rulesTtl = `
|
|
36
|
+
* @prefix ex: <http://example.org/> .
|
|
37
|
+
* @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
38
|
+
*
|
|
39
|
+
* { ?x ex:parent ?y } => { ?x rdfs:subClassOf ?y } .
|
|
40
|
+
* `;
|
|
41
|
+
*
|
|
42
|
+
* const reasonedStore = await reason(dataStore, rulesTtl);
|
|
43
|
+
* console.log('Original quads:', dataStore.size);
|
|
44
|
+
* console.log('Reasoned quads:', reasonedStore.size);
|
|
45
|
+
*/
|
|
46
|
+
export async function reason(store, rules, options = {}) {
|
|
47
|
+
if (!store || typeof store.getQuads !== 'function') {
|
|
48
|
+
throw new TypeError('reason: store must be a valid Store instance');
|
|
49
|
+
}
|
|
50
|
+
if (!rules) {
|
|
51
|
+
throw new TypeError('reason: rules must be provided');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { includeOriginal = true, _maxIterations = 100, debug = false } = options;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Get data quads from store
|
|
58
|
+
const dataQuads = store.getQuads();
|
|
59
|
+
|
|
60
|
+
// Convert rules to quads if needed
|
|
61
|
+
let rulesQuads;
|
|
62
|
+
if (typeof rules === 'string') {
|
|
63
|
+
// Parse Turtle rules to quads
|
|
64
|
+
const parser = new Parser();
|
|
65
|
+
rulesQuads = parser.parse(rules);
|
|
66
|
+
} else if (rules && typeof rules.getQuads === 'function') {
|
|
67
|
+
rulesQuads = rules.getQuads();
|
|
68
|
+
} else {
|
|
69
|
+
throw new TypeError('reason: rules must be a Store or Turtle string');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (debug) {
|
|
73
|
+
console.log('Data quads:', dataQuads.length);
|
|
74
|
+
console.log('Rules quads:', rulesQuads.length);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Run reasoning using EYE reasoner
|
|
78
|
+
// Load basicQuery dynamically to avoid top-level await issues
|
|
79
|
+
const query = await loadBasicQuery();
|
|
80
|
+
|
|
81
|
+
// Combine data and rules as EYE expects
|
|
82
|
+
const combinedQuads = [...dataQuads, ...rulesQuads];
|
|
83
|
+
const { result } = await query(combinedQuads, []);
|
|
84
|
+
|
|
85
|
+
if (debug) {
|
|
86
|
+
console.log('Result quads:', result.length);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Create result store
|
|
90
|
+
const _resultStore = createStore(result);
|
|
91
|
+
|
|
92
|
+
// Combine original and inferred data if requested
|
|
93
|
+
if (includeOriginal) {
|
|
94
|
+
return createStore([...dataQuads, ...result]);
|
|
95
|
+
} else {
|
|
96
|
+
// Extract only inferred quads (not in original data)
|
|
97
|
+
const originalQuadSet = new Set(dataQuads.map(q => q.toString()));
|
|
98
|
+
const inferredQuads = result.filter(q => !originalQuadSet.has(q.toString()));
|
|
99
|
+
return createStore(inferredQuads);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`N3 reasoning failed: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Run reasoning with multiple rule sets.
|
|
108
|
+
* @param {Store} store - The store containing data to reason over
|
|
109
|
+
* @param {Array<Store|string>} rulesList - Array of stores or Turtle strings containing N3 rules
|
|
110
|
+
* @param {Object} [options] - Reasoning options
|
|
111
|
+
* @returns {Promise<Store>} Promise resolving to a new store with all reasoning results
|
|
112
|
+
*
|
|
113
|
+
* @throws {Error} If reasoning fails
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const rulesList = [
|
|
117
|
+
* rdfsRulesTtl,
|
|
118
|
+
* owlRulesTtl,
|
|
119
|
+
* customRulesTtl
|
|
120
|
+
* ];
|
|
121
|
+
*
|
|
122
|
+
* const reasonedStore = await reasonMultiple(store, rulesList);
|
|
123
|
+
*/
|
|
124
|
+
export async function reasonMultiple(store, rulesList, options = {}) {
|
|
125
|
+
if (!store || typeof store.getQuads !== 'function') {
|
|
126
|
+
throw new TypeError('reasonMultiple: store must be a valid Store instance');
|
|
127
|
+
}
|
|
128
|
+
if (!Array.isArray(rulesList) || rulesList.length === 0) {
|
|
129
|
+
throw new TypeError('reasonMultiple: rulesList must be a non-empty array');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
let currentStore = store;
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < rulesList.length; i++) {
|
|
136
|
+
const rules = rulesList[i];
|
|
137
|
+
if (options.debug) {
|
|
138
|
+
console.log(`Applying rule set ${i + 1}/${rulesList.length}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
currentStore = await reason(currentStore, rules, {
|
|
142
|
+
...options,
|
|
143
|
+
includeOriginal: true, // Always include original data for subsequent rule applications
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return currentStore;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
throw new Error(`Multiple N3 reasoning failed: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Extract only the newly inferred quads from reasoning.
|
|
155
|
+
* @param {Store} originalStore - The original store before reasoning
|
|
156
|
+
* @param {Store} reasonedStore - The store after reasoning
|
|
157
|
+
* @returns {Store} Store containing only the newly inferred quads
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* const originalStore = createStore();
|
|
161
|
+
* // ... add original quads
|
|
162
|
+
*
|
|
163
|
+
* const reasonedStore = await reason(originalStore, rules);
|
|
164
|
+
* const inferredOnly = extractInferred(originalStore, reasonedStore);
|
|
165
|
+
* console.log('Newly inferred quads:', inferredOnly.size);
|
|
166
|
+
*/
|
|
167
|
+
export function extractInferred(originalStore, reasonedStore) {
|
|
168
|
+
if (!originalStore || typeof originalStore.getQuads !== 'function') {
|
|
169
|
+
throw new TypeError('extractInferred: originalStore must be a valid Store instance');
|
|
170
|
+
}
|
|
171
|
+
if (!reasonedStore || typeof reasonedStore.getQuads !== 'function') {
|
|
172
|
+
throw new TypeError('extractInferred: reasonedStore must be a valid Store instance');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const originalQuads = new Set(originalStore.getQuads().map(q => q.toString()));
|
|
176
|
+
const reasonedQuads = reasonedStore.getQuads();
|
|
177
|
+
|
|
178
|
+
const inferredQuads = reasonedQuads.filter(q => !originalQuads.has(q.toString()));
|
|
179
|
+
|
|
180
|
+
return createStore(inferredQuads);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get reasoning statistics.
|
|
185
|
+
* @param {Store} originalStore - The original store before reasoning
|
|
186
|
+
* @param {Store} reasonedStore - The store after reasoning
|
|
187
|
+
* @returns {Object} Reasoning statistics
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const stats = getReasoningStats(originalStore, reasonedStore);
|
|
191
|
+
* console.log(`Original quads: ${stats.originalCount}`);
|
|
192
|
+
* console.log(`Inferred quads: ${stats.inferredCount}`);
|
|
193
|
+
* console.log(`Total quads: ${stats.totalCount}`);
|
|
194
|
+
* console.log(`Inference ratio: ${stats.inferenceRatio}`);
|
|
195
|
+
*/
|
|
196
|
+
export function getReasoningStats(originalStore, reasonedStore) {
|
|
197
|
+
if (!originalStore || typeof originalStore.getQuads !== 'function') {
|
|
198
|
+
throw new TypeError('getReasoningStats: originalStore must be a valid Store instance');
|
|
199
|
+
}
|
|
200
|
+
if (!reasonedStore || typeof reasonedStore.getQuads !== 'function') {
|
|
201
|
+
throw new TypeError('getReasoningStats: reasonedStore must be a valid Store instance');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const originalCount = originalStore.size;
|
|
205
|
+
const totalCount = reasonedStore.size;
|
|
206
|
+
const inferredCount = totalCount - originalCount;
|
|
207
|
+
const inferenceRatio = originalCount > 0 ? inferredCount / originalCount : 0;
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
originalCount,
|
|
211
|
+
inferredCount,
|
|
212
|
+
totalCount,
|
|
213
|
+
inferenceRatio,
|
|
214
|
+
hasInferences: inferredCount > 0,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate N3 rules syntax.
|
|
220
|
+
* @param {Store|string} rules - The store or Turtle string containing N3 rules
|
|
221
|
+
* @returns {Object} Validation result
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* const validation = validateRules(rulesTtl);
|
|
225
|
+
* if (!validation.valid) {
|
|
226
|
+
* console.log('Rule validation errors:', validation.errors);
|
|
227
|
+
* }
|
|
228
|
+
*/
|
|
229
|
+
export function validateRules(rules) {
|
|
230
|
+
if (!rules) {
|
|
231
|
+
return { valid: false, errors: ['Rules must be provided'] };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
let rulesTurtle;
|
|
236
|
+
if (typeof rules === 'string') {
|
|
237
|
+
rulesTurtle = rules;
|
|
238
|
+
} else if (rules && typeof rules.getQuads === 'function') {
|
|
239
|
+
const writer = new Writer({ format: 'Turtle' });
|
|
240
|
+
writer.addQuads(rules.getQuads());
|
|
241
|
+
rulesTurtle = writer.quadsToString(rules.getQuads());
|
|
242
|
+
} else {
|
|
243
|
+
return {
|
|
244
|
+
valid: false,
|
|
245
|
+
errors: ['Rules must be a Store or Turtle string'],
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Basic syntax validation
|
|
250
|
+
const parser = new Parser();
|
|
251
|
+
parser.parse(rulesTurtle);
|
|
252
|
+
|
|
253
|
+
return { valid: true, errors: [] };
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return { valid: false, errors: [error.message] };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create a reasoning session with persistent state.
|
|
261
|
+
* @param {Store} initialStore - Initial store state
|
|
262
|
+
* @param {Store|string} rules - N3 rules to apply
|
|
263
|
+
* @param {Object} [options] - Session options
|
|
264
|
+
* @returns {Object} Reasoning session object
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* const session = createReasoningSession(store, rules);
|
|
268
|
+
*
|
|
269
|
+
* // Add new data
|
|
270
|
+
* session.addData(newQuads);
|
|
271
|
+
*
|
|
272
|
+
* // Apply reasoning
|
|
273
|
+
* await session.reason();
|
|
274
|
+
*
|
|
275
|
+
* // Get current state
|
|
276
|
+
* const currentState = session.getState();
|
|
277
|
+
*/
|
|
278
|
+
export function createReasoningSession(initialStore, rules, options = {}) {
|
|
279
|
+
if (!initialStore || typeof initialStore.getQuads !== 'function') {
|
|
280
|
+
throw new TypeError('createReasoningSession: initialStore must be a valid Store instance');
|
|
281
|
+
}
|
|
282
|
+
if (!rules) {
|
|
283
|
+
throw new TypeError('createReasoningSession: rules must be provided');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let currentStore = createStore(initialStore.getQuads());
|
|
287
|
+
const sessionRules = rules;
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
/**
|
|
291
|
+
* Add new data to the session.
|
|
292
|
+
* @param {Array|Quad} quads - Quads to add
|
|
293
|
+
*/
|
|
294
|
+
addData(quads) {
|
|
295
|
+
if (Array.isArray(quads)) {
|
|
296
|
+
currentStore.addQuads(quads);
|
|
297
|
+
} else {
|
|
298
|
+
currentStore.add(quads);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Remove data from the session.
|
|
304
|
+
* @param {Array|Quad} quads - Quads to remove
|
|
305
|
+
*/
|
|
306
|
+
removeData(quads) {
|
|
307
|
+
if (Array.isArray(quads)) {
|
|
308
|
+
currentStore.removeQuads(quads);
|
|
309
|
+
} else {
|
|
310
|
+
currentStore.delete(quads);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Apply reasoning to current state.
|
|
316
|
+
* @param {Object} [reasonOptions] - Reasoning options
|
|
317
|
+
* @returns {Promise<Store>} Updated store
|
|
318
|
+
*/
|
|
319
|
+
async reason(reasonOptions = {}) {
|
|
320
|
+
currentStore = await reason(currentStore, sessionRules, {
|
|
321
|
+
...options,
|
|
322
|
+
...reasonOptions,
|
|
323
|
+
});
|
|
324
|
+
return currentStore;
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get current store state.
|
|
329
|
+
* @returns {Store} Current store
|
|
330
|
+
*/
|
|
331
|
+
getState() {
|
|
332
|
+
return createStore(currentStore.getQuads());
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Reset to initial state.
|
|
337
|
+
*/
|
|
338
|
+
reset() {
|
|
339
|
+
currentStore = createStore(initialStore.getQuads());
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get session statistics.
|
|
344
|
+
* @returns {Object} Session statistics
|
|
345
|
+
*/
|
|
346
|
+
getStats() {
|
|
347
|
+
return getReasoningStats(initialStore, currentStore);
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|