rdflib 2.3.9 → 2.4.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/esm/fetcher.js CHANGED
@@ -674,16 +674,42 @@ export default class Fetcher {
674
674
  // if metadata flaged clear cache and removeDocument
675
675
  const meta = this.appNode;
676
676
  const kb = this.store;
677
- const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.sym(docuri), meta).map(st => st.subject);
677
+ // The recorded URI is stored as a string literal, not as a node (see
678
+ // saveRequestMetadata and linkeddata/rdflib.js#427): matching it with
679
+ // kb.sym() never found the request, so a flagged document was answered from
680
+ // the cache instead of being refetched (#870).
681
+ const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.rdfFactory.literal(docuri), meta).map(st => st.subject);
682
+ let answered = false;
683
+ let usableAnswer = false;
678
684
  for (const request of requests) {
679
685
  const response = kb.any(request, this.ns.link('response'), null, meta);
680
- if (response != undefined) {
681
- // ts
682
- const quad = kb.statementsMatching(response, this.ns.link('outOfDate'), true, meta);
683
- kb.remove(quad);
684
- options.force = true;
685
- options.clearPreviousData = true;
686
+ // any() returns null (never undefined) when nothing matches: keep the
687
+ // loose check so a request that has no recorded answer yet is not
688
+ // counted as answered.
689
+ if (response == null) {
690
+ continue;
686
691
  }
692
+ answered = true;
693
+ // Every recorded answer that is flagged out-of-date is unusable: the
694
+ // answer of a previous identity/authorization (flagAuthorizationMetadata).
695
+ // Read the mark with anyJS, as update-manager.editable does: it is stored
696
+ // as an xsd:boolean literal and the object position has to stay a
697
+ // wildcard - any() returns null when all four positions are given, even
698
+ // when the statement exists.
699
+ const outOfDate = kb.anyJS(response, this.ns.link('outOfDate'), null, meta);
700
+ if (!outOfDate) {
701
+ usableAnswer = true;
702
+ break;
703
+ }
704
+ }
705
+ if (answered && !usableAnswer) {
706
+ // Refetch instead of answering from the cache, and keep the marks: an
707
+ // answer of the previous authorization stays excluded, so it cannot win
708
+ // over the fresh one — and if the refetch fails the state stays
709
+ // "unknown" instead of becoming definitive again. Once a fresh answer is
710
+ // recorded the loop above finds it and later loads are cache hits again.
711
+ options.force = true;
712
+ options.clearPreviousData = true;
687
713
  }
688
714
  const initialisedOptions = this.initFetchOptions(docuri, options);
689
715
  return this.pendingFetchPromise(docuri, initialisedOptions.baseURI, initialisedOptions);
@@ -977,27 +1003,27 @@ export default class Fetcher {
977
1003
  linkData(originalUri, rel, uri, why, reverse) {
978
1004
  if (!uri) return;
979
1005
  let kb = this.store;
980
- let predicate;
981
1006
  // See http://www.w3.org/TR/powder-dr/#httplink for describedby 2008-12-10
982
1007
  let obj = kb.rdfFactory.namedNode(Uri.join(uri, originalUri.value));
1008
+ let predicates;
983
1009
  if (rel === 'alternate' || rel === 'seeAlso' || rel === 'meta' || rel === 'describedby') {
984
1010
  if (obj.value === originalUri.value) {
985
1011
  return;
986
1012
  }
987
- predicate = this.ns.rdfs('seeAlso');
1013
+ // Also emit the IANA relation predicate triple (issue #741)
1014
+ predicates = [kb.rdfFactory.namedNode(this.ianaLinkRelation(rel)), this.ns.rdfs('seeAlso')];
988
1015
  } else if (rel === 'type') {
989
- predicate = kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
1016
+ predicates = [kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')];
990
1017
  } else {
991
1018
  // See https://www.iana.org/assignments/link-relations/link-relations.xml
992
1019
  // Alas not yet in RDF yet for each predicate
993
1020
  // encode space in e.g. rel="shortcut icon"
994
- predicate = kb.rdfFactory.namedNode(Uri.join(encodeURIComponent(rel), 'http://www.iana.org/assignments/link-relations/'));
995
- }
996
- if (reverse) {
997
- kb.add(obj, predicate, originalUri, why);
998
- } else {
999
- kb.add(originalUri, predicate, obj, why);
1021
+ predicates = [kb.rdfFactory.namedNode(this.ianaLinkRelation(rel))];
1000
1022
  }
1023
+ kb.addAll(predicates.map(predicate => reverse ? kb.rdfFactory.quad(obj, predicate, originalUri, why) : kb.rdfFactory.quad(originalUri, predicate, obj, why)));
1024
+ }
1025
+ ianaLinkRelation(rel) {
1026
+ return Uri.join(encodeURIComponent(rel), 'http://www.iana.org/assignments/link-relations/');
1001
1027
  }
1002
1028
  parseLinkHeader(linkHeader, originalUri, reqNode) {
1003
1029
  if (!linkHeader) {
package/esm/index.js CHANGED
@@ -5,7 +5,6 @@ import Empty from './empty';
5
5
  import Fetcher from './fetcher';
6
6
  import Formula from './formula';
7
7
  import Store from './store';
8
- import jsonParser from './jsonparser';
9
8
  import Literal from './literal';
10
9
  import log from './log';
11
10
  import N3Parser from './n3parser';
@@ -56,7 +55,7 @@ const term = Node.fromValue;
56
55
  // it exports the _current_ value of nextId, which is always 0
57
56
  const NextId = BlankNode.nextId;
58
57
  export * from './utils/terms';
59
- export { BlankNode, Collection, DataFactory, Empty, Fetcher, Formula, Store, jsonParser, Literal, log, N3Parser, NamedNode, Namespace, Node, parse, Query, queryToSPARQL, RDFaProcessor, RDFParser, serialize, Serializer, SPARQLToQuery, sparqlUpdateParser, Statement, term, UpdateManager, UpdatesSocket, UpdatesVia, uri, Util, Variable, Store as IndexedFormula,
58
+ export { BlankNode, Collection, DataFactory, Empty, Fetcher, Formula, Store, Literal, log, N3Parser, NamedNode, Namespace, Node, parse, Query, queryToSPARQL, RDFaProcessor, RDFParser, serialize, Serializer, SPARQLToQuery, sparqlUpdateParser, Statement, term, UpdateManager, UpdatesSocket, UpdatesVia, uri, Util, Variable, Store as IndexedFormula,
60
59
  // Alias
61
60
 
62
61
  NextId, fromNT, fetcher, graph, lit, st, namedNode as sym,
package/esm/parse.js CHANGED
@@ -7,7 +7,7 @@ import { parseRDFaDOM } from './rdfaparser';
7
7
  import RDFParser from './rdfxmlparser';
8
8
  import sparqlUpdateParser from './patch-parser';
9
9
  import * as Util from './utils-js';
10
- import { TurtleContentType, N3ContentType, RDFXMLContentType, XHTMLContentType, HTMLContentType, SPARQLUpdateContentType, SPARQLUpdateSingleMatchContentType, JSONLDContentType, NQuadsContentType, NQuadsAltContentType } from './types';
10
+ import { TurtleContentType, N3ContentType, NTriplesContentType, RDFXMLContentType, XHTMLContentType, HTMLContentType, SPARQLUpdateContentType, SPARQLUpdateSingleMatchContentType, JSONLDContentType, NQuadsContentType, NQuadsAltContentType } from './types';
11
11
  /**
12
12
  * Parse a string and put the result into the graph kb.
13
13
  * Normal method is sync.
@@ -23,7 +23,7 @@ export default function parse(str, kb, base, contentType = 'text/turtle', callba
23
23
  contentType = contentType || TurtleContentType;
24
24
  contentType = contentType.split(';')[0];
25
25
  try {
26
- if (contentType === N3ContentType || contentType === TurtleContentType) {
26
+ if (contentType === N3ContentType || contentType === TurtleContentType || contentType === NTriplesContentType) {
27
27
  var p = N3Parser(kb, kb, base, base, null, null, '', null);
28
28
  p.loadBuf(str);
29
29
  executeCallback();
@@ -74,7 +74,8 @@ export default function parse(str, kb, base, contentType = 'text/turtle', callba
74
74
  'application/sparql-update-single-match': true,
75
75
  'application/ld+json': true,
76
76
  'application/nquads': true,
77
- 'application/n-quads': true
77
+ 'application/n-quads': true,
78
+ 'application/n-triples': true
78
79
  };
79
80
  function executeCallback() {
80
81
  if (callback) {
package/esm/serializer.js CHANGED
@@ -295,7 +295,7 @@ export class Serializer {
295
295
  return self.atomicTermToN3(x);
296
296
  }
297
297
  var list = x.elements;
298
- var rest = kb.sym(rdfns + 'nill');
298
+ var rest = kb.sym(rdfns + 'nil');
299
299
  for (var i = list.length - 1; i >= 0; i--) {
300
300
  var bnode = factory.blankNode();
301
301
  str += termToNT(bnode) + ' ' + termToNT(kb.sym(rdfns + 'first')) + ' ' + termToNT(list[i]) + '.\n';
@@ -656,7 +656,7 @@ export class Serializer {
656
656
  if (canSplit) {
657
657
  if (this.defaultNamespace && this.defaultNamespace === namesp && this.flags.indexOf('d') < 0) {
658
658
  // d -> suppress default
659
- if (this.flags.indexOf('k') >= 0 && this.keyords.indexOf(localid) < 0) {
659
+ if (this.flags.indexOf('k') >= 0 && this.keywords.indexOf(localid) < 0) {
660
660
  return localid;
661
661
  }
662
662
  return ':' + localid;
package/lib/fetcher.d.ts CHANGED
@@ -340,6 +340,7 @@ export default class Fetcher implements CallbackifyInterface {
340
340
  original: Quad_Subject;
341
341
  } & Options, errorMessage: string, statusCode: StatusValues, response?: ExtendedResponse): Promise<FetchError>;
342
342
  linkData(originalUri: NamedNode, rel: string, uri: string, why: Quad_Graph, reverse?: boolean): void;
343
+ private ianaLinkRelation;
343
344
  parseLinkHeader(linkHeader: string, originalUri: NamedNode, reqNode: Quad_Graph): void;
344
345
  doneFetch(options: {
345
346
  req: Quad_Subject;
package/lib/fetcher.js CHANGED
@@ -683,16 +683,42 @@ class Fetcher {
683
683
  // if metadata flaged clear cache and removeDocument
684
684
  const meta = this.appNode;
685
685
  const kb = this.store;
686
- const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.sym(docuri), meta).map(st => st.subject);
686
+ // The recorded URI is stored as a string literal, not as a node (see
687
+ // saveRequestMetadata and linkeddata/rdflib.js#427): matching it with
688
+ // kb.sym() never found the request, so a flagged document was answered from
689
+ // the cache instead of being refetched (#870).
690
+ const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.rdfFactory.literal(docuri), meta).map(st => st.subject);
691
+ let answered = false;
692
+ let usableAnswer = false;
687
693
  for (const request of requests) {
688
694
  const response = kb.any(request, this.ns.link('response'), null, meta);
689
- if (response != undefined) {
690
- // ts
691
- const quad = kb.statementsMatching(response, this.ns.link('outOfDate'), true, meta);
692
- kb.remove(quad);
693
- options.force = true;
694
- options.clearPreviousData = true;
695
+ // any() returns null (never undefined) when nothing matches: keep the
696
+ // loose check so a request that has no recorded answer yet is not
697
+ // counted as answered.
698
+ if (response == null) {
699
+ continue;
695
700
  }
701
+ answered = true;
702
+ // Every recorded answer that is flagged out-of-date is unusable: the
703
+ // answer of a previous identity/authorization (flagAuthorizationMetadata).
704
+ // Read the mark with anyJS, as update-manager.editable does: it is stored
705
+ // as an xsd:boolean literal and the object position has to stay a
706
+ // wildcard - any() returns null when all four positions are given, even
707
+ // when the statement exists.
708
+ const outOfDate = kb.anyJS(response, this.ns.link('outOfDate'), null, meta);
709
+ if (!outOfDate) {
710
+ usableAnswer = true;
711
+ break;
712
+ }
713
+ }
714
+ if (answered && !usableAnswer) {
715
+ // Refetch instead of answering from the cache, and keep the marks: an
716
+ // answer of the previous authorization stays excluded, so it cannot win
717
+ // over the fresh one — and if the refetch fails the state stays
718
+ // "unknown" instead of becoming definitive again. Once a fresh answer is
719
+ // recorded the loop above finds it and later loads are cache hits again.
720
+ options.force = true;
721
+ options.clearPreviousData = true;
696
722
  }
697
723
  const initialisedOptions = this.initFetchOptions(docuri, options);
698
724
  return this.pendingFetchPromise(docuri, initialisedOptions.baseURI, initialisedOptions);
@@ -986,27 +1012,27 @@ class Fetcher {
986
1012
  linkData(originalUri, rel, uri, why, reverse) {
987
1013
  if (!uri) return;
988
1014
  let kb = this.store;
989
- let predicate;
990
1015
  // See http://www.w3.org/TR/powder-dr/#httplink for describedby 2008-12-10
991
1016
  let obj = kb.rdfFactory.namedNode(Uri.join(uri, originalUri.value));
1017
+ let predicates;
992
1018
  if (rel === 'alternate' || rel === 'seeAlso' || rel === 'meta' || rel === 'describedby') {
993
1019
  if (obj.value === originalUri.value) {
994
1020
  return;
995
1021
  }
996
- predicate = this.ns.rdfs('seeAlso');
1022
+ // Also emit the IANA relation predicate triple (issue #741)
1023
+ predicates = [kb.rdfFactory.namedNode(this.ianaLinkRelation(rel)), this.ns.rdfs('seeAlso')];
997
1024
  } else if (rel === 'type') {
998
- predicate = kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
1025
+ predicates = [kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')];
999
1026
  } else {
1000
1027
  // See https://www.iana.org/assignments/link-relations/link-relations.xml
1001
1028
  // Alas not yet in RDF yet for each predicate
1002
1029
  // encode space in e.g. rel="shortcut icon"
1003
- predicate = kb.rdfFactory.namedNode(Uri.join(encodeURIComponent(rel), 'http://www.iana.org/assignments/link-relations/'));
1004
- }
1005
- if (reverse) {
1006
- kb.add(obj, predicate, originalUri, why);
1007
- } else {
1008
- kb.add(originalUri, predicate, obj, why);
1030
+ predicates = [kb.rdfFactory.namedNode(this.ianaLinkRelation(rel))];
1009
1031
  }
1032
+ kb.addAll(predicates.map(predicate => reverse ? kb.rdfFactory.quad(obj, predicate, originalUri, why) : kb.rdfFactory.quad(originalUri, predicate, obj, why)));
1033
+ }
1034
+ ianaLinkRelation(rel) {
1035
+ return Uri.join(encodeURIComponent(rel), 'http://www.iana.org/assignments/link-relations/');
1010
1036
  }
1011
1037
  parseLinkHeader(linkHeader, originalUri, reqNode) {
1012
1038
  if (!linkHeader) {
package/lib/index.d.ts CHANGED
@@ -4,7 +4,6 @@ import Empty from './empty';
4
4
  import Fetcher from './fetcher';
5
5
  import Formula from './formula';
6
6
  import Store from './store';
7
- import jsonParser from './jsonparser';
8
7
  import Literal from './literal';
9
8
  import log from './log';
10
9
  import N3Parser from './n3parser';
@@ -34,7 +33,7 @@ declare const term: <T extends import("./types").FromValueReturns>(value: import
34
33
  declare const NextId: number;
35
34
  export * from './utils/terms';
36
35
  export type { AutoInitOptions, ExtendedResponse, FetchError } from './fetcher';
37
- export { BlankNode, Collection, DataFactory, Empty, Fetcher, Formula, Store, jsonParser, Literal, log, N3Parser, NamedNode, Namespace, Node, parse, Query, queryToSPARQL, RDFaProcessor, RDFParser, serialize, Serializer, SPARQLToQuery, sparqlUpdateParser, Statement, term, UpdateManager, UpdatesSocket, UpdatesVia, uri, Util, Variable, Store as IndexedFormula, // Alias
36
+ export { BlankNode, Collection, DataFactory, Empty, Fetcher, Formula, Store, Literal, log, N3Parser, NamedNode, Namespace, Node, parse, Query, queryToSPARQL, RDFaProcessor, RDFParser, serialize, Serializer, SPARQLToQuery, sparqlUpdateParser, Statement, term, UpdateManager, UpdatesSocket, UpdatesVia, uri, Util, Variable, Store as IndexedFormula, // Alias
38
37
  NextId, fromNT, fetcher, graph, lit, st, namedNode as sym, blankNode, defaultGraph, literal, namedNode, quad, triple, variable, };
39
38
  export { termValue } from './utils/termValue';
40
39
  export declare class ConnectedStore extends Store {
package/lib/index.js CHANGED
@@ -29,7 +29,6 @@ var _exportNames = {
29
29
  Formula: true,
30
30
  Store: true,
31
31
  IndexedFormula: true,
32
- jsonParser: true,
33
32
  Literal: true,
34
33
  log: true,
35
34
  N3Parser: true,
@@ -197,14 +196,7 @@ Object.defineProperty(exports, "Variable", {
197
196
  return _variable.default;
198
197
  }
199
198
  });
200
- exports.graph = exports.fromNT = exports.fetcher = exports.defaultGraph = exports.blankNode = void 0;
201
- Object.defineProperty(exports, "jsonParser", {
202
- enumerable: true,
203
- get: function () {
204
- return _jsonparser.default;
205
- }
206
- });
207
- exports.literal = exports.lit = void 0;
199
+ exports.literal = exports.lit = exports.graph = exports.fromNT = exports.fetcher = exports.defaultGraph = exports.blankNode = void 0;
208
200
  Object.defineProperty(exports, "log", {
209
201
  enumerable: true,
210
202
  get: function () {
@@ -252,7 +244,6 @@ var _empty = _interopRequireDefault(require("./empty"));
252
244
  var _fetcher = _interopRequireDefault(require("./fetcher"));
253
245
  var _formula = _interopRequireDefault(require("./formula"));
254
246
  var _store = _interopRequireDefault(require("./store"));
255
- var _jsonparser = _interopRequireDefault(require("./jsonparser"));
256
247
  var _literal = _interopRequireDefault(require("./literal"));
257
248
  var _log = _interopRequireDefault(require("./log"));
258
249
  var _n3parser = _interopRequireDefault(require("./n3parser"));
package/lib/parse.js CHANGED
@@ -33,7 +33,7 @@ function parse(str, kb, base, contentType = 'text/turtle', callback) {
33
33
  contentType = contentType || _types.TurtleContentType;
34
34
  contentType = contentType.split(';')[0];
35
35
  try {
36
- if (contentType === _types.N3ContentType || contentType === _types.TurtleContentType) {
36
+ if (contentType === _types.N3ContentType || contentType === _types.TurtleContentType || contentType === _types.NTriplesContentType) {
37
37
  var p = (0, _n3parser.default)(kb, kb, base, base, null, null, '', null);
38
38
  p.loadBuf(str);
39
39
  executeCallback();
@@ -84,7 +84,8 @@ function parse(str, kb, base, contentType = 'text/turtle', callback) {
84
84
  'application/sparql-update-single-match': true,
85
85
  'application/ld+json': true,
86
86
  'application/nquads': true,
87
- 'application/n-quads': true
87
+ 'application/n-quads': true,
88
+ 'application/n-triples': true
88
89
  };
89
90
  function executeCallback() {
90
91
  if (callback) {
@@ -6,7 +6,7 @@ import { BlankNode, NamedNode } from './tf-types';
6
6
  */
7
7
  export default function serialize(
8
8
  /** The graph or nodes that should be serialized */
9
- target: Formula | NamedNode | BlankNode | null,
9
+ target: Formula | NamedNode | BlankNode | null | undefined,
10
10
  /** The store */
11
11
  kb: Formula, base?: unknown,
12
12
  /**
package/lib/serializer.js CHANGED
@@ -305,7 +305,7 @@ class Serializer {
305
305
  return self.atomicTermToN3(x);
306
306
  }
307
307
  var list = x.elements;
308
- var rest = kb.sym(rdfns + 'nill');
308
+ var rest = kb.sym(rdfns + 'nil');
309
309
  for (var i = list.length - 1; i >= 0; i--) {
310
310
  var bnode = factory.blankNode();
311
311
  str += termToNT(bnode) + ' ' + termToNT(kb.sym(rdfns + 'first')) + ' ' + termToNT(list[i]) + '.\n';
@@ -666,7 +666,7 @@ class Serializer {
666
666
  if (canSplit) {
667
667
  if (this.defaultNamespace && this.defaultNamespace === namesp && this.flags.indexOf('d') < 0) {
668
668
  // d -> suppress default
669
- if (this.flags.indexOf('k') >= 0 && this.keyords.indexOf(localid) < 0) {
669
+ if (this.flags.indexOf('k') >= 0 && this.keywords.indexOf(localid) < 0) {
670
670
  return localid;
671
671
  }
672
672
  return ':' + localid;
package/lib/types.d.ts CHANGED
@@ -36,7 +36,7 @@ export declare const XHTMLContentType: "application/xhtml+xml";
36
36
  /**
37
37
  * A valid mime type header
38
38
  */
39
- export type ContentType = typeof RDFXMLContentType | typeof HTMLContentType | typeof JSONLDContentType | typeof N3ContentType | typeof N3LegacyContentType | typeof NQuadsAltContentType | typeof NQuadsContentType | typeof SPARQLUpdateContentType | typeof SPARQLUpdateSingleMatchContentType | typeof TurtleContentType | typeof TurtleLegacyContentType | typeof XHTMLContentType;
39
+ export type ContentType = typeof RDFXMLContentType | typeof HTMLContentType | typeof JSONLDContentType | typeof N3ContentType | typeof N3LegacyContentType | typeof NQuadsAltContentType | typeof NQuadsContentType | typeof NTriplesContentType | typeof SPARQLUpdateContentType | typeof SPARQLUpdateSingleMatchContentType | typeof TurtleContentType | typeof TurtleLegacyContentType | typeof XHTMLContentType;
40
40
  /** A type for values that serves as inputs */
41
41
  export type ValueType = Term | Node | Date | string | number | boolean | undefined | null | Collection;
42
42
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rdflib",
3
3
  "description": "an RDF library for node.js. Suitable for client and server side.",
4
- "version": "2.3.9",
4
+ "version": "2.4.1",
5
5
  "private": false,
6
6
  "browserslist": [
7
7
  "> 0.5%"
@@ -51,8 +51,6 @@
51
51
  "cross-fetch": "^4.1.0",
52
52
  "jsonld": "^9.0.0",
53
53
  "n3": "^2.0.3",
54
- "package-lock.json": "^1.0.0",
55
- "package.json": "^2.0.1",
56
54
  "solid-namespace": "^0.5.4"
57
55
  },
58
56
  "devDependencies": {
@@ -101,6 +99,7 @@
101
99
  "doc": "typedoc --out ./doc ./src/index.ts --excludePrivate --excludeInternal --tsconfig ./tsconfig.json",
102
100
  "lint": "eslint src tests",
103
101
  "lint:fix": "eslint src tests --fix",
102
+ "prepare": "npm run build",
104
103
  "ignore:prepublishOnly": "npm ci && npm run build:all && npm run doc && npm run test",
105
104
  "postversion": "case \"$npm_package_version\" in *-*) git push ;; *) git push --follow-tags ;; esac",
106
105
  "start": "webpack serve --port 4800",
package/src/fetcher.ts CHANGED
@@ -979,16 +979,43 @@ export default class Fetcher implements CallbackifyInterface {
979
979
  // if metadata flaged clear cache and removeDocument
980
980
  const meta = this.appNode
981
981
  const kb = this.store
982
- const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.sym(docuri), meta).map(st => st.subject)
982
+ // The recorded URI is stored as a string literal, not as a node (see
983
+ // saveRequestMetadata and linkeddata/rdflib.js#427): matching it with
984
+ // kb.sym() never found the request, so a flagged document was answered from
985
+ // the cache instead of being refetched (#870).
986
+ const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), kb.rdfFactory.literal(docuri), meta).map(st => st.subject)
987
+ let answered = false
988
+ let usableAnswer = false
983
989
  for (const request of requests) {
984
990
  const response = kb.any(request, this.ns.link('response'), null, meta) as Quad_Subject
985
- if (response != undefined) { // ts
986
- const quad = kb.statementsMatching(response, this.ns.link('outOfDate'), true as any, meta)
987
- kb.remove(quad)
988
- options.force = true
989
- options.clearPreviousData = true
991
+ // any() returns null (never undefined) when nothing matches: keep the
992
+ // loose check so a request that has no recorded answer yet is not
993
+ // counted as answered.
994
+ if (response == null) {
995
+ continue
996
+ }
997
+ answered = true
998
+ // Every recorded answer that is flagged out-of-date is unusable: the
999
+ // answer of a previous identity/authorization (flagAuthorizationMetadata).
1000
+ // Read the mark with anyJS, as update-manager.editable does: it is stored
1001
+ // as an xsd:boolean literal and the object position has to stay a
1002
+ // wildcard - any() returns null when all four positions are given, even
1003
+ // when the statement exists.
1004
+ const outOfDate = kb.anyJS(response, this.ns.link('outOfDate'), null, meta)
1005
+ if (!outOfDate) {
1006
+ usableAnswer = true
1007
+ break
990
1008
  }
991
1009
  }
1010
+ if (answered && !usableAnswer) {
1011
+ // Refetch instead of answering from the cache, and keep the marks: an
1012
+ // answer of the previous authorization stays excluded, so it cannot win
1013
+ // over the fresh one — and if the refetch fails the state stays
1014
+ // "unknown" instead of becoming definitive again. Once a fresh answer is
1015
+ // recorded the loop above finds it and later loads are cache hits again.
1016
+ options.force = true
1017
+ options.clearPreviousData = true
1018
+ }
992
1019
 
993
1020
  const initialisedOptions = this.initFetchOptions(docuri, options)
994
1021
 
@@ -1346,30 +1373,40 @@ export default class Fetcher implements CallbackifyInterface {
1346
1373
  ) {
1347
1374
  if (!uri) return
1348
1375
  let kb = this.store
1349
- let predicate
1350
1376
  // See http://www.w3.org/TR/powder-dr/#httplink for describedby 2008-12-10
1351
1377
  let obj = kb.rdfFactory.namedNode(Uri.join(uri, originalUri.value))
1352
1378
 
1379
+ let predicates: Quad_Predicate[]
1353
1380
  if (rel === 'alternate' || rel === 'seeAlso' || rel === 'meta' ||
1354
1381
  rel === 'describedby') {
1355
1382
  if (obj.value === originalUri.value) { return }
1356
- predicate = this.ns.rdfs('seeAlso')
1383
+ // Also emit the IANA relation predicate triple (issue #741)
1384
+ predicates = [
1385
+ kb.rdfFactory.namedNode(this.ianaLinkRelation(rel)),
1386
+ this.ns.rdfs('seeAlso')
1387
+ ]
1357
1388
  } else if (rel === 'type') {
1358
- predicate = kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')
1389
+ predicates = [kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type')]
1359
1390
  } else {
1360
1391
  // See https://www.iana.org/assignments/link-relations/link-relations.xml
1361
1392
  // Alas not yet in RDF yet for each predicate
1362
1393
  // encode space in e.g. rel="shortcut icon"
1363
- predicate = kb.rdfFactory.namedNode(
1364
- Uri.join(encodeURIComponent(rel),
1365
- 'http://www.iana.org/assignments/link-relations/')
1394
+ predicates = [kb.rdfFactory.namedNode(
1395
+ this.ianaLinkRelation(rel)
1396
+ )]
1397
+ }
1398
+ kb.addAll(
1399
+ predicates.map(predicate =>
1400
+ reverse
1401
+ ? kb.rdfFactory.quad(obj, predicate, originalUri, why)
1402
+ : kb.rdfFactory.quad(originalUri, predicate, obj, why)
1366
1403
  )
1367
- }
1368
- if (reverse) {
1369
- kb.add(obj, predicate, originalUri, why)
1370
- } else {
1371
- kb.add(originalUri, predicate, obj, why)
1372
- }
1404
+ )
1405
+ }
1406
+
1407
+ private ianaLinkRelation(rel: string) {
1408
+ return Uri.join(encodeURIComponent(rel),
1409
+ 'http://www.iana.org/assignments/link-relations/');
1373
1410
  }
1374
1411
 
1375
1412
  parseLinkHeader (
package/src/index.ts CHANGED
@@ -6,7 +6,6 @@ import Empty from './empty'
6
6
  import Fetcher from './fetcher'
7
7
  import Formula from './formula'
8
8
  import Store from './store'
9
- import jsonParser from './jsonparser'
10
9
  import Literal from './literal'
11
10
  import log from './log'
12
11
  import N3Parser from './n3parser'
@@ -70,7 +69,6 @@ export {
70
69
  Fetcher,
71
70
  Formula,
72
71
  Store,
73
- jsonParser,
74
72
  Literal,
75
73
  log,
76
74
  N3Parser,
package/src/parse.ts CHANGED
@@ -8,7 +8,7 @@ import RDFParser from './rdfxmlparser'
8
8
  import sparqlUpdateParser from './patch-parser'
9
9
  import * as Util from './utils-js'
10
10
  import Formula from './formula'
11
- import { ContentType, TurtleContentType, N3ContentType, RDFXMLContentType, XHTMLContentType, HTMLContentType, SPARQLUpdateContentType, SPARQLUpdateSingleMatchContentType, JSONLDContentType, NQuadsContentType, NQuadsAltContentType } from './types'
11
+ import { ContentType, TurtleContentType, N3ContentType, NTriplesContentType, RDFXMLContentType, XHTMLContentType, HTMLContentType, SPARQLUpdateContentType, SPARQLUpdateSingleMatchContentType, JSONLDContentType, NQuadsContentType, NQuadsAltContentType } from './types'
12
12
  import { Quad } from './tf-types'
13
13
  import type { Document as XmldomDocument } from '@xmldom/xmldom'
14
14
 
@@ -35,7 +35,7 @@ export default function parse (
35
35
  contentType = contentType || TurtleContentType
36
36
  contentType = contentType.split(';')[0] as ContentType
37
37
  try {
38
- if (contentType === N3ContentType || contentType === TurtleContentType) {
38
+ if (contentType === N3ContentType || contentType === TurtleContentType || contentType === NTriplesContentType) {
39
39
  var p = N3Parser(kb, kb, base, base, null, null, '', null)
40
40
  p.loadBuf(str)
41
41
  executeCallback()
@@ -83,7 +83,8 @@ export default function parse (
83
83
  'application/sparql-update-single-match': true,
84
84
  'application/ld+json': true,
85
85
  'application/nquads' : true,
86
- 'application/n-quads' : true
86
+ 'application/n-quads' : true,
87
+ 'application/n-triples' : true
87
88
  }
88
89
 
89
90
  function executeCallback () {
package/src/serialize.ts CHANGED
@@ -20,7 +20,7 @@ import { BlankNode, NamedNode } from './tf-types'
20
20
  */
21
21
  export default function serialize (
22
22
  /** The graph or nodes that should be serialized */
23
- target: Formula | NamedNode | BlankNode | null,
23
+ target: Formula | NamedNode | BlankNode | null | undefined,
24
24
  /** The store */
25
25
  kb: Formula,
26
26
  base?: unknown,
package/src/serializer.js CHANGED
@@ -306,7 +306,7 @@ export class Serializer {
306
306
  return self.atomicTermToN3(x)
307
307
  }
308
308
  var list = x.elements
309
- var rest = kb.sym(rdfns + 'nill')
309
+ var rest = kb.sym(rdfns + 'nil')
310
310
  for (var i = list.length - 1; i >= 0; i--) {
311
311
  var bnode = factory.blankNode()
312
312
  str += termToNT(bnode) + ' ' + termToNT(kb.sym(rdfns + 'first')) + ' ' + termToNT(list[i]) + '.\n'
@@ -679,7 +679,7 @@ export class Serializer {
679
679
  if (this.defaultNamespace && this.defaultNamespace === namesp &&
680
680
  this.flags.indexOf('d') < 0) { // d -> suppress default
681
681
  if (this.flags.indexOf('k') >= 0 &&
682
- this.keyords.indexOf(localid) < 0) {
682
+ this.keywords.indexOf(localid) < 0) {
683
683
  return localid
684
684
  }
685
685
  return ':' + localid
package/src/types.ts CHANGED
@@ -55,6 +55,7 @@ export type ContentType = typeof RDFXMLContentType
55
55
  | typeof N3LegacyContentType
56
56
  | typeof NQuadsAltContentType
57
57
  | typeof NQuadsContentType
58
+ | typeof NTriplesContentType
58
59
  | typeof SPARQLUpdateContentType
59
60
  | typeof SPARQLUpdateSingleMatchContentType
60
61
  | typeof TurtleContentType
package/esm/jsonparser.js DELETED
@@ -1,53 +0,0 @@
1
- export default (function () {
2
- return {
3
- parseJSON: function (data, source, store) {
4
- var subject, predicate, object;
5
- var bnodes = {};
6
- var why = store.sym(source);
7
- for (var x in data) {
8
- if (x.indexOf('_:') === 0) {
9
- if (bnodes[x]) {
10
- subject = bnodes[x];
11
- } else {
12
- subject = store.bnode(x);
13
- bnodes[x] = subject;
14
- }
15
- } else {
16
- subject = store.sym(x);
17
- }
18
- var preds = data[x];
19
- for (var y in preds) {
20
- var objects = preds[y];
21
- predicate = store.sym(y);
22
- for (var z in objects) {
23
- var obj = objects[z];
24
- if (obj.type === 'uri') {
25
- object = store.sym(obj.value);
26
- store.add(subject, predicate, object, why);
27
- } else if (obj.type === 'BlankNode') {
28
- if (bnodes[obj.value]) {
29
- object = bnodes[obj.value];
30
- } else {
31
- object = store.bnode(obj.value);
32
- bnodes[obj.value] = object;
33
- }
34
- store.add(subject, predicate, object, why);
35
- } else if (obj.type === 'Literal') {
36
- // var datatype
37
- if (obj.datatype) {
38
- object = store.literal(obj.value, undefined, store.sym(obj.datatype));
39
- } else if (obj.lang) {
40
- object = store.literal(obj.value, obj.lang);
41
- } else {
42
- object = store.literal(obj.value);
43
- }
44
- store.add(subject, predicate, object, why);
45
- } else {
46
- throw new Error('error: unexpected termtype: ' + z.type);
47
- }
48
- }
49
- }
50
- }
51
- }
52
- };
53
- })();
@@ -1,4 +0,0 @@
1
- declare namespace _default {
2
- function parseJSON(data: any, source: any, store: any): void;
3
- }
4
- export default _default;