@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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +64 -0
  4. package/src/browser-shims.mjs +343 -0
  5. package/src/browser.mjs +910 -0
  6. package/src/canonicalize.mjs +414 -0
  7. package/src/condition-cache.mjs +109 -0
  8. package/src/condition-evaluator.mjs +722 -0
  9. package/src/dark-matter-core.mjs +742 -0
  10. package/src/define-hook.mjs +213 -0
  11. package/src/effect-sandbox-browser.mjs +283 -0
  12. package/src/effect-sandbox-worker.mjs +170 -0
  13. package/src/effect-sandbox.mjs +517 -0
  14. package/src/engines/index.mjs +11 -0
  15. package/src/engines/rdf-engine.mjs +299 -0
  16. package/src/file-resolver.mjs +387 -0
  17. package/src/hook-executor-batching.mjs +277 -0
  18. package/src/hook-executor.mjs +870 -0
  19. package/src/hook-management.mjs +150 -0
  20. package/src/index.mjs +93 -0
  21. package/src/ken-parliment.mjs +119 -0
  22. package/src/ken.mjs +149 -0
  23. package/src/knowledge-engine/builtin-rules.mjs +190 -0
  24. package/src/knowledge-engine/inference-engine.mjs +418 -0
  25. package/src/knowledge-engine/knowledge-engine.mjs +317 -0
  26. package/src/knowledge-engine/pattern-dsl.mjs +142 -0
  27. package/src/knowledge-engine/pattern-matcher.mjs +215 -0
  28. package/src/knowledge-engine/rules.mjs +184 -0
  29. package/src/knowledge-engine.mjs +319 -0
  30. package/src/knowledge-hook-engine.mjs +360 -0
  31. package/src/knowledge-hook-manager.mjs +469 -0
  32. package/src/knowledge-substrate-core.mjs +927 -0
  33. package/src/lite.mjs +222 -0
  34. package/src/lockchain-writer-browser.mjs +414 -0
  35. package/src/lockchain-writer.mjs +602 -0
  36. package/src/monitoring/andon-signals.mjs +775 -0
  37. package/src/observability.mjs +531 -0
  38. package/src/parse.mjs +290 -0
  39. package/src/performance-optimizer.mjs +678 -0
  40. package/src/policy-pack.mjs +572 -0
  41. package/src/query-cache.mjs +116 -0
  42. package/src/query-optimizer.mjs +1051 -0
  43. package/src/query.mjs +306 -0
  44. package/src/reason.mjs +350 -0
  45. package/src/resolution-layer.mjs +506 -0
  46. package/src/schemas.mjs +1063 -0
  47. package/src/security/error-sanitizer.mjs +257 -0
  48. package/src/security/path-validator.mjs +194 -0
  49. package/src/security/sandbox-restrictions.mjs +331 -0
  50. package/src/security-validator.mjs +389 -0
  51. package/src/store-cache.mjs +137 -0
  52. package/src/telemetry.mjs +167 -0
  53. package/src/transaction.mjs +810 -0
  54. package/src/utils/adaptive-monitor.mjs +746 -0
  55. package/src/utils/circuit-breaker.mjs +513 -0
  56. package/src/utils/edge-case-handler.mjs +503 -0
  57. package/src/utils/memory-manager.mjs +498 -0
  58. package/src/utils/ring-buffer.mjs +282 -0
  59. package/src/validate.mjs +319 -0
  60. package/src/validators/index.mjs +338 -0
@@ -0,0 +1,317 @@
1
+ /**
2
+ * @file Knowledge Engine Class
3
+ * @module @unrdf/knowledge-engine/knowledge-engine
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
8
+ import { getBuiltinRules } from './builtin-rules.mjs';
9
+
10
+ const { namedNode } = DataFactory;
11
+
12
+ /**
13
+ * @typedef {Object} KnowledgeEngineOptions
14
+ * @property {import('n3').Store} store - N3 Store for RDF data
15
+ * @property {Array<Rule>} [rules] - Additional custom rules
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} InferenceStats
20
+ * @property {number} triplesInferred - Number of triples inferred
21
+ * @property {number} iterations - Number of inference iterations
22
+ * @property {number} duration - Duration in milliseconds
23
+ */
24
+
25
+ // Namespace prefixes
26
+ const NAMESPACES = {
27
+ 'rdf:': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
28
+ 'rdfs:': 'http://www.w3.org/2000/01/rdf-schema#',
29
+ 'owl:': 'http://www.w3.org/2002/07/owl#',
30
+ };
31
+
32
+ /**
33
+ * Knowledge Engine - Rule-based inference for RDF graphs
34
+ */
35
+ export class KnowledgeEngine {
36
+ /**
37
+ * Create a new Knowledge Engine
38
+ * @param {KnowledgeEngineOptions} options
39
+ */
40
+ constructor(options) {
41
+ const schema = z.object({
42
+ store: z.any(),
43
+ rules: z.array(z.any()).optional(),
44
+ });
45
+
46
+ const validated = schema.parse(options);
47
+
48
+ // Validate store has required methods
49
+ if (
50
+ !validated.store ||
51
+ typeof validated.store.addQuad !== 'function' ||
52
+ typeof validated.store.getQuads !== 'function'
53
+ ) {
54
+ throw new Error('Store must have addQuad and getQuads methods');
55
+ }
56
+
57
+ this.store = validated.store;
58
+ this.rules = validated.rules || getBuiltinRules();
59
+ }
60
+
61
+ /**
62
+ * Expand prefixed URIs to full URIs
63
+ * @param {string} value - Prefixed or full URI
64
+ * @returns {string} Full URI
65
+ */
66
+ expandPrefix(value) {
67
+ for (const [prefix, namespace] of Object.entries(NAMESPACES)) {
68
+ if (value.startsWith(prefix)) {
69
+ return value.replace(prefix, namespace);
70
+ }
71
+ }
72
+ return value;
73
+ }
74
+
75
+ /**
76
+ * Run inference and materialize inferred triples into the store
77
+ * @returns {InferenceStats} Inference statistics
78
+ */
79
+ materialize() {
80
+ const startTime = Date.now();
81
+ let triplesInferred = 0;
82
+ let iterations = 0;
83
+ const maxIterations = 100;
84
+
85
+ let changed = true;
86
+ while (changed && iterations < maxIterations) {
87
+ changed = false;
88
+ iterations++;
89
+
90
+ const beforeCount = this.store.size;
91
+
92
+ for (const rule of this.rules) {
93
+ const newTriples = this.applyRule(rule);
94
+ triplesInferred += newTriples;
95
+ if (newTriples > 0) {
96
+ changed = true;
97
+ }
98
+ }
99
+
100
+ const afterCount = this.store.size;
101
+ const actualNew = afterCount - beforeCount;
102
+
103
+ if (actualNew === 0) {
104
+ changed = false;
105
+ }
106
+ }
107
+
108
+ const duration = Date.now() - startTime;
109
+
110
+ return {
111
+ triplesInferred,
112
+ iterations,
113
+ duration,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Apply a single rule to the store
119
+ * @param {Object} rule - Rule to apply
120
+ * @returns {number} Number of new triples inferred
121
+ */
122
+ applyRule(rule) {
123
+ let inferredCount = 0;
124
+ const patterns = Array.isArray(rule.pattern) ? rule.pattern : [rule.pattern];
125
+ const consequents = Array.isArray(rule.consequent) ? rule.consequent : [rule.consequent];
126
+
127
+ // Find all binding sets that match the pattern
128
+ const bindingSets = this.findBindings(patterns);
129
+
130
+ // For each binding set, generate consequent triples
131
+ for (const bindings of bindingSets) {
132
+ for (const consequent of consequents) {
133
+ const triple = this.instantiateConsequent(consequent, bindings);
134
+ if (triple && this.addTriple(triple)) {
135
+ inferredCount++;
136
+ }
137
+ }
138
+ }
139
+
140
+ return inferredCount;
141
+ }
142
+
143
+ /**
144
+ * Find all binding sets that match the patterns
145
+ * @param {Array<Object>} patterns - Patterns to match
146
+ * @returns {Array<Object>} Array of variable bindings
147
+ */
148
+ findBindings(patterns) {
149
+ if (patterns.length === 0) return [];
150
+ if (patterns.length === 1) {
151
+ return this.matchPattern(patterns[0]);
152
+ }
153
+
154
+ // Match first pattern
155
+ let bindings = this.matchPattern(patterns[0]);
156
+
157
+ // Join with remaining patterns
158
+ for (let i = 1; i < patterns.length; i++) {
159
+ const nextBindings = [];
160
+ for (const binding of bindings) {
161
+ const matches = this.matchPatternWithBindings(patterns[i], binding);
162
+ nextBindings.push(...matches);
163
+ }
164
+ bindings = nextBindings;
165
+ }
166
+
167
+ return bindings;
168
+ }
169
+
170
+ /**
171
+ * Match a single pattern against the store
172
+ * @param {Object} pattern - Pattern to match
173
+ * @returns {Array<Object>} Array of variable bindings
174
+ */
175
+ matchPattern(pattern) {
176
+ const bindings = [];
177
+ const subj = this.expandPrefix(pattern.subject);
178
+ const pred = this.expandPrefix(pattern.predicate);
179
+ const obj = this.expandPrefix(pattern.object);
180
+
181
+ const subjVar = subj.startsWith('?');
182
+ const predVar = pred.startsWith('?');
183
+ const objVar = obj.startsWith('?');
184
+
185
+ const quads = this.store.getQuads(
186
+ subjVar ? null : namedNode(subj),
187
+ predVar ? null : namedNode(pred),
188
+ objVar ? null : namedNode(obj),
189
+ null
190
+ );
191
+
192
+ for (const quad of quads) {
193
+ const binding = {};
194
+ if (subjVar) binding[subj] = quad.subject;
195
+ if (predVar) binding[pred] = quad.predicate;
196
+ if (objVar) binding[obj] = quad.object;
197
+ bindings.push(binding);
198
+ }
199
+
200
+ return bindings;
201
+ }
202
+
203
+ /**
204
+ * Match a pattern with existing bindings
205
+ * @param {Object} pattern - Pattern to match
206
+ * @param {Object} existingBindings - Existing variable bindings
207
+ * @returns {Array<Object>} Array of extended variable bindings
208
+ */
209
+ matchPatternWithBindings(pattern, existingBindings) {
210
+ const bindings = [];
211
+ const subj = this.expandPrefix(pattern.subject);
212
+ const pred = this.expandPrefix(pattern.predicate);
213
+ const obj = this.expandPrefix(pattern.object);
214
+
215
+ const subjVal = subj.startsWith('?') ? existingBindings[subj] : namedNode(subj);
216
+ const predVal = pred.startsWith('?') ? existingBindings[pred] : namedNode(pred);
217
+ const objVal = obj.startsWith('?') ? existingBindings[obj] : namedNode(obj);
218
+
219
+ const quads = this.store.getQuads(subjVal || null, predVal || null, objVal || null, null);
220
+
221
+ for (const quad of quads) {
222
+ const binding = { ...existingBindings };
223
+ let consistent = true;
224
+
225
+ if (subj.startsWith('?')) {
226
+ if (existingBindings[subj] && !quad.subject.equals(existingBindings[subj])) {
227
+ consistent = false;
228
+ } else {
229
+ binding[subj] = quad.subject;
230
+ }
231
+ }
232
+
233
+ if (pred.startsWith('?')) {
234
+ if (existingBindings[pred] && !quad.predicate.equals(existingBindings[pred])) {
235
+ consistent = false;
236
+ } else {
237
+ binding[pred] = quad.predicate;
238
+ }
239
+ }
240
+
241
+ if (obj.startsWith('?')) {
242
+ if (existingBindings[obj] && !quad.object.equals(existingBindings[obj])) {
243
+ consistent = false;
244
+ } else {
245
+ binding[obj] = quad.object;
246
+ }
247
+ }
248
+
249
+ if (consistent) {
250
+ bindings.push(binding);
251
+ }
252
+ }
253
+
254
+ return bindings;
255
+ }
256
+
257
+ /**
258
+ * Instantiate a consequent with variable bindings
259
+ * @param {Object} consequent - Consequent pattern
260
+ * @param {Object} bindings - Variable bindings
261
+ * @returns {Object|null} Instantiated triple or null
262
+ */
263
+ instantiateConsequent(consequent, bindings) {
264
+ const subj = this.expandPrefix(consequent.subject);
265
+ const pred = this.expandPrefix(consequent.predicate);
266
+ const obj = this.expandPrefix(consequent.object);
267
+
268
+ const subjTerm = subj.startsWith('?') ? bindings[subj] : namedNode(subj);
269
+ const predTerm = pred.startsWith('?') ? bindings[pred] : namedNode(pred);
270
+ const objTerm = obj.startsWith('?') ? bindings[obj] : namedNode(obj);
271
+
272
+ if (!subjTerm || !predTerm || !objTerm) {
273
+ return null;
274
+ }
275
+
276
+ return { subject: subjTerm, predicate: predTerm, object: objTerm };
277
+ }
278
+
279
+ /**
280
+ * Add a triple to the store if it doesn't already exist
281
+ * @param {Object} triple - Triple to add
282
+ * @returns {boolean} True if added, false if already exists
283
+ */
284
+ addTriple(triple) {
285
+ const existing = this.store.getQuads(triple.subject, triple.predicate, triple.object, null);
286
+ if (existing.length > 0) {
287
+ return false;
288
+ }
289
+ this.store.addQuad(triple.subject, triple.predicate, triple.object);
290
+ return true;
291
+ }
292
+
293
+ /**
294
+ * Add a custom rule to the engine
295
+ * @param {Rule} rule - Rule to add
296
+ */
297
+ addRule(rule) {
298
+ this.rules.push(rule);
299
+ }
300
+
301
+ /**
302
+ * Get all rules in the engine
303
+ * @returns {Array<Rule>} All rules
304
+ */
305
+ getRules() {
306
+ return this.rules;
307
+ }
308
+
309
+ /**
310
+ * Clear all inferred triples from the store
311
+ */
312
+ clear() {
313
+ // Note: This would need additional tracking to distinguish
314
+ // inferred triples from asserted triples
315
+ // For now, this is a no-op
316
+ }
317
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @file Pattern DSL - Simple pattern parsing
3
+ * @module @unrdf/knowledge-engine/pattern-dsl
4
+ */
5
+
6
+ /**
7
+ * Parse a simple pattern string
8
+ * Format: "?subject predicate ?object"
9
+ *
10
+ * @param {string} patternString - Pattern string to parse
11
+ * @returns {Object} Parsed pattern object
12
+ *
13
+ * @example
14
+ * const pattern = parsePattern("?person rdf:type foaf:Person");
15
+ * // Returns: { subject: '?person', predicate: 'rdf:type', object: 'foaf:Person' }
16
+ */
17
+ export function parsePattern(patternString) {
18
+ const trimmed = patternString.trim();
19
+ const parts = trimmed.split(/\s+/);
20
+
21
+ if (parts.length < 3) {
22
+ throw new Error(
23
+ `Invalid pattern: "${patternString}". Expected format: "subject predicate object"`
24
+ );
25
+ }
26
+
27
+ return {
28
+ subject: parts[0],
29
+ predicate: parts[1],
30
+ object: parts.slice(2).join(' '),
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Convert a pattern to SPARQL WHERE clause
36
+ *
37
+ * @param {Object} pattern - Pattern object
38
+ * @returns {string} SPARQL pattern string
39
+ *
40
+ * @example
41
+ * const sparql = patternToSparql({ subject: '?x', predicate: 'rdf:type', object: 'foaf:Person' });
42
+ * // Returns: "?x rdf:type foaf:Person ."
43
+ */
44
+ export function patternToSparql(pattern) {
45
+ const subject = formatTerm(pattern.subject);
46
+ const predicate = formatTerm(pattern.predicate);
47
+ const object = formatTerm(pattern.object);
48
+
49
+ return `${subject} ${predicate} ${object} .`;
50
+ }
51
+
52
+ /**
53
+ * Format a term for SPARQL
54
+ *
55
+ * @param {string|Object} term - Term to format
56
+ * @returns {string} Formatted term
57
+ */
58
+ function formatTerm(term) {
59
+ if (!term) return '?_';
60
+
61
+ if (typeof term === 'object' && term.value) {
62
+ return formatTerm(term.value);
63
+ }
64
+
65
+ const str = String(term);
66
+
67
+ if (str.startsWith('?')) {
68
+ return str;
69
+ }
70
+
71
+ if (str.startsWith('http://') || str.startsWith('https://')) {
72
+ return `<${str}>`;
73
+ }
74
+
75
+ if (str.includes(':')) {
76
+ return str;
77
+ }
78
+
79
+ return `"${str}"`;
80
+ }
81
+
82
+ /**
83
+ * Parse multiple patterns from a multi-line string
84
+ *
85
+ * @param {string} patternsString - Multi-line pattern string
86
+ * @returns {Object[]} Array of parsed patterns
87
+ *
88
+ * @example
89
+ * const patterns = parsePatterns(`
90
+ * ?x rdf:type foaf:Person
91
+ * ?x foaf:name ?name
92
+ * `);
93
+ */
94
+ export function parsePatterns(patternsString) {
95
+ const lines = patternsString
96
+ .split('\n')
97
+ .map(line => line.trim())
98
+ .filter(line => line && !line.startsWith('#'));
99
+
100
+ return lines.map(parsePattern);
101
+ }
102
+
103
+ /**
104
+ * Build a pattern object from components
105
+ *
106
+ * @param {string} subject - Subject term
107
+ * @param {string} predicate - Predicate term
108
+ * @param {string} object - Object term
109
+ * @param {string} [graph] - Optional graph term
110
+ * @returns {Object} Pattern object
111
+ *
112
+ * @example
113
+ * const pattern = buildPattern('?x', 'rdf:type', 'foaf:Person');
114
+ */
115
+ export function buildPattern(subject, predicate, object, graph = null) {
116
+ const pattern = { subject, predicate, object };
117
+
118
+ if (graph) {
119
+ pattern.graph = graph;
120
+ }
121
+
122
+ return pattern;
123
+ }
124
+
125
+ /**
126
+ * Validate a pattern object
127
+ *
128
+ * @param {Object} pattern - Pattern to validate
129
+ * @returns {boolean} True if valid
130
+ *
131
+ * @example
132
+ * if (isValidPattern(pattern)) {
133
+ * console.log('Pattern is valid');
134
+ * }
135
+ */
136
+ export function isValidPattern(pattern) {
137
+ if (!pattern || typeof pattern !== 'object') {
138
+ return false;
139
+ }
140
+
141
+ return Boolean(pattern.subject && pattern.predicate && pattern.object);
142
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * @file Pattern Matching - Find quads matching triple patterns
3
+ * @module @unrdf/knowledge-engine/pattern-matcher
4
+ */
5
+
6
+ import { getQuads } from '@unrdf/core';
7
+
8
+ /**
9
+ * @typedef {import('n3').Quad} Quad
10
+ * @typedef {import('n3').Term} Term
11
+ * @typedef {import('n3').Store} Store
12
+ */
13
+
14
+ /**
15
+ * Match a pattern against the store
16
+ *
17
+ * @param {Store} store - RDF store
18
+ * @param {Object} pattern - Pattern to match
19
+ * @param {string|Object} [pattern.subject] - Subject pattern
20
+ * @param {string|Object} [pattern.predicate] - Predicate pattern
21
+ * @param {string|Object} [pattern.object] - Object pattern
22
+ * @param {string|Object} [pattern.graph] - Graph pattern
23
+ * @returns {Quad[]} Array of matching quads
24
+ *
25
+ * @example
26
+ * const matches = matchPattern(store, {
27
+ * subject: null,
28
+ * predicate: 'rdf:type',
29
+ * object: 'foaf:Person'
30
+ * });
31
+ */
32
+ export function matchPattern(store, pattern) {
33
+ const subject = pattern.subject && !isVariable(pattern.subject) ? pattern.subject : null;
34
+ const predicate = pattern.predicate && !isVariable(pattern.predicate) ? pattern.predicate : null;
35
+ const object = pattern.object && !isVariable(pattern.object) ? pattern.object : null;
36
+ const graph = pattern.graph && !isVariable(pattern.graph) ? pattern.graph : null;
37
+
38
+ return getQuads(store, subject, predicate, object, graph);
39
+ }
40
+
41
+ /**
42
+ * Match a pattern and return variable bindings
43
+ *
44
+ * @param {Store} store - RDF store
45
+ * @param {Object} pattern - Pattern to match
46
+ * @returns {Object[]} Array of binding objects
47
+ *
48
+ * @example
49
+ * const bindings = matchPatternWithBindings(store, {
50
+ * subject: '?person',
51
+ * predicate: 'rdf:type',
52
+ * object: 'foaf:Person'
53
+ * });
54
+ * // Returns: [{ person: <http://example.org/alice> }, ...]
55
+ */
56
+ export function matchPatternWithBindings(store, pattern) {
57
+ const matches = matchPattern(store, pattern);
58
+ const bindings = [];
59
+
60
+ for (const quad of matches) {
61
+ const binding = {};
62
+
63
+ if (isVariable(pattern.subject)) {
64
+ const varName = getVariableName(pattern.subject);
65
+ binding[varName] = quad.subject;
66
+ }
67
+
68
+ if (isVariable(pattern.predicate)) {
69
+ const varName = getVariableName(pattern.predicate);
70
+ binding[varName] = quad.predicate;
71
+ }
72
+
73
+ if (isVariable(pattern.object)) {
74
+ const varName = getVariableName(pattern.object);
75
+ binding[varName] = quad.object;
76
+ }
77
+
78
+ if (pattern.graph && isVariable(pattern.graph)) {
79
+ const varName = getVariableName(pattern.graph);
80
+ binding[varName] = quad.graph;
81
+ }
82
+
83
+ bindings.push(binding);
84
+ }
85
+
86
+ return bindings;
87
+ }
88
+
89
+ /**
90
+ * Check if a pattern has at least one match
91
+ *
92
+ * @param {Store} store - RDF store
93
+ * @param {Object} pattern - Pattern to match
94
+ * @returns {boolean} True if pattern matches at least one quad
95
+ *
96
+ * @example
97
+ * if (hasMatch(store, { subject: '?x', predicate: 'rdf:type', object: 'foaf:Person' })) {
98
+ * console.log('Found at least one person');
99
+ * }
100
+ */
101
+ export function hasMatch(store, pattern) {
102
+ const matches = matchPattern(store, pattern);
103
+ return matches.length > 0;
104
+ }
105
+
106
+ /**
107
+ * Match multiple patterns (AND conjunction)
108
+ *
109
+ * @param {Store} store - RDF store
110
+ * @param {Object[]} patterns - Array of patterns
111
+ * @returns {Object[]} Array of binding sets that satisfy all patterns
112
+ *
113
+ * @example
114
+ * const bindings = matchMultiplePatterns(store, [
115
+ * { subject: '?x', predicate: 'rdf:type', object: 'foaf:Person' },
116
+ * { subject: '?x', predicate: 'foaf:name', object: '?name' }
117
+ * ]);
118
+ */
119
+ export function matchMultiplePatterns(store, patterns) {
120
+ if (patterns.length === 0) {
121
+ return [];
122
+ }
123
+
124
+ if (patterns.length === 1) {
125
+ return matchPatternWithBindings(store, patterns[0]);
126
+ }
127
+
128
+ let currentBindings = matchPatternWithBindings(store, patterns[0]);
129
+
130
+ for (let i = 1; i < patterns.length; i++) {
131
+ const pattern = patterns[i];
132
+ const newBindings = [];
133
+
134
+ for (const binding of currentBindings) {
135
+ const instantiatedPattern = instantiatePattern(pattern, binding);
136
+ const matches = matchPatternWithBindings(store, instantiatedPattern);
137
+
138
+ for (const match of matches) {
139
+ const merged = { ...binding, ...match };
140
+ newBindings.push(merged);
141
+ }
142
+ }
143
+
144
+ currentBindings = newBindings;
145
+
146
+ if (currentBindings.length === 0) {
147
+ break;
148
+ }
149
+ }
150
+
151
+ return currentBindings;
152
+ }
153
+
154
+ /**
155
+ * Instantiate a pattern with variable bindings
156
+ *
157
+ * @param {Object} pattern - Pattern template
158
+ * @param {Object} bindings - Variable bindings
159
+ * @returns {Object} Pattern with variables replaced
160
+ */
161
+ function instantiatePattern(pattern, bindings) {
162
+ return {
163
+ subject: instantiateTerm(pattern.subject, bindings),
164
+ predicate: instantiateTerm(pattern.predicate, bindings),
165
+ object: instantiateTerm(pattern.object, bindings),
166
+ graph: pattern.graph ? instantiateTerm(pattern.graph, bindings) : null,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Instantiate a term with variable bindings
172
+ *
173
+ * @param {string|Object} term - Term or variable
174
+ * @param {Object} bindings - Variable bindings
175
+ * @returns {Object|string} Instantiated term
176
+ */
177
+ function instantiateTerm(term, bindings) {
178
+ if (!isVariable(term)) {
179
+ return term;
180
+ }
181
+
182
+ const varName = getVariableName(term);
183
+ return bindings[varName] || term;
184
+ }
185
+
186
+ /**
187
+ * Check if a term is a variable
188
+ *
189
+ * @param {string|Object} term - Term to check
190
+ * @returns {boolean} True if term is a variable
191
+ */
192
+ function isVariable(term) {
193
+ if (!term) return false;
194
+
195
+ if (typeof term === 'object' && term.type === 'variable') {
196
+ return true;
197
+ }
198
+
199
+ const str = String(term);
200
+ return str.startsWith('?');
201
+ }
202
+
203
+ /**
204
+ * Get variable name (remove '?' prefix)
205
+ *
206
+ * @param {string|Object} term - Variable term
207
+ * @returns {string} Variable name
208
+ */
209
+ function getVariableName(term) {
210
+ if (typeof term === 'object' && term.value) {
211
+ return term.value.replace(/^\?/, '');
212
+ }
213
+
214
+ return String(term).replace(/^\?/, '');
215
+ }