n3 2.7.12 → 2.8.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/lib/N3Reasoner.js CHANGED
@@ -34,10 +34,17 @@ class N3Reasoner {
34
34
  this._maxPremiseDepth = options.maxPremiseDepth === undefined ? Infinity : options.maxPremiseDepth;
35
35
  }
36
36
  _add(subject, predicate, object, graphItem, c, cb) {
37
- // Only add to the remaining indexes if there is not already a value in the index
38
- if (!this._store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
39
- this._store._addToIndex(graphItem.predicates, predicate, object, subject);
40
- this._store._addToIndex(graphItem.objects, object, subject, predicate);
37
+ const store = this._store;
38
+ // Observing views must see each derivation before indexing
39
+ // (stores from other N3 versions have no observers)
40
+ if (store._observers) {
41
+ if (!store._addQuad(subject, predicate, object, this._graphId)) return;
42
+ } else {
43
+ // Only add to the remaining indexes if there is not already a value in the index
44
+ if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
45
+ store._addToIndex(graphItem.predicates, predicate, object, subject);
46
+ store._addToIndex(graphItem.objects, object, subject, predicate);
47
+ }
41
48
  // Count genuinely new derivations and fail past the budget. The check comes
42
49
  // after all three indexes are updated, so a caught error leaves the store
43
50
  // in a consistent state (the reasoning result is merely incomplete).
@@ -224,6 +231,8 @@ class N3Reasoner {
224
231
  const graphs = this._store._getGraphs();
225
232
  try {
226
233
  for (const graphId in graphs) {
234
+ // Observers compare numeric graph identifiers
235
+ this._graphId = Number(graphId);
227
236
  this._reasonGraphNaive(rules, graphs[graphId]);
228
237
  }
229
238
  } finally {
package/lib/N3Store.js CHANGED
@@ -15,6 +15,17 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
15
15
 
16
16
  const ITERATOR = Symbol('iter');
17
17
  const SIZE = Symbol('size');
18
+
19
+ // Neither a registration nor its cleanup record may keep a view or store alive.
20
+ // Initialize lazily so importing N3 and using lazy views needs no weak-reference APIs.
21
+ let observerRegistry;
22
+ function finalizeObserver({
23
+ store,
24
+ observer
25
+ }) {
26
+ const target = store.deref();
27
+ if (target) target._removeObserver(observer);
28
+ }
18
29
  function hasInIndex(index0, key0, key1, key2) {
19
30
  const index1 = index0 && index0[key0];
20
31
  const index2 = index1 && index1[key1];
@@ -169,11 +180,14 @@ class N3Store {
169
180
  this._size = 0;
170
181
  // `_graphs` contains subject, predicate, and object indexes per graph
171
182
  this._graphs = Object.create(null);
183
+ // `_observers` contains weak references to views notified before every mutation
184
+ this._observers = null;
172
185
 
173
186
  // Shift parameters if `quads` is not given
174
187
  if (!options && quads && !quads[0] && !(typeof quads.match === 'function')) options = quads, quads = null;
175
188
  options = options || {};
176
189
  this._factory = options.factory || _N3DataFactory.default;
190
+ this._matchSemantics = validateMatchSemantics(options.matchSemantics);
177
191
  this._entityIndex = options.entityIndex || new N3EntityIndex({
178
192
  factory: this._factory
179
193
  });
@@ -373,6 +387,38 @@ class N3Store {
373
387
  };
374
388
  }
375
389
 
390
+ // ### `_addObserver` registers a mutation observer.
391
+ _addObserver(view) {
392
+ const observer = new WeakRef(view);
393
+ const registry = observerRegistry || (observerRegistry = new FinalizationRegistry(finalizeObserver));
394
+ registry.register(view, {
395
+ store: new WeakRef(this),
396
+ observer
397
+ }, observer);
398
+ (this._observers || (this._observers = new Set())).add(observer);
399
+ return observer;
400
+ }
401
+
402
+ // ### `_removeObserver` unregisters a mutation observer.
403
+ _removeObserver(observer) {
404
+ observerRegistry.unregister(observer);
405
+ if (this._observers) {
406
+ this._observers.delete(observer);
407
+ if (this._observers.size === 0) this._observers = null;
408
+ }
409
+ }
410
+
411
+ // ### `_notifyObservers` notifies observers of a mutation.
412
+ _notifyObservers(subjectId, predicateId, objectId, graphId, added) {
413
+ // Observers can remove themselves during Set iteration
414
+ for (const observer of this._observers) {
415
+ const view = observer.deref();
416
+ if (view) view._onParentMutation(subjectId, predicateId, objectId, graphId, added);else
417
+ // Finalization may be delayed; also prune collected views during notification.
418
+ this._removeObserver(observer);
419
+ }
420
+ }
421
+
376
422
  // ### `_uniqueEntities` returns a function that accepts an entity ID
377
423
  // and passes the corresponding entity to callback if it hasn't occurred before.
378
424
  _uniqueEntities(callback) {
@@ -404,6 +450,15 @@ class N3Store {
404
450
  // Convert terms to internal string representation
405
451
  graph = graph ? this._termToNewNumericId(graph) : 1;
406
452
 
453
+ // Map long IRIs to shared numeric identifiers before updating the indexes.
454
+ subject = this._termToNewNumericId(subject);
455
+ predicate = this._termToNewNumericId(predicate);
456
+ object = this._termToNewNumericId(object);
457
+ return this._addQuad(subject, predicate, object, graph);
458
+ }
459
+
460
+ // ### `_addQuad` adds a quad using identifiers from this store's entity index.
461
+ _addQuad(subject, predicate, object, graph) {
407
462
  // Find the graph that will contain the triple
408
463
  let graphItem = this._graphs[graph];
409
464
  // Create the graph if it doesn't exist yet
@@ -424,12 +479,11 @@ class N3Store {
424
479
  Object.freeze(graphItem);
425
480
  }
426
481
 
427
- // Since entities can often be long IRIs, we avoid storing them in every index.
428
- // Instead, we have a separate index that maps entities to numbers,
429
- // which are then used as keys in the other indexes.
430
- subject = this._termToNewNumericId(subject);
431
- predicate = this._termToNewNumericId(predicate);
432
- object = this._termToNewNumericId(object);
482
+ // Notify observers before inserting a new quad so snapshots retain their prior contents
483
+ if (this._observers !== null) {
484
+ if (hasInIndex(graphItem.subjects, subject, predicate, object)) return false;
485
+ this._notifyObservers(subject, predicate, object, graph, true);
486
+ }
433
487
  if (!this._addToIndex(graphItem.subjects, subject, predicate, object)) return false;
434
488
  this._addToIndex(graphItem.predicates, predicate, object, subject);
435
489
  this._addToIndex(graphItem.objects, object, subject, predicate);
@@ -490,12 +544,19 @@ class N3Store {
490
544
  } = subject);
491
545
  // Convert terms to internal string representation
492
546
  graph = graph ? this._termToNumericId(graph) : 1;
547
+ if (!(subject = subject && this._termToNumericId(subject)) || !(predicate = predicate && this._termToNumericId(predicate)) || !(object = object && this._termToNumericId(object))) return false;
548
+ return this._removeQuad(subject, predicate, object, graph);
549
+ }
493
550
 
494
- // Find internal identifiers for all components
495
- // and verify the quad exists.
551
+ // ### `_removeQuad` removes a quad using identifiers from this store's entity index.
552
+ _removeQuad(subject, predicate, object, graph) {
553
+ // Verify the quad exists before notifying observers or changing any indexes.
496
554
  const graphs = this._graphs;
497
- let graphItem, subjects, predicates;
498
- if (!(subject = subject && this._termToNumericId(subject)) || !(predicate = predicate && this._termToNumericId(predicate)) || !(object = object && this._termToNumericId(object)) || !(graphItem = graphs[graph]) || !(subjects = graphItem.subjects[subject]) || !(predicates = subjects[predicate]) || !(object in predicates)) return false;
555
+ const graphItem = graphs[graph];
556
+ if (!graphItem || !hasInIndex(graphItem.subjects, subject, predicate, object)) return false;
557
+
558
+ // Notify observers before the mutation
559
+ if (this._observers !== null) this._notifyObservers(subject, predicate, object, graph, false);
499
560
 
500
561
  // Remove it from all indexes
501
562
  this._removeFromIndex(graphItem.subjects, subject, predicate, object);
@@ -593,9 +654,11 @@ class N3Store {
593
654
  // Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary.
594
655
  // Setting any field to `undefined` or `null` indicates a wildcard.
595
656
  // For backwards compatibility, the object return also implements the Readable stream interface.
596
- match(subject, predicate, object, graph) {
657
+ // `options.matchSemantics` controls how the view reacts to later mutations.
658
+ match(subject, predicate, object, graph, options = null) {
597
659
  return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph, {
598
- entityIndex: this._entityIndex
660
+ entityIndex: this._entityIndex,
661
+ matchSemantics: options && options.matchSemantics !== undefined ? options.matchSemantics : this._matchSemantics
599
662
  });
600
663
  }
601
664
 
@@ -891,7 +954,9 @@ class N3Store {
891
954
  */
892
955
  addAll(quads) {
893
956
  if (quads instanceof DatasetCoreAndReadableStream) quads = quads.filtered;
894
- if (Array.isArray(quads)) this.addQuads(quads);else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
957
+ if (Array.isArray(quads)) this.addQuads(quads);
958
+ // Index merging bypasses observer notifications
959
+ else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
895
960
  if (quads._size !== 0) {
896
961
  this._graphs = merge(this._graphs, quads._graphs);
897
962
  this._size = null; // Invalidate the cached size
@@ -941,7 +1006,9 @@ class N3Store {
941
1006
  * @param graph The optional exact graph to match.
942
1007
  */
943
1008
  deleteMatches(subject, predicate, object, graph) {
944
- for (const quad of this.match(subject, predicate, object, graph)) this.removeQuad(quad);
1009
+ for (const quad of this.match(subject, predicate, object, graph, {
1010
+ matchSemantics: 'lazy'
1011
+ })) this.removeQuad(quad);
945
1012
  return this;
946
1013
  }
947
1014
 
@@ -1065,7 +1132,9 @@ class N3Store {
1065
1132
  * Returns a stream that contains all quads of the dataset.
1066
1133
  */
1067
1134
  toStream() {
1068
- return this.match();
1135
+ return this.match(null, null, null, null, {
1136
+ matchSemantics: 'lazy'
1137
+ });
1069
1138
  }
1070
1139
 
1071
1140
  /**
@@ -1126,6 +1195,97 @@ function indexMatch(index, ids, depth = 0) {
1126
1195
  return target;
1127
1196
  }
1128
1197
 
1198
+ // Capture a read in the same index order as readQuads, without constructing RDF terms.
1199
+ // A flat list avoids allocating a Quad (and its terms) for every unread result.
1200
+ function snapshotMatch(store, subject, predicate, object, graph) {
1201
+ const snapshot = {
1202
+ store,
1203
+ ids: []
1204
+ };
1205
+ const subjectId = subject && store._termToNumericId(subject);
1206
+ const predicateId = predicate && store._termToNumericId(predicate);
1207
+ const objectId = object && store._termToNumericId(object);
1208
+ // Only active reads are frozen: bound terms and graphs have already resolved,
1209
+ // and notifications run before deletion. Entity IDs are never removed.
1210
+
1211
+ // Keep this choice aligned with readQuads: changing order would repeat or skip
1212
+ // results when an iterator resumes partway through its snapshot.
1213
+ let indexName, key0, key1, key2, positions;
1214
+ if (objectId && (subjectId || !predicateId)) {
1215
+ indexName = 'objects';
1216
+ [key0, key1, key2] = [objectId, subjectId, predicateId];
1217
+ positions = [2, 0, 1];
1218
+ } else if (!subjectId && predicateId) {
1219
+ indexName = 'predicates';
1220
+ [key0, key1, key2] = [predicateId, objectId, subjectId];
1221
+ positions = [1, 2, 0];
1222
+ } else {
1223
+ indexName = 'subjects';
1224
+ [key0, key1, key2] = [subjectId, predicateId, objectId];
1225
+ positions = [0, 1, 2];
1226
+ }
1227
+ const graphs = store._getGraphs(graph),
1228
+ parts = [];
1229
+ for (const graphId in graphs) {
1230
+ const index = graphs[graphId][indexName];
1231
+ const graphKey = Number(graphId);
1232
+ for (const value0 in key0 ? {
1233
+ [key0]: index[key0]
1234
+ } : index) {
1235
+ const index1 = index[value0];
1236
+ if (!index1) continue; // eslint-disable-line no-continue
1237
+ parts[positions[0]] = Number(value0);
1238
+ for (const value1 in key1 ? {
1239
+ [key1]: index1[key1]
1240
+ } : index1) {
1241
+ const index2 = index1[value1];
1242
+ if (!index2) continue; // eslint-disable-line no-continue
1243
+ parts[positions[1]] = Number(value1);
1244
+ for (const value2 in key2 ? key2 in index2 ? {
1245
+ [key2]: null
1246
+ } : {} : index2) {
1247
+ parts[positions[2]] = Number(value2);
1248
+ snapshot.ids.push(parts[0], parts[1], parts[2], graphKey);
1249
+ }
1250
+ }
1251
+ }
1252
+ }
1253
+ return snapshot;
1254
+ }
1255
+
1256
+ // Skip already-yielded IDs in constant time; only materialize the requested tail.
1257
+ function* iterateSnapshot({
1258
+ store,
1259
+ ids
1260
+ }, offset) {
1261
+ const entities = store._entities;
1262
+ let subject, predicate, object, graph;
1263
+ let subjectId, predicateId, objectId, graphId;
1264
+ for (let i = offset * 4; i < ids.length; i += 4) {
1265
+ if (subjectId !== ids[i]) subject = store._termFromId(entities[subjectId = ids[i]]);
1266
+ if (predicateId !== ids[i + 1]) predicate = store._termFromId(entities[predicateId = ids[i + 1]]);
1267
+ if (objectId !== ids[i + 2]) object = store._termFromId(entities[objectId = ids[i + 2]]);
1268
+ if (graphId !== ids[i + 3]) graph = store._termFromId(entities[graphId = ids[i + 3]]);
1269
+ yield store._factory.quad(subject, predicate, object, graph);
1270
+ }
1271
+ }
1272
+ function validateMatchSemantics(semantics = 'lazy') {
1273
+ if (semantics !== 'lazy' && semantics !== 'snapshot' && semantics !== 'forwarded') throw new Error(`Unknown matchSemantics: ${semantics}`);
1274
+ if (semantics !== 'lazy' && (typeof WeakRef !== 'function' || typeof FinalizationRegistry !== 'function')) throw new Error('Non-lazy matchSemantics requires WeakRef and FinalizationRegistry support');
1275
+ return semantics;
1276
+ }
1277
+
1278
+ // Returns the intersection of two quad patterns, or false if they conflict.
1279
+ function intersectMatchPatterns(left, right) {
1280
+ const result = new Array(4);
1281
+ for (let i = 0; i < 4; i++) {
1282
+ const leftTerm = left[i],
1283
+ rightTerm = right[i];
1284
+ if (leftTerm === null || leftTerm === undefined) result[i] = rightTerm;else if (rightTerm === null || rightTerm === undefined || (0, _N3DataFactory.termToId)(leftTerm) === (0, _N3DataFactory.termToId)(rightTerm)) result[i] = leftTerm;else return false;
1285
+ }
1286
+ return result;
1287
+ }
1288
+
1129
1289
  /**
1130
1290
  * A class that implements both DatasetCore and Readable.
1131
1291
  */
@@ -1142,6 +1302,86 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1142
1302
  graph,
1143
1303
  options
1144
1304
  });
1305
+ const semantics = this._semantics = validateMatchSemantics(options.matchSemantics);
1306
+ if (options.matchesNothing) {
1307
+ this._matchesNothing = true;
1308
+ this._filtered = new N3Store({
1309
+ factory: n3Store._factory,
1310
+ entityIndex: options.entityIndex
1311
+ });
1312
+ }
1313
+ if (semantics !== 'lazy') {
1314
+ // Active reads share a snapshot only when their source changes.
1315
+ this._readers = null;
1316
+ // Cache pattern ids on first use
1317
+ this._subjectId = this._predicateId = this._objectId = this._graphId = undefined;
1318
+ if (!this._matchesNothing) {
1319
+ this._observer = n3Store._addObserver(this);
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ // ### `_matchesPattern` tests quad ids against this view.
1325
+ _matchesPattern(subjectId, predicateId, objectId, graphId) {
1326
+ const {
1327
+ subject,
1328
+ predicate,
1329
+ object,
1330
+ graph,
1331
+ n3Store
1332
+ } = this;
1333
+ if (subject && subjectId !== (this._subjectId || (this._subjectId = n3Store._termToNumericId(subject)))) return false;
1334
+ if (predicate && predicateId !== (this._predicateId || (this._predicateId = n3Store._termToNumericId(predicate)))) return false;
1335
+ if (object && objectId !== (this._objectId || (this._objectId = n3Store._termToNumericId(object)))) return false;
1336
+ return graph === null || graph === undefined || graphId === (this._graphId || (this._graphId = graph === '' || (0, _N3Util.isDefaultGraph)(graph) ? 1 : n3Store._termToNumericId(graph)));
1337
+ }
1338
+
1339
+ // ### `_matchesQuad` tests a Quad against this view.
1340
+ _matchesQuad(quad) {
1341
+ const {
1342
+ subject,
1343
+ predicate,
1344
+ object,
1345
+ graph
1346
+ } = this;
1347
+ return !this._matchesNothing && (subject === null || subject === undefined || (0, _N3DataFactory.termToId)(subject) === (0, _N3DataFactory.termToId)(quad.subject)) && (predicate === null || predicate === undefined || (0, _N3DataFactory.termToId)(predicate) === (0, _N3DataFactory.termToId)(quad.predicate)) && (object === null || object === undefined || (0, _N3DataFactory.termToId)(object) === (0, _N3DataFactory.termToId)(quad.object)) && (graph === null || graph === undefined || (0, _N3DataFactory.termToId)(graph) === (0, _N3DataFactory.termToId)(quad.graph));
1348
+ }
1349
+
1350
+ // ### `_assertMatchesPattern` rejects a Quad outside this view.
1351
+ _assertMatchesPattern(quad) {
1352
+ if (!this._matchesQuad(quad)) throw new Error('Quad does not match the forwarded view pattern');
1353
+ }
1354
+
1355
+ // ### `_sourceIterator` returns an iterator over the current backing store.
1356
+ _sourceIterator() {
1357
+ return this._filtered ? this._filtered[Symbol.iterator]() : this.n3Store.readQuads(this.subject, this.predicate, this.object, this.graph);
1358
+ }
1359
+
1360
+ // ### `_freezeCurrentIterators` freezes only readers still using the live source.
1361
+ _freezeCurrentIterators() {
1362
+ if (this._readers) {
1363
+ this._readers.snapshot = this._filtered ? snapshotMatch(this._filtered) : snapshotMatch(this.n3Store, this.subject, this.predicate, this.object, this.graph);
1364
+ // Each iterator retains its own group. New reads must see the new source;
1365
+ // the view must not retain snapshots belonging to suspended or abandoned reads.
1366
+ this._readers = null;
1367
+ }
1368
+ }
1369
+
1370
+ // ### `_onParentMutation` applies a parent mutation to this view.
1371
+ _onParentMutation(subjectId, predicateId, objectId, graphId, added) {
1372
+ if (!this._matchesPattern(subjectId, predicateId, objectId, graphId)) return;
1373
+ this._freezeCurrentIterators();
1374
+
1375
+ // Keep using the parent until materialization
1376
+ if (this._semantics === 'forwarded') {
1377
+ if (this._filtered) {
1378
+ if (added) this._filtered._addQuad(subjectId, predicateId, objectId, graphId);else this._filtered._removeQuad(subjectId, predicateId, objectId, graphId);
1379
+ }
1380
+ return;
1381
+ }
1382
+
1383
+ // Capture the pre-mutation snapshot
1384
+ this._filtered = this.filtered;
1145
1385
  }
1146
1386
  get filtered() {
1147
1387
  if (!this._filtered) {
@@ -1152,10 +1392,12 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1152
1392
  predicate,
1153
1393
  subject
1154
1394
  } = this;
1395
+ if (this._semantics !== 'lazy') this._freezeCurrentIterators();
1155
1396
  const newStore = this._filtered = new N3Store({
1156
1397
  factory: n3Store._factory,
1157
1398
  entityIndex: this.options.entityIndex
1158
1399
  });
1400
+ if (this._semantics === 'snapshot') this._detachObserver();
1159
1401
  let subjectId, predicateId, objectId;
1160
1402
 
1161
1403
  // Translate IRIs to internal index keys.
@@ -1207,13 +1449,55 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1207
1449
  this.push(value);
1208
1450
  }
1209
1451
  }
1452
+
1453
+ // ### `_destroy` closes the cached iterator.
1454
+ _destroy(error, callback) {
1455
+ if (this[ITERATOR]) {
1456
+ this[ITERATOR].return();
1457
+ this[ITERATOR] = null;
1458
+ }
1459
+ callback(error);
1460
+ }
1461
+
1462
+ // ### `_detachObserver` stops observing the parent store.
1463
+ _detachObserver() {
1464
+ if (this._observer) {
1465
+ this.n3Store._removeObserver(this._observer);
1466
+ this._observer = null;
1467
+ }
1468
+ }
1469
+
1470
+ // ### `_detach` freezes the view and stops observing the parent.
1471
+ _detach() {
1472
+ this._filtered = this.filtered;
1473
+ this._detachObserver();
1474
+ // Lazy views deferred this state
1475
+ if (this._semantics === 'lazy') this._readers = null;
1476
+ this._semantics = 'snapshot';
1477
+ return this;
1478
+ }
1210
1479
  addAll(quads) {
1480
+ if (this._semantics === 'forwarded') {
1481
+ for (const quad of quads) {
1482
+ this._assertMatchesPattern(quad);
1483
+ this.n3Store.addQuad(quad);
1484
+ }
1485
+ return this;
1486
+ }
1211
1487
  return this.filtered.addAll(quads);
1212
1488
  }
1213
1489
  contains(other) {
1214
1490
  return this.filtered.contains(other);
1215
1491
  }
1216
1492
  deleteMatches(subject, predicate, object, graph) {
1493
+ if (this._semantics === 'forwarded') {
1494
+ // No deletion pattern can match a view with conflicting ancestor patterns.
1495
+ if (this._matchesNothing) throw new Error('Cannot delete from a conflicting forwarded view pattern');
1496
+ const pattern = intersectMatchPatterns([this.subject, this.predicate, this.object, this.graph], [subject, predicate, object, graph]);
1497
+ if (!pattern) throw new Error('Deletion pattern does not match the forwarded view pattern');
1498
+ this.n3Store.deleteMatches(...pattern);
1499
+ return this;
1500
+ }
1217
1501
  return this.filtered.deleteMatches(subject, predicate, object, graph);
1218
1502
  }
1219
1503
  difference(other) {
@@ -1223,57 +1507,131 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1223
1507
  return this.filtered.equals(other);
1224
1508
  }
1225
1509
  every(callback, subject, predicate, object, graph) {
1226
- return this.filtered.every(callback, subject, predicate, object, graph);
1510
+ return this.filtered.every(this._semantics === 'forwarded' ? quad => callback(quad, this) : callback, subject, predicate, object, graph);
1227
1511
  }
1228
1512
  filter(iteratee) {
1229
- return this.filtered.filter(iteratee);
1513
+ return this.filtered.filter(this._semantics === 'forwarded' ? quad => iteratee(quad, this) : iteratee);
1230
1514
  }
1231
1515
  forEach(callback, subject, predicate, object, graph) {
1232
- return this.filtered.forEach(callback, subject, predicate, object, graph);
1516
+ return this.filtered.forEach(this._semantics === 'forwarded' ? quad => callback(quad, this) : callback, subject, predicate, object, graph);
1233
1517
  }
1234
1518
  import(stream) {
1235
- return this.filtered.import(stream);
1519
+ if (this._semantics !== 'forwarded') return this.filtered.import(stream);
1520
+ const view = this;
1521
+ function onData(quad) {
1522
+ try {
1523
+ view._assertMatchesPattern(quad);
1524
+ view.n3Store.addQuad(quad);
1525
+ } catch (error) {
1526
+ // Stop this import; RDF/JS streams need not implement destroy()
1527
+ stream.removeListener('data', onData);
1528
+ if (typeof stream.destroy === 'function') stream.destroy(error);else stream.emit('error', error);
1529
+ }
1530
+ }
1531
+ stream.on('data', onData);
1532
+ return stream;
1236
1533
  }
1237
1534
  intersection(other) {
1238
1535
  return this.filtered.intersection(other);
1239
1536
  }
1240
1537
  map(iteratee) {
1241
- return this.filtered.map(iteratee);
1538
+ return this.filtered.map(this._semantics === 'forwarded' ? quad => iteratee(quad, this) : iteratee);
1242
1539
  }
1243
1540
  some(callback, subject, predicate, object, graph) {
1244
- return this.filtered.some(callback, subject, predicate, object, graph);
1541
+ return this.filtered.some(this._semantics === 'forwarded' ? quad => callback(quad, this) : callback, subject, predicate, object, graph);
1245
1542
  }
1246
1543
  toCanonical() {
1247
1544
  return this.filtered.toCanonical();
1248
1545
  }
1249
1546
  toStream() {
1250
- return this._filtered ? this._filtered.toStream() : this.n3Store.match(this.subject, this.predicate, this.object, this.graph);
1547
+ if (this._semantics !== 'lazy')
1548
+ // Use a fresh sync iterator instead of consuming this view's own readable stream.
1549
+ return _readableStream.Readable.from(this[Symbol.iterator]());
1550
+ return this._filtered ? this._filtered.toStream() : this.n3Store.match(this.subject, this.predicate, this.object, this.graph, {
1551
+ matchSemantics: 'lazy'
1552
+ });
1251
1553
  }
1252
1554
  union(quads) {
1253
- return this._filtered ? this._filtered.union(quads) : this.n3Store.match(this.subject, this.predicate, this.object, this.graph).addAll(quads);
1555
+ return this._filtered ? this._filtered.union(quads) : this.n3Store.match(this.subject, this.predicate, this.object, this.graph, {
1556
+ matchSemantics: 'lazy'
1557
+ }).addAll(quads);
1254
1558
  }
1255
1559
  toArray() {
1560
+ if (this._semantics !== 'lazy') return [...this];
1256
1561
  return this._filtered ? this._filtered.toArray() : this.n3Store.getQuads(this.subject, this.predicate, this.object, this.graph);
1257
1562
  }
1258
1563
  reduce(callback, initialValue) {
1259
- return this.filtered.reduce(callback, initialValue);
1564
+ return this.filtered.reduce(this._semantics === 'forwarded' ? (accumulator, quad) => callback(accumulator, quad, this) : callback, initialValue);
1260
1565
  }
1261
1566
  toString() {
1262
1567
  return new _N3Writer.default().quadsToString(this);
1263
1568
  }
1264
1569
  add(quad) {
1570
+ if (this._semantics === 'forwarded') {
1571
+ this._assertMatchesPattern(quad);
1572
+ this.n3Store.addQuad(quad);
1573
+ return this;
1574
+ }
1265
1575
  return this.filtered.add(quad);
1266
1576
  }
1267
1577
  delete(quad) {
1578
+ if (this._semantics === 'forwarded') {
1579
+ this._assertMatchesPattern(quad);
1580
+ this.n3Store.removeQuad(quad);
1581
+ return this;
1582
+ }
1268
1583
  return this.filtered.delete(quad);
1269
1584
  }
1270
1585
  has(quad) {
1271
1586
  return this.filtered.has(quad);
1272
1587
  }
1273
- match(subject, predicate, object, graph) {
1274
- return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, this.options);
1588
+ match(subject, predicate, object, graph, options = null) {
1589
+ const requestedSemantics = options && options.matchSemantics;
1590
+ if (options && requestedSemantics !== undefined) {
1591
+ validateMatchSemantics(requestedSemantics);
1592
+ if (requestedSemantics !== this._semantics) throw new Error(`Cannot override matchSemantics on a view: inherited "${this._semantics}", received "${requestedSemantics}"`);
1593
+ }
1594
+ if (this._semantics !== 'forwarded') return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, {
1595
+ entityIndex: this.options.entityIndex,
1596
+ matchSemantics: this._semantics
1597
+ });
1598
+ const pattern = !this._matchesNothing && intersectMatchPatterns([this.subject, this.predicate, this.object, this.graph], [subject, predicate, object, graph]);
1599
+ const [matchedSubject, matchedPredicate, matchedObject, matchedGraph] = pattern || [null, null, null, null];
1600
+ return new DatasetCoreAndReadableStream(this.n3Store, matchedSubject, matchedPredicate, matchedObject, matchedGraph, {
1601
+ entityIndex: this.options.entityIndex,
1602
+ matchSemantics: 'forwarded',
1603
+ matchesNothing: !pattern
1604
+ });
1275
1605
  }
1276
1606
  [Symbol.iterator]() {
1277
- return this._filtered ? this._filtered[Symbol.iterator]() : this.n3Store.readQuads(this.subject, this.predicate, this.object, this.graph);
1607
+ return this._semantics === 'lazy' ? this._sourceIterator() : this._iterateStable();
1608
+ }
1609
+ *_iterateStable() {
1610
+ const readers = this._readers || (this._readers = {
1611
+ count: 0,
1612
+ snapshot: null
1613
+ });
1614
+ const source = this._sourceIterator();
1615
+ let yielded = 0;
1616
+ readers.count++;
1617
+ try {
1618
+ while (!readers.snapshot) {
1619
+ const {
1620
+ value,
1621
+ done
1622
+ } = source.next();
1623
+ // A custom factory can mutate the store inside next(), even on its first call.
1624
+ if (readers.snapshot || done) break;
1625
+ yielded++;
1626
+ yield value;
1627
+ }
1628
+ if (readers.snapshot) yield* iterateSnapshot(readers.snapshot, yielded);
1629
+ } finally {
1630
+ source.return();
1631
+ if (--readers.count === 0) {
1632
+ readers.snapshot = null;
1633
+ if (this._readers === readers) this._readers = null;
1634
+ }
1635
+ }
1278
1636
  }
1279
1637
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.7.12",
3
+ "version": "2.8.0",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
package/src/N3Reasoner.js CHANGED
@@ -23,10 +23,18 @@ export default class N3Reasoner {
23
23
  }
24
24
 
25
25
  _add(subject, predicate, object, graphItem, c, cb) {
26
- // Only add to the remaining indexes if there is not already a value in the index
27
- if (!this._store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
28
- this._store._addToIndex(graphItem.predicates, predicate, object, subject);
29
- this._store._addToIndex(graphItem.objects, object, subject, predicate);
26
+ const store = this._store;
27
+ // Observing views must see each derivation before indexing
28
+ // (stores from other N3 versions have no observers)
29
+ if (store._observers) {
30
+ if (!store._addQuad(subject, predicate, object, this._graphId)) return;
31
+ }
32
+ else {
33
+ // Only add to the remaining indexes if there is not already a value in the index
34
+ if (!store._addToIndex(graphItem.subjects, subject, predicate, object)) return;
35
+ store._addToIndex(graphItem.predicates, predicate, object, subject);
36
+ store._addToIndex(graphItem.objects, object, subject, predicate);
37
+ }
30
38
  // Count genuinely new derivations and fail past the budget. The check comes
31
39
  // after all three indexes are updated, so a caught error leaves the store
32
40
  // in a consistent state (the reasoning result is merely incomplete).
@@ -228,6 +236,8 @@ export default class N3Reasoner {
228
236
  const graphs = this._store._getGraphs();
229
237
  try {
230
238
  for (const graphId in graphs) {
239
+ // Observers compare numeric graph identifiers
240
+ this._graphId = Number(graphId);
231
241
  this._reasonGraphNaive(rules, graphs[graphId]);
232
242
  }
233
243
  }