From f0a166c2d9558c69a143c35f02b9cae293b13ac2 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Fri, 29 Nov 2019 14:58:10 +0100 Subject: [PATCH 01/29] Enable class properties --- .babelrc | 7 +- package-lock.json | 167 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 3 files changed, 167 insertions(+), 8 deletions(-) diff --git a/.babelrc b/.babelrc index 453c2e5d9..2253b6c75 100644 --- a/.babelrc +++ b/.babelrc @@ -1,9 +1,10 @@ { "presets": [ "@babel/preset-env", - "@babel/preset-typescript", + "@babel/preset-typescript" ], "plugins": [ - "@babel/plugin-transform-runtime" - ], + "@babel/plugin-transform-runtime", + "@babel/plugin-proposal-class-properties" + ] } diff --git a/package-lock.json b/package-lock.json index bb27e00c3..64cdc1a5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -339,6 +339,163 @@ "@babel/plugin-syntax-async-generators": "^7.2.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.7.4.tgz", + "integrity": "sha512-EcuXeV4Hv1X3+Q1TsuOmyyxeTRiSqurGJ26+I/FW1WbymmRRapVORm6x1Zl3iDIHyRxEs+VXWp6qnlcfcJSbbw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.7.4.tgz", + "integrity": "sha512-l+OnKACG4uiDHQ/aJT8dwpR+LhCJALxL0mJ6nzjB25e5IPwqV1VOsY7ah6UB1DG+VOXAIMtuC54rFJGiHkxjgA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-member-expression-to-functions": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-replace-supers": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4" + } + }, + "@babel/helper-function-name": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", + "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz", + "integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz", + "integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", + "dev": true, + "requires": { + "@babel/types": "^7.7.4" + } + }, + "@babel/parser": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz", + "integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==", + "dev": true + }, + "@babel/template": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/traverse": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.5.5", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "@babel/plugin-proposal-dynamic-import": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", @@ -1841,7 +1998,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2427,7 +2584,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -2440,7 +2597,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -2673,7 +2830,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -7119,7 +7276,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { diff --git a/package.json b/package.json index 82b1fa6be..9e0bc61bd 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "devDependencies": { "@babel/cli": "^7.5.5", "@babel/core": "^7.5.5", + "@babel/plugin-proposal-class-properties": "^7.7.4", "@babel/plugin-transform-runtime": "^7.5.5", "@babel/preset-env": "^7.5.5", "@babel/preset-typescript": "^7.6.0", From dbf61f909a478e656cedc461b6f6c4f4c759650b Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Fri, 29 Nov 2019 15:00:16 +0100 Subject: [PATCH 02/29] Move term utils to separate file --- src/fetcher.js | 2 +- src/formula.js | 3 ++- src/index.ts | 2 ++ src/store.js | 3 ++- src/update-manager.js | 2 +- src/util.js | 19 ------------------- src/utils/terms.ts | 28 ++++++++++++++++++++++++++++ tests/unit/util-test.js | 3 ++- 8 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 src/utils/terms.ts diff --git a/src/fetcher.js b/src/fetcher.js index 362908db4..5a37eca51 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -34,7 +34,7 @@ import rdfParse from './parse' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import * as Uri from './uri' -import { isNamedNode } from './util' +import { isNamedNode } from './utils/terms' import * as Util from './util' import serialize from './serialize' diff --git a/src/formula.js b/src/formula.js index 34a361155..6bb2edee2 100644 --- a/src/formula.js +++ b/src/formula.js @@ -9,7 +9,8 @@ import Namespace from './namespace' import Node from './node' import Serializer from './serialize' import Statement from './statement' -import { appliedFactoryMethods, arrayToStatements, isStatement } from './util' +import { appliedFactoryMethods, arrayToStatements } from './util' +import { isStatement } from './utils/terms' import Variable from './variable' /** @module formula */ diff --git a/src/index.ts b/src/index.ts index f49c6ceeb..1a921d305 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,8 @@ import * as uri from './uri' import * as Util from './util' import Variable from './variable' +export * from './utils/terms' + const NextId = BlankNode.nextId const { fromNT } = Formula.prototype; diff --git a/src/store.js b/src/store.js index acbc2a6e5..88a01ce06 100644 --- a/src/store.js +++ b/src/store.js @@ -20,7 +20,8 @@ import ClassOrder from './class-order' import { defaultGraphURI } from './data-factory-internal' import DataFactory from './data-factory' import Formula from './formula' -import { ArrayIndexOf, isStatement, isStore, RDFArrayRemove } from './util' +import { ArrayIndexOf, RDFArrayRemove } from './util' +import { isStatement, isStore } from './utils/terms' import Statement from './statement' import Node from './node' import Variable from './variable' diff --git a/src/update-manager.js b/src/update-manager.js index 294c19057..3d39aedcc 100644 --- a/src/update-manager.js +++ b/src/update-manager.js @@ -11,7 +11,7 @@ import DataFactory from './data-factory' import Namespace from './namespace' import Serializer from './serializer' import { join as uriJoin } from './uri' -import { isStore } from './util' +import { isStore } from './utils/terms' import * as Util from './util' /** Update Manager diff --git a/src/util.js b/src/util.js index 0550963ec..08de267bd 100644 --- a/src/util.js +++ b/src/util.js @@ -30,25 +30,6 @@ export const appliedFactoryMethods = [ 'supports', ] -export function isTerm(obj) { - return typeof obj === "object" - && obj !== null - && "termType" in obj - && "value" in obj -} - -export function isStatement(obj) { - return typeof obj === "object" && obj !== null && "subject" in obj -} - -export function isStore(obj) { - return typeof obj === "object" && obj !== null && "statements" in obj -} - -export function isNamedNode(obj) { - return isTerm(obj) && obj.termType === "NamedNode" -} - /** * Loads ontologies of the data we load (this is the callback from the kb to * the fetcher). diff --git a/src/utils/terms.ts b/src/utils/terms.ts new file mode 100644 index 000000000..448f70a8d --- /dev/null +++ b/src/utils/terms.ts @@ -0,0 +1,28 @@ +import { TFTerm } from "../types"; + +export function isTerm(obj) { + return typeof obj === 'object' + && obj !== null + && 'termType' in obj + && 'value' in obj +} + +export function isStatement(obj) { + return typeof obj === 'object' && obj !== null && 'subject' in obj +} + +export function isStore(obj) { + return typeof obj === 'object' && obj !== null && 'statements' in obj +} + +export function isNamedNode(obj) { + return isTerm(obj) && obj.termType === 'NamedNode' +} + +export function termValue(node: TFTerm | string): string { + if (typeof node === 'string') { + return node + } + + return node.value +} diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index 697ead171..4c54f5ad4 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -5,7 +5,8 @@ import CanonicalDataFactory from '../../src/data-factory-internal' import Literal from '../../src/literal' import NamedNode from '../../src/named-node' import Statement from '../../src/statement' -import { arrayToStatements, isNamedNode, isStatement, isTerm } from '../../src/util' +import { arrayToStatements } from '../../src/util' +import { isNamedNode, isStatement, isTerm } from '../../src/utils/terms' describe('util', () => { describe('isTerm', () => { From 071eeafb499d245380a948bc6f839567008ae71a Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Fri, 29 Nov 2019 15:03:29 +0100 Subject: [PATCH 03/29] Convert terms to typescript Sans formula, which technically is a term --- src/blank-node.js | 65 ------------- src/blank-node.ts | 91 ++++++++++++++++++ src/{class-order.js => class-order.ts} | 3 + src/collection.js | 51 ---------- src/collection.ts | 125 +++++++++++++++++++++++++ src/default-graph.js | 13 --- src/default-graph.ts | 18 ++++ src/{empty.js => empty.ts} | 12 ++- src/formula.js | 2 +- src/{literal.js => literal.ts} | 106 +++++++++++++-------- src/{named-node.js => named-node.ts} | 60 +++++++----- src/namespace.js | 9 -- src/namespace.ts | 14 +++ src/node-internal.js | 51 ---------- src/node-internal.ts | 116 +++++++++++++++++++++++ src/{node.js => node.ts} | 17 +--- src/types.ts | 25 +++++ src/{variable.js => variable.ts} | 37 +++++--- 18 files changed, 533 insertions(+), 282 deletions(-) delete mode 100644 src/blank-node.js create mode 100644 src/blank-node.ts rename src/{class-order.js => class-order.ts} (84%) delete mode 100644 src/collection.js create mode 100644 src/collection.ts delete mode 100644 src/default-graph.js create mode 100644 src/default-graph.ts rename src/{empty.js => empty.ts} (53%) rename src/{literal.js => literal.ts} (66%) rename src/{named-node.js => named-node.ts} (65%) delete mode 100644 src/namespace.js create mode 100644 src/namespace.ts delete mode 100644 src/node-internal.js create mode 100644 src/node-internal.ts rename src/{node.js => node.ts} (74%) create mode 100644 src/types.ts rename src/{variable.js => variable.ts} (67%) diff --git a/src/blank-node.js b/src/blank-node.js deleted file mode 100644 index 40aaffa36..000000000 --- a/src/blank-node.js +++ /dev/null @@ -1,65 +0,0 @@ -'use strict' -import ClassOrder from './class-order' -import Node from './node-internal' - -export default class BlankNode extends Node { - constructor (id) { - super() - this.termType = BlankNode.termType - - if (id) { - if (typeof id !== 'string') { - console.log('Bad blank id:', id) - throw new Error('Bad id argument to new blank node: ' + id) - } - if (id.includes('#')) { - // Is a URI with hash fragment - let fragments = id.split('#') - id = fragments[fragments.length - 1] - } - this.id = id - // this.id = '' + BlankNode.nextId++ - } else { - this.id = 'n' + BlankNode.nextId++ - } - - this.value = this.id - } - - compareTerm (other) { - if (this.classOrder < other.classOrder) { - return -1 - } - if (this.classOrder > other.classOrder) { - return +1 - } - if (this.id < other.id) { - return -1 - } - if (this.id > other.id) { - return +1 - } - return 0 - } - - copy (formula) { // depends on the formula - var bnodeNew = new BlankNode() - formula.copyTo(this, bnodeNew) - return bnodeNew - } - - toCanonical () { - return '_:' + this.value - } - - toString () { - return BlankNode.NTAnonymousNodePrefix + this.id - } -} - -BlankNode.nextId = 0 -BlankNode.termType = 'BlankNode' -BlankNode.NTAnonymousNodePrefix = '_:' -BlankNode.prototype.classOrder = ClassOrder['BlankNode'] -BlankNode.prototype.isBlank = 1 -BlankNode.prototype.isVar = 1 diff --git a/src/blank-node.ts b/src/blank-node.ts new file mode 100644 index 000000000..927793fa8 --- /dev/null +++ b/src/blank-node.ts @@ -0,0 +1,91 @@ +'use strict' +import ClassOrder from './class-order' +import Node from './node-internal' +import { TermType } from './types' + +export default class BlankNode extends Node { + static termType = TermType.BlankNode + static NTAnonymousNodePrefix = '_:' + /** The next unique identifier for blank nodes */ + static nextId = 0 + + private static getId (id: string | unknown): string { + if (id) { + if (typeof id !== 'string') { + console.log('Bad blank id:', id) + throw new Error('Bad id argument to new blank node: ' + id) + } + + if (id.includes('#')) { + // Is a URI with hash fragment + let fragments = id.split('#') + return fragments[fragments.length - 1] + } + + return id + } + + return 'n' + BlankNode.nextId++ + } + + classOrder = ClassOrder.BlankNode + /** Whether this is a blank node */ + isBlank = 1 + /** + * This type of node is a variable. + * + * Note that the existence of this property already indicates that it is a variable. + */ + isVar = 1 + termType = BlankNode.termType + + /** + * Initializes this node + * @param [id] - The identifier for the blank node + */ + constructor (id?: string | unknown) { + super(BlankNode.getId(id)) + } + + /** + * The identifier for the blank node + * @deprecated use {value} instead. + */ + public get id (): string { + return this.value + } + + public set id (value: string) { + this.value = value + } + + compareTerm (other) { + if (this.classOrder < other.classOrder) { + return -1 + } + if (this.classOrder > other.classOrder) { + return +1 + } + if (this.id < other.id) { + return -1 + } + if (this.id > other.id) { + return +1 + } + return 0 + } + + copy (formula) { // depends on the formula + var bnodeNew = new BlankNode() + formula.copyTo(this, bnodeNew) + return bnodeNew + } + + toCanonical () { + return '_:' + this.value + } + + toString () { + return BlankNode.NTAnonymousNodePrefix + this.id + } +} diff --git a/src/class-order.js b/src/class-order.ts similarity index 84% rename from src/class-order.js rename to src/class-order.ts index f2529983e..6801115bb 100644 --- a/src/class-order.js +++ b/src/class-order.ts @@ -1,3 +1,6 @@ +/** +* Class orders +*/ export default { 'Literal': 1, 'Collection': 3, diff --git a/src/collection.js b/src/collection.js deleted file mode 100644 index 00ad37c67..000000000 --- a/src/collection.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict' -import BlankNode from './blank-node' -import ClassOrder from './class-order' -import Node from './node-internal' - -export default class Collection extends Node { - constructor (initial) { - super() - this.termType = Collection.termType - this.id = BlankNode.nextId++ - this.elements = [] - this.closed = false - if (initial && initial.length > 0) { - initial.forEach(element => { - this.elements.push(Node.fromValue(element)) - }) - } - } - append (element) { - return this.elements.push(element) - } - close () { - this.closed = true - return this.closed - } - shift () { - return this.elements.shift() - } - substitute (bindings) { - var elementsCopy = this.elements.map(function (ea) { - ea.substitute(bindings) - }) - return new Collection(elementsCopy) - } - toNT () { - return Collection.toNT(this) - } - static toNT (collection) { - return BlankNode.NTAnonymousNodePrefix + collection.id - } - toString () { - return '(' + this.elements.join(' ') + ')' - } - unshift (element) { - return this.elements.unshift(element) - } -} -Collection.termType = 'Collection' -Collection.prototype.classOrder = ClassOrder['Collection'] -Collection.prototype.compareTerm = BlankNode.prototype.compareTerm -Collection.prototype.isVar = 0 diff --git a/src/collection.ts b/src/collection.ts new file mode 100644 index 000000000..3bebb69dd --- /dev/null +++ b/src/collection.ts @@ -0,0 +1,125 @@ +'use strict' +import BlankNode from './blank-node' +import ClassOrder from './class-order' +import Literal from "./literal"; +import Node from './node-internal' +import { TermType } from './types'; +import Variable from "./variable"; + +/** + * Creates an RDF Node from a native javascript value. + * RDF Nodes are returned unchanged, undefined returned as itself. + * @param value {Node|Date|String|Number|Boolean|Undefined} + * @return {Node|Collection} + */ +export function fromValue (value) { + if (typeof value === 'undefined' || value === null) { + return value + } + const isNode = value && value.termType + if (isNode) { // a Node subclass or a Collection + return value + } + + if (Array.isArray(value)) { + return new Collection(value) + } + + return Literal.fromValue(value) +} + +/** + * A collection of other RDF nodes + * + * Use generic T to control the contents of the array. + */ +export default class Collection | Literal | Variable> extends Node { + static termType = TermType.Collection + + classOrder = ClassOrder.Collection + closed: boolean = false + compareTerm = BlankNode.prototype.compareTerm + /** + * The nodes in this collection + */ + elements: T[] = [] + isVar = 0 + termType = TermType.Collection + + constructor (initial?) { + super((BlankNode.nextId++).toString()) + + if (initial && initial.length > 0) { + initial.forEach(element => { + this.elements.push(fromValue(element)) + }) + } + } + + public get id (): string { + return this.value + } + + public set id (value) { + this.value = value + } + + /** + * Appends an element to this collection + * @param element - The new element + */ + append (element): number { + return this.elements.push(element) + } + + /** + * Closes this collection + */ + close (): boolean { + this.closed = true + return this.closed + } + + /** + * Removes the first element from the collection (and return it) + */ + shift () { + return this.elements.shift() + } + + /** + * Gets a new Collection with the substituting bindings applied + * @param bindings - The bindings to substitute + */ + substitute (bindings) { + const elementsCopy = this.elements.map(function (ea) { + ea.substitute(bindings) + }) + + return new Collection(elementsCopy) + } + + toNT () { + return Collection.toNT(this) + } + + static toNT (collection) { + return BlankNode.NTAnonymousNodePrefix + collection.id + } + + /** + * Serializes the collection to a string. + * Surrounded by (parentheses) and separated by spaces. + */ + toString () { + return '(' + this.elements.join(' ') + ')' + } + + /** + * Prepends the specified element to the collection's front + * @param element - The element to prepend + */ + unshift (element) { + return this.elements.unshift(element) + } +} diff --git a/src/default-graph.js b/src/default-graph.js deleted file mode 100644 index a45cd7af5..000000000 --- a/src/default-graph.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' -import Node from './node' - -export default class DefaultGraph extends Node { - constructor () { - super() - this.termType = 'DefaultGraph' - this.value = '' - } - toCanonical () { - return this.value - } -} diff --git a/src/default-graph.ts b/src/default-graph.ts new file mode 100644 index 000000000..193481213 --- /dev/null +++ b/src/default-graph.ts @@ -0,0 +1,18 @@ +'use strict' +import Node from './node-internal' +import { TermType } from "./types"; + +/** The RDF default graph */ +export default class DefaultGraph extends Node { + static termType = TermType.DefaultGraph; + + termType = TermType.DefaultGraph; + + constructor () { + super('') + } + + toCanonical () { + return this.value + } +} diff --git a/src/empty.js b/src/empty.ts similarity index 53% rename from src/empty.js rename to src/empty.ts index a24ee039d..80dbf7441 100644 --- a/src/empty.js +++ b/src/empty.ts @@ -1,16 +1,20 @@ 'use strict' -import Node from './node' +import Node from './node-internal' +import { TermType } from "./types"; /** * Singleton subclass of an empty Collection. */ export default class Empty extends Node { + static termType = TermType.Empty + + termType = TermType.Empty + constructor () { - super() - this.termType = Empty.termType + super('') } + toString () { return '()' } } -Empty.termType = 'empty' diff --git a/src/formula.js b/src/formula.js index 6bb2edee2..ab3757b01 100644 --- a/src/formula.js +++ b/src/formula.js @@ -26,7 +26,7 @@ export default class Formula extends Node { * @param {DataFactory} opts.rdfFactory - The rdf factory that should be used by the store */ constructor (statements, constraints, initBindings, optional, opts = {}) { - super() + super('') this.termType = Formula.termType this.statements = statements || [] this.constraints = constraints || [] diff --git a/src/literal.js b/src/literal.ts similarity index 66% rename from src/literal.js rename to src/literal.ts index 55ba43ffd..5af121984 100644 --- a/src/literal.js +++ b/src/literal.ts @@ -2,45 +2,80 @@ import ClassOrder from './class-order' import NamedNode from './named-node' import Node from './node-internal' +import { TermType } from './types'; import XSD from './xsd-internal' +/** + * An RDF literal, containing some value which isn't expressed as an IRI. + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ export default class Literal extends Node { - constructor (value, language, datatype) { - super() - this.termType = Literal.termType - this.value = value + static termType = TermType.Literal + + classOrder = ClassOrder.Literal + datatype = XSD.string + isVar = 0 + /** + * The language for the literal + */ + language: string = '' + termType = TermType.Literal + + + constructor (value: string, language?: string | null, datatype?) { + super(value) + if (language) { - this.lang = language - datatype = XSD.langString - } - // If not specified, a literal has the implied XSD.string default datatype - if (datatype) { + this.language = language + this.datatype = XSD.langString + } else if (datatype) { this.datatype = NamedNode.fromValue(datatype) + } else { + this.datatype = XSD.string } } - copy () { + + /** + * Gets a copy of this literal + */ + copy (): Literal { return new Literal(this.value, this.lang, this.datatype) } - equals (other) { + + /** + * Gets whether two literals are the same + * @param other The other statement + */ + equals (other: any): boolean { if (!other) { return false } + return (this.termType === other.termType) && (this.value === other.value) && (this.language === other.language) && ((!this.datatype && !other.datatype) || (this.datatype && this.datatype.equals(other.datatype))) } - get language () { - return this.lang + + /** + * The language for the literal + * @deprecated use {language} instead + */ + get lang () { + return this.language } - set language (language) { - this.lang = language || '' + + set lang (language) { + this.language = language || '' } + toNT() { return Literal.toNT(this) } - static toNT (literal) { + + /** Serializes a literal to an N-Triples string */ + static toNT (literal: Literal): string { if (typeof literal.value === 'number') { return '' + literal.value } else if (typeof literal.value !== 'string') { @@ -61,26 +96,25 @@ export default class Literal extends Node { } return str } + toString () { return '' + this.value } + /** - * @method fromBoolean - * @static - * @param value {Boolean} - * @return {Literal} + * Builds a literal node from a boolean value + * @param value {Boolean} The value */ - static fromBoolean (value) { + static fromBoolean (value: boolean): Literal { let strValue = value ? '1' : '0' return new Literal(strValue, null, XSD.boolean) } + /** - * @method fromDate - * @static - * @param value {Date} - * @return {Literal} + * Builds a literal node from a date value + * @param value The value */ - static fromDate (value) { + static fromDate(value: Date): Literal { if (!(value instanceof Date)) { throw new TypeError('Invalid argument to Literal.fromDate()') } @@ -92,13 +126,12 @@ export default class Literal extends Node { d2(value.getUTCMinutes()) + ':' + d2(value.getUTCSeconds()) + 'Z' return new Literal(date, null, XSD.dateTime) } + /** - * @method fromNumber - * @static - * @param value {Number} - * @return {Literal} + * Builds a literal node from a number value + * @param value The value */ - static fromNumber (value) { + static fromNumber(value: number): Literal { if (typeof value !== 'number') { throw new TypeError('Invalid argument to Literal.fromNumber()') } @@ -111,10 +144,10 @@ export default class Literal extends Node { } return new Literal(strValue, null, datatype) } + /** - * @method fromValue - * @param value - * @return {Literal} + * Builds a literal node from an input value + * @param value The input value */ static fromValue (value) { if (typeof value === 'undefined' || value === null) { @@ -140,8 +173,3 @@ export default class Literal extends Node { } } -Literal.termType = 'Literal' -Literal.prototype.classOrder = ClassOrder['Literal'] -Literal.prototype.datatype = XSD.string -Literal.prototype.lang = '' -Literal.prototype.isVar = 0 diff --git a/src/named-node.js b/src/named-node.ts similarity index 65% rename from src/named-node.js rename to src/named-node.ts index b806386ca..d22ab5848 100644 --- a/src/named-node.js +++ b/src/named-node.ts @@ -1,39 +1,40 @@ 'use strict' import ClassOrder from './class-order' import Node from './node-internal' +import { TermType } from './types'; +import { termValue } from "./utils/terms"; /** - * @class NamedNode - * @extends Node + * A named (IRI) RDF node */ export default class NamedNode extends Node { + static termType = TermType.NamedNode + + classOrder = ClassOrder.NamedNode + termType = TermType.NamedNode + /** + * Create a named (IRI) RDF Node * @constructor - * @param iri {String} + * @param iri {String} - The IRI for this node */ constructor (iri) { - super() - this.termType = NamedNode.termType + super(termValue(iri)) - if (iri && iri.termType === NamedNode.termType) { // param is a named node - iri = iri.value - } - - if (!iri) { + if (!this.value) { throw new Error('Missing IRI for NamedNode') } - if (!iri.includes(':')) { + if (!this.value.includes(':')) { throw new Error('NamedNode IRI "' + iri + '" must be absolute.') } - if (iri.includes(' ')) { + if (this.value.includes(' ')) { var message = 'Error: NamedNode IRI "' + iri + '" must not contain unencoded spaces.' throw new Error(message) } - - this.value = iri } + /** * Returns an $rdf node for the containing directory, ending in slash. */ @@ -44,10 +45,11 @@ export default class NamedNode extends Node { if ((q >= 0 && p < q + 2) || p < 0) return null return new NamedNode(str.slice(0, p + 1)) } - /** - * Returns an NN for the whole web site, ending in slash. - * Contrast with the "origin" which does NOT have a trailing slash - */ + + /** + * Returns an NN for the whole web site, ending in slash. + * Contrast with the "origin" which does NOT have a trailing slash + */ site () { var str = this.uri.split('#')[0] var p = str.indexOf('//') @@ -59,6 +61,11 @@ export default class NamedNode extends Node { return new NamedNode(str.slice(0, q + 1)) } } + + /** + * Creates the fetchable named node for the document. + * Removes everything from the # anchor tag. + */ doc () { if (this.uri.indexOf('#') < 0) { return this @@ -66,25 +73,35 @@ export default class NamedNode extends Node { return new NamedNode(this.uri.split('#')[0]) } } + + /** + * Returns the URI including + */ toString () { return '<' + this.uri + '>' } - /* The local identifier with the document - */ + /** The local identifier with the document */ id () { return this.uri.split('#')[1] } /** * Legacy getter and setter alias, node.uri + * @deprecated use {value} */ get uri () { return this.value } + set uri (uri) { this.value = uri } + + /** + * Creates a named node from the specified input value + * @param value - An input value + */ static fromValue (value) { if (typeof value === 'undefined' || value === null) { return value @@ -96,6 +113,3 @@ export default class NamedNode extends Node { return new NamedNode(value) } } -NamedNode.termType = 'NamedNode' -NamedNode.prototype.classOrder = ClassOrder['NamedNode'] -NamedNode.prototype.isVar = 0 diff --git a/src/namespace.js b/src/namespace.js deleted file mode 100644 index b42486d13..000000000 --- a/src/namespace.js +++ /dev/null @@ -1,9 +0,0 @@ -import NamedNode from './named-node' - -export default function Namespace (nsuri, factory) { - const dataFactory = factory || { namedNode: (value) => new NamedNode(value) } - - return function (ln) { - return dataFactory.namedNode(nsuri + (ln || '')) - } -} diff --git a/src/namespace.ts b/src/namespace.ts new file mode 100644 index 000000000..814b3c225 --- /dev/null +++ b/src/namespace.ts @@ -0,0 +1,14 @@ +import NamedNode from './named-node' + +/** + * Gets a namespace for the specified namespace's URI + * @param nsuri - The URI for the namespace + * @param [factory] - The factory for creating named nodes with + */ +export default function Namespace (nsuri: string, factory?) { + const dataFactory = factory || { namedNode: (value) => new NamedNode(value) } + + return function (ln: string) { + return dataFactory.namedNode(nsuri + (ln || '')) + } +} diff --git a/src/node-internal.js b/src/node-internal.js deleted file mode 100644 index 2c5e63b8d..000000000 --- a/src/node-internal.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict' - -/** - * The superclass of all RDF Statement objects, that is - * NamedNode, Literal, BlankNode, etc. - * @class Node - */ - -export default class Node { - substitute (bindings) { - console.log('@@@ node substitute' + this) - return this - } - compareTerm (other) { - if (this.classOrder < other.classOrder) { - return -1 - } - if (this.classOrder > other.classOrder) { - return +1 - } - if (this.value < other.value) { - return -1 - } - if (this.value > other.value) { - return +1 - } - return 0 - } - equals (other) { - if (!other) { - return false - } - return (this.termType === other.termType) && - (this.value === other.value) - } - hashString () { - return this.toCanonical() - } - sameTerm (other) { - return this.equals(other) - } - toCanonical () { - return this.toNT() - } - toNT () { - return this.toString() - } - toString () { - throw new Error('Node.toString() is abstract - see the subclasses instead') - } -} diff --git a/src/node-internal.ts b/src/node-internal.ts new file mode 100644 index 000000000..fa519b7d6 --- /dev/null +++ b/src/node-internal.ts @@ -0,0 +1,116 @@ +/** + * The superclass of all RDF Statement objects, that is + * NamedNode, Literal, BlankNode, etc. + * Should not be instantiated directly. + * Also called Term. + * @link https://rdf.js.org/data-model-spec/#term-interface + * @class Node + */ +export default class Node { + static fromValue: (value: any) => unknown; + static toJS: (term: any) => Date | Number | string | boolean | object | Array; + + /** + * The type of node + */ + termType!: string; + + /** + * The class order for this node + */ + classOrder!: number; + + /** + * The node's value + */ + value: string; + + constructor(value: string) { + this.value = value + } + + /** + * Creates the substituted node for this one, according to the specified bindings + * @param bindings - Bindings of identifiers to nodes + */ + substitute (bindings): Node | any { + console.log('@@@ node substitute' + this) + return this + } + + /** + * Compares this node with another + * @param other - The other node + */ + compareTerm (other): number { + if (this.classOrder < other.classOrder) { + return -1 + } + if (this.classOrder > other.classOrder) { + return +1 + } + if (this.value < other.value) { + return -1 + } + if (this.value > other.value) { + return +1 + } + return 0 + } + + /** + * Compares whether the two nodes are equal + * @param other The other node + */ + equals (other): boolean { + if (!other) { + return false + } + return (this.termType === other.termType) && + (this.value === other.value) + } + + /** + * Creates a hash for this node + * @deprecated use {rdfFactory.id} instead if possible + */ + hashString (): string { + return this.toCanonical() + } + + /** + * Compares whether this node is the same as the other one + * @param other - Another node + */ + sameTerm (other): boolean { + return this.equals(other) + } + + /** + * Creates a canonical string representation of this node + */ + toCanonical (): string { + return this.toNT() + } + + /** + * Creates a n-triples string representation of this node + */ + toNT (): string { + return this.toString() + } + + /** + * Creates a n-quads string representation of this node + */ + toNQ (): string { + return this.toNT(); + } + + /** + * Creates a string representation of this node + */ + toString (): string { + throw new Error('Node.toString() is abstract - see the subclasses instead') + } +} diff --git a/src/node.js b/src/node.ts similarity index 74% rename from src/node.js rename to src/node.ts index 1e16803b7..55c6b26b4 100644 --- a/src/node.js +++ b/src/node.ts @@ -3,8 +3,7 @@ // This file attaches all functionality to Node // that would otherwise require circular dependencies. import Node from './node-internal' -import Collection from './collection' -import Literal from './literal' +import Collection, { fromValue } from "./collection"; export default Node @@ -16,19 +15,7 @@ export default Node * @param value {Node|Date|String|Number|Boolean|Undefined} * @return {Node|Collection} */ -Node.fromValue = function fromValue (value) { - if (typeof value === 'undefined' || value === null) { - return value - } - const isNode = value && value.termType - if (isNode) { // a Node subclass or a Collection - return value - } - if (Array.isArray(value)) { - return new Collection(value) - } - return Literal.fromValue(value) -} +Node.fromValue = fromValue import Namespace from './namespace' const ns = { xsd: Namespace('http://www.w3.org/2001/XMLSchema#') } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..1f6b770be --- /dev/null +++ b/src/types.ts @@ -0,0 +1,25 @@ +/** + * All the possible TermTypes + * @todo Convert these to const enums when it's supported https://github.com/babel/babel/issues/8741 + */ +export const TermType = { + NamedNode: 'NamedNode', + BlankNode: 'BlankNode', + Literal: 'Literal', + Variable: 'Variable', + DefaultGraph: 'DefaultGraph', + // The next ones are not specified by the rdf.js taskforce + Collection: 'Collection', + Empty: 'Empty', + Graph: 'Graph', +} + +/** + * RDF/JS taskforce Term + * @link https://rdf.js.org/data-model-spec/#term-interface + */ +export interface TFTerm { + termType: string + value: string + equals(other: TFTerm): boolean +} diff --git a/src/variable.js b/src/variable.ts similarity index 67% rename from src/variable.js rename to src/variable.ts index 65d199e1f..d38d854cc 100644 --- a/src/variable.js +++ b/src/variable.ts @@ -1,6 +1,7 @@ 'use strict' import ClassOrder from './class-order' -import Node from './node' +import Node from './node-internal' +import { TermType } from "./types"; import * as Uri from './uri' /** @@ -12,37 +13,51 @@ import * as Uri from './uri' * @class Variable */ export default class Variable extends Node { + static termType = TermType.Variable + + /** The base string for a variable's name */ + base = 'varid:' + classOrder = ClassOrder.Variable + isVar = 1 + termType = TermType.Variable + /** The unique identifier of this variable */ + uri: string + + /** + * Initializes this variable + * @param name The variable's name + */ constructor (name = '') { - super() - this.termType = Variable.termType - this.value = name - this.base = 'varid:' + super(name) this.uri = Uri.join(name, this.base) } + equals (other) { if (!other) { return false } + return (this.termType === other.termType) && (this.value === other.value) } + hashString () { return this.toString() } + substitute (bindings) { var ref return (ref = bindings[this.toNT()]) != null ? ref : this } + toString () { return Variable.toString(this) } + static toString (variable) { if (variable.uri.slice(0, variable.base.length) === variable.base) { - return '?' + variable.uri.slice(variable.base.length) + return `?${variable.uri.slice(variable.base.length)}` } - return '?' + variable.uri + + return `?${variable.uri}` } } - -Variable.termType = 'Variable' -Variable.prototype.classOrder = ClassOrder['Variable'] -Variable.prototype.isVar = 1 From 5442f71f58ccb38f8af98d3be7cc401656d0b540 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Fri, 29 Nov 2019 18:10:49 +0100 Subject: [PATCH 04/29] Convert statement and formula --- src/{formula.js => formula.ts} | 503 ++++++++++++++++++++------------- src/node-internal.ts | 2 +- src/statement.js | 64 ----- src/statement.ts | 120 ++++++++ 4 files changed, 427 insertions(+), 262 deletions(-) rename src/{formula.js => formula.ts} (59%) delete mode 100644 src/statement.js create mode 100644 src/statement.ts diff --git a/src/formula.js b/src/formula.ts similarity index 59% rename from src/formula.js rename to src/formula.ts index ab3757b01..859038cb4 100644 --- a/src/formula.js +++ b/src/formula.ts @@ -9,23 +9,53 @@ import Namespace from './namespace' import Node from './node' import Serializer from './serialize' import Statement from './statement' +import { TermType } from "./types"; import { appliedFactoryMethods, arrayToStatements } from './util' import { isStatement } from './utils/terms' import Variable from './variable' /** @module formula */ +export interface FormulaOpts { + rdfFactory?: any +} + +/** + * A formula, or store of RDF statements + */ export default class Formula extends Node { + static termType = TermType.Graph + + classOrder = ClassOrder.Graph + /** The additional constraints */ + constraints: ReadonlyArray; /** - * @constructor - * @param statements - Initial array of statements - * @param constraints - initial array of constraints - * @param initBindings - initial bindings used in Query - * @param optional - optional - * @param opts - * @param {DataFactory} opts.rdfFactory - The rdf factory that should be used by the store - */ - constructor (statements, constraints, initBindings, optional, opts = {}) { + * The accompanying fetcher instance. + * + * Is set by the fetcher when initialized. + */ + fetcher?: any + initBindings: { [id: string]: Node; } + isVar = 0 + ns = Namespace + optional: any[] + /** The factory used to generate statements and terms */ + rdfFactory: any + /** The stored statements */ + statements: Statement[]; + termType = TermType.Graph + + /** + * Initializes this formula + * @constructor + * @param statements - Initial array of statements + * @param constraints - initial array of constraints + * @param initBindings - initial bindings used in Query + * @param optional - optional + * @param opts + * @param {DataFactory} opts.rdfFactory - The rdf factory that should be used by the store +*/ + constructor (statements?: Statement[], constraints?, initBindings?, optional?, opts: Partial = {}) { super('') this.termType = Formula.termType this.statements = statements || [] @@ -40,24 +70,31 @@ export default class Formula extends Node { } } /** Add a statement from its parts - * @param {Node} subject - the first part of the statemnt - * @param {Node} predicate - the second part of the statemnt - * @param {Node} obbject - the third part of the statemnt - * @param {Node} graph - the last part of the statemnt - */ + * @param {Node} subject - the first part of the statement + * @param {Node} predicate - the second part of the statement + * @param {Node} object - the third part of the statement + * @param {Node} graph - the last part of the statement + */ add (subject, predicate, object, graph) { return this.statements.push(this.rdfFactory.quad(subject, predicate, object, graph)) } + /** Add a statment object - * @param {Statement} statement - an existing constructed statement to add - */ - addStatement (st) { - return this.statements.push(st) + * @param {Statement} statement - An existing constructed statement to add + */ + addStatement (statement) { + return this.statements.push(statement) } + + /** @deprecated use {this.rdfFactory.blankNode} instead */ bnode (id) { return this.rdfFactory.blankNode(id) } + /** + * Adds all the statements to this formula + * @param statements - A collection of statements + */ addAll (statements) { statements.forEach(quad => { this.add(quad.subject, quad.predicate, quad.object, quad.graph) @@ -70,14 +107,14 @@ export default class Formula extends Node { * any(me, knows, null, null) - a person I know accoring to anything in store . * any(null, knows, me, null) - a person who know me accoring to anything in store . * - * @param {Node} subject - A node to search for as subject, or if null, a wildcard - * @param {Node} predicate - A node to search for as predicate, or if null, a wildcard - * @param {Node} object - A node to search for as object, or if null, a wildcard - * @param {Node} graph - A node to search for as graph, or if null, a wildcard + * @param {Node} s - A node to search for as subject, or if null, a wildcard + * @param {Node} p - A node to search for as predicate, or if null, a wildcard + * @param {Node} o - A node to search for as object, or if null, a wildcard + * @param {Node} g - A node to search for as graph, or if null, a wildcard * @returns {Node} - A node which match the wildcard position, or null */ - any (s, p, o, g) { - var st = this.anyStatementMatching(s, p, o, g) + any (s, p, o, g): any | undefined { + let st = this.anyStatementMatching(s, p, o, g) if (st == null) { return void 0 } else if (s == null) { @@ -87,21 +124,31 @@ export default class Formula extends Node { } else if (o == null) { return st.object } + return void 0 } + /** + * Gets the value of a node that matches the specified pattern + */ anyValue (s, p, o, g) { - var y = this.any(s, p, o, g) + let y = this.any(s, p, o, g) return y ? y.value : void 0 } + /** + * Gets the first JavaScript object equivalent to a node based on the specified pattern + */ anyJS (s, p, o, g) { - var y = this.any(s, p, o, g) + let y = this.any(s, p, o, g) return y ? Node.toJS(y) : void 0 } + /** + * Gets the first statement that matches the specified pattern + */ anyStatementMatching (subj, pred, obj, why) { - var x = this.statementsMatching(subj, pred, obj, why, true) + let x = this.statementsMatching(subj, pred, obj, why, true) if (!x || x.length === 0) { return undefined } @@ -113,51 +160,55 @@ export default class Formula extends Node { * * Falls back to the rdflib hashString implementation if the given factory doesn't support id. */ - id (term) { + id (term): string | number { return this.rdfFactory.id(term) } - /** Search the Store - * + /** + * Search the Store * This is really a teaching method as to do this properly you would use IndexedFormula * - * @param {Node} subject - A node to search for as subject, or if null, a wildcard - * @param {Node} predicate - A node to search for as predicate, or if null, a wildcard - * @param {Node} object - A node to search for as object, or if null, a wildcard + * @param {Node} subj - A node to search for as subject, or if null, a wildcard + * @param {Node} pred - A node to search for as predicate, or if null, a wildcard + * @param {Node} obj - A node to search for as object, or if null, a wildcard * @param {Node} graph - A node to search for as graph, or if null, a wildcard * @param {Boolean} justOne - flag - stop when found one rather than get all of them? * @returns {Array} - An array of nodes which match the wildcard position */ - statementsMatching (subj, pred, obj, why, justOne) { - let found = this.statements.filter(st => + statementsMatching (subj?, pred?, obj?, graph?, justOne = false): Statement[] { + return this.statements.filter(st => (!subj || subj.equals(st.subject)) && (!pred || pred.equals(st.predicate)) && (!obj || subj.equals(st.object)) && - (!why || why.equals(st.subject)) + (!graph || graph.equals(st.subject)) ) - return found } + /** * Finds the types in the list which have no *stored* subtypes * These are a set of classes which provide by themselves complete * information -- the other classes are redundant for those who * know the class DAG. + * @param types A map of the types */ bottomTypeURIs (types) { - var bots - var bottom - var elt - var i - var k - var len - var ref - var subs - var v + let bots + let bottom + let elt + let i + let len + let ref + let subs + let v bots = [] - for (k in types) { + for (let k in types) { if (!types.hasOwnProperty(k)) continue v = types[k] - subs = this.each(void 0, this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf'), this.sym(k)) + subs = this.each( + void 0, + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + this.rdfFactory.namedNode(k) + ) bottom = true i = 0 for (len = subs.length; i < len; i++) { @@ -174,64 +225,58 @@ export default class Formula extends Node { } return bots } - collection () { + + /** Creates a new collection */ + collection (): Collection { return new Collection() } - /** Follow links from one node, using one wildcard + /** Follow links from one node, using one wildcard. * * For example, each(me, knows, null, profile) - people I know accoring to my profile . * each(me, knows, null, null) - people I know accoring to anything in store . * each(null, knows, me, null) - people who know me accoring to anything in store . * - * @param {Node} subject - A node to search for as subject, or if null, a wildcard - * @param {Node} predicate - A node to search for as predicate, or if null, a wildcard - * @param {Node} object - A node to search for as object, or if null, a wildcard - * @param {Node} graph - A node to search for as graph, or if null, a wildcard + * @param {Node} s - A node to search for as subject, or if null, a wildcard + * @param {Node} p - A node to search for as predicate, or if null, a wildcard + * @param {Node} o - A node to search for as object, or if null, a wildcard + * @param {Node} g - A node to search for as graph, or if null, a wildcard * @returns {Array} - An array of nodes which match the wildcard position */ - each (s, p, o, g) { - var elt, i, l, m, q - var len, len1, len2, len3 - var results = [] - var sts = this.statementsMatching(s, p, o, g, false) + each (s?: any | null, p?: any | null, o?: any | null, g?: any | null) { + const results: any[] = [] + let sts = this.statementsMatching(s, p, o, g, false) if (s == null) { - for (i = 0, len = sts.length; i < len; i++) { - elt = sts[i] - results.push(elt.subject) + for (let i = 0, len = sts.length; i < len; i++) { + results.push(sts[i].subject) } } else if (p == null) { - for (l = 0, len1 = sts.length; l < len1; l++) { - elt = sts[l] - results.push(elt.predicate) + for (let l = 0, len1 = sts.length; l < len1; l++) { + results.push(sts[l].predicate) } } else if (o == null) { - for (m = 0, len2 = sts.length; m < len2; m++) { - elt = sts[m] - results.push(elt.object) + for (let m = 0, len2 = sts.length; m < len2; m++) { + results.push(sts[m].object) } } else if (g == null) { - for (q = 0, len3 = sts.length; q < len3; q++) { - elt = sts[q] - results.push(elt.why) + for (let q = 0, len3 = sts.length; q < len3; q++) { + results.push(sts[q].graph) } } + return results } - equals (other) { + + /** + * Test whether this formula is equals to {other} + * @param other - The other formula + */ + equals(other: Formula): boolean { if (!other) { return false } return this.hashString() === other.hashString() } - /* - For thisClass or any subclass, anything which has it is its type - or is the object of something which has the type as its range, or subject - of something which has the type as its domain - We don't bother doing subproperty (yet?)as it doesn't seeem to be used much. - Get all the Classes of which we can RDFS-infer the subject is a member - @returns a hash of URIs - */ /** * For thisClass or any subclass, anything which has it is its type @@ -243,44 +288,37 @@ export default class Formula extends Node { * @return a hash of URIs */ findMembersNT (thisClass) { - var i - var l - var len - var len1 - var len2 - var len3 - var len4 - var m - var members - var pred - var q - var ref - var ref1 - var ref2 - var ref3 - var ref4 - var ref5 - var seeds - var st - var t - var u + let len2 + let len4 + let m + let members + let pred + let ref + let ref1 + let ref2 + let ref3 + let ref4 + let ref5 + let seeds + let st + let u seeds = {} seeds[thisClass.toNT()] = true members = {} - ref = this.transitiveClosure(seeds, this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf'), true) - for (t in ref) { + ref = this.transitiveClosure(seeds, this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), true) + for (let t in ref) { if (!ref.hasOwnProperty(t)) continue ref1 = this.statementsMatching(void 0, - this.sym('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + this.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), this.fromNT(t)) - for (i = 0, len = ref1.length; i < len; i++) { + for (let i = 0, len = ref1.length; i < len; i++) { st = ref1[i] members[st.subject.toNT()] = st } ref2 = this.each(void 0, - this.sym('http://www.w3.org/2000/01/rdf-schema#domain'), + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#domain'), this.fromNT(t)) - for (l = 0, len1 = ref2.length; l < len1; l++) { + for (let l = 0, len1 = ref2.length; l < len1; l++) { pred = ref2[l] ref3 = this.statementsMatching(void 0, pred) for (m = 0, len2 = ref3.length; m < len2; m++) { @@ -289,9 +327,9 @@ export default class Formula extends Node { } } ref4 = this.each(void 0, - this.sym('http://www.w3.org/2000/01/rdf-schema#range'), + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#range'), this.fromNT(t)) - for (q = 0, len3 = ref4.length; q < len3; q++) { + for (let q = 0, len3 = ref4.length; q < len3; q++) { pred = ref4[q] ref5 = this.statementsMatching(void 0, pred) for (u = 0, len4 = ref5.length; u < len4; u++) { @@ -300,11 +338,23 @@ export default class Formula extends Node { } } } + return members } + + /** + * For thisClass or any subclass, anything which has it is its type + * or is the object of something which has the type as its range, or subject + * of something which has the type as its domain + * We don't bother doing subproperty (yet?)as it doesn't seeem to be used + * much. + * Get all the Classes of which we can RDFS-infer the subject is a member + * @param subject - A named node + */ findMemberURIs (subject) { return this.NTtoURI(this.findMembersNT(subject)) } + /** * Get all the Classes of which we can RDFS-infer the subject is a superclass * Returns a hash table where key is NT of type and value is statement why we @@ -313,11 +363,15 @@ export default class Formula extends Node { * We use NT representations in this version because they handle blank nodes. */ findSubClassesNT (subject) { - var types = {} + let types = {} types[subject.toNT()] = true - return this.transitiveClosure(types, - this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf'), true) + return this.transitiveClosure( + types, + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + true + ) } + /** * Get all the Classes of which we can RDFS-infer the subject is a subclass * @param {NamedNode} subject - The thing whose classes are to be found @@ -327,11 +381,12 @@ export default class Formula extends Node { * We use NT representations in this version because they handle blank nodes. */ findSuperClassesNT (subject) { - var types = {} + let types = {} types[subject.toNT()] = true return this.transitiveClosure(types, - this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf'), false) + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), false) } + /** * Get all the Classes of which we can RDFS-infer the subject is a member * todo: This will loop is there is a class subclass loop (Sublass loops are @@ -342,77 +397,81 @@ export default class Formula extends Node { * We use NT representations in this version because they handle blank nodes. */ findTypesNT (subject) { - var domain - var i - var l - var len - var len1 - var len2 - var len3 - var m - var q - var range - var rdftype - var ref - var ref1 - var ref2 - var ref3 - var st - var types + let domain + let range + let rdftype + let ref + let ref1 + let ref2 + let ref3 + let st + let types rdftype = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' types = [] ref = this.statementsMatching(subject, void 0, void 0) - for (i = 0, len = ref.length; i < len; i++) { + for (let i = 0, len = ref.length; i < len; i++) { st = ref[i] if (st.predicate.uri === rdftype) { types[st.object.toNT()] = st } else { - ref1 = this.each(st.predicate, this.sym('http://www.w3.org/2000/01/rdf-schema#domain')) - for (l = 0, len1 = ref1.length; l < len1; l++) { + ref1 = this.each( + st.predicate, + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#domain') + ) + for (let l = 0, len1 = ref1.length; l < len1; l++) { range = ref1[l] types[range.toNT()] = st } } } ref2 = this.statementsMatching(void 0, void 0, subject) - for (m = 0, len2 = ref2.length; m < len2; m++) { + for (let m = 0, len2 = ref2.length; m < len2; m++) { st = ref2[m] - ref3 = this.each(st.predicate, this.sym('http://www.w3.org/2000/01/rdf-schema#range')) - for (q = 0, len3 = ref3.length; q < len3; q++) { + ref3 = this.each( + st.predicate, + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#range') + ) + for (let q = 0, len3 = ref3.length; q < len3; q++) { domain = ref3[q] types[domain.toNT()] = st } } - return this.transitiveClosure(types, this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf'), false) + return this.transitiveClosure( + types, + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf'), + false + ) } + findTypeURIs (subject) { return this.NTtoURI(this.findTypesNT(subject)) } + /** Trace statements which connect directly, or through bnodes * * @param {NamedNode} subject - The node to start looking for statments * @param {NamedNode} doc - The document to be searched, or null to search all documents * @returns an array of statements, duplicate statements are suppresssed. */ - connectedStatements (subject, doc, excludePredicateURIs) { + connectedStatements (subject, doc, excludePredicateURIs): Statement[] { excludePredicateURIs = excludePredicateURIs || [] - var todo = [subject] - var done = [] - var doneArcs = [] - var result = [] - var self = this - var follow = function (x) { - var queue = function (x) { + let todo = [subject] + let done: { [k: string]: boolean } = {} + let doneArcs: { [k: string]: boolean } = {} + let result: Statement[] = [] + let self = this + let follow = function (x) { + let queue = function (x) { if (x.termType === 'BlankNode' && !done[x.value]) { done[x.value] = true todo.push(x) } } - var sts = self.statementsMatching(null, null, x, doc) + let sts = self.statementsMatching(null, null, x, doc) .concat(self.statementsMatching(x, null, null, doc)) sts = sts.filter(function (st) { if (excludePredicateURIs[st.predicate.uri]) return false - var hash = st.toNT() + let hash = st.toNT() if (doneArcs[hash]) return false doneArcs[hash] = true return true @@ -434,14 +493,15 @@ export default class Formula extends Node { formula () { return new Formula() } + /** * Transforms an NTriples string format into a Node. - * The bnode bit should not be used on program-external values; designed - * for internal work such as storing a bnode id in an HTML attribute. - * This will only parse the strings generated by the vaious toNT() methods. + * The blank node bit should not be used on program-external values; designed + * for internal work such as storing a blank node id in an HTML attribute. + * This will only parse the strings generated by the various toNT() methods. */ fromNT (str) { - var dt, k, lang + let dt, k, lang switch (str[0]) { case '<': return this.sym(str.slice(1, -1)) @@ -462,7 +522,7 @@ export default class Formula extends Node { str = str.replace(/\\"/g, '"') str = str.replace(/\\n/g, '\n') str = str.replace(/\\\\/g, '\\') - return this.literal(str, lang || dt) + return this.rdfFactory.literal(str, lang || dt) case '_': return this.rdfFactory.blankNode(str.slice(2)) case '?': @@ -471,8 +531,9 @@ export default class Formula extends Node { throw new Error("Can't convert from NT: " + str) } - holds (s, p, o, g) { - var i + /** Returns true if this formula holds the specified statement(s) */ + holds (s: any | any[], p?, o?, g?): boolean { + let i if (arguments.length === 1) { if (!s) { return true @@ -491,11 +552,20 @@ export default class Formula extends Node { } } - var st = this.anyStatementMatching(s, p, o, g) + let st = this.anyStatementMatching(s, p, o, g) return st != null } - holdsStatement (st) { - return this.holds(st.subject, st.predicate, st.object, st.why) + + /** + * Returns true if this formula holds the specified {statement} + */ + holdsStatement (statement) { + return this.holds( + statement.subject, + statement.predicate, + statement.object, + statement.graph, + ) } /** @@ -518,15 +588,16 @@ export default class Formula extends Node { return node } } + /** - * transform a collection of NTriple URIs into their URI strings - * @param t some iterable colletion of NTriple URI strings - * @return a collection of the URIs as strings + * Transform a collection of NTriple URIs into their URI strings + * @param t - Some iterable collection of NTriple URI strings + * @return A collection of the URIs as strings * todo: explain why it is important to go through NT */ NTtoURI (t) { - var k, v - var uris = {} + let k, v + let uris = {} for (k in t) { if (!t.hasOwnProperty(k)) continue v = t[k] @@ -536,12 +607,19 @@ export default class Formula extends Node { } return uris } + + /** + * Serializes this formula + * @param base - The base string + * @param contentType - The content type of the syntax to use + * @param provenance - The provenance URI + */ serialize (base, contentType, provenance) { - var documentString - var sts - var sz + let documentString + let sts + let sz sz = Serializer(this) - sz.suggestNamespaces(this.namespaces) + sz.suggestNamespaces(this.ns) sz.setBase(base) if (provenance) { sts = this.statementsMatching(void 0, void 0, void 0, provenance) @@ -563,45 +641,59 @@ export default class Formula extends Node { } return documentString } + + /** + * Creates a new formula with the substituting bindings applied + * @param bindings - The bindings to substitute + */ substitute (bindings) { - var statementsCopy = this.statements.map(function (ea) { + let statementsCopy = this.statements.map(function (ea) { return ea.substitute(bindings) }) console.log('Formula subs statmnts:' + statementsCopy) - var y = new Formula() - y.add(statementsCopy) + const y = new Formula() + y.addAll(statementsCopy) console.log('indexed-form subs formula:' + y) return y } - sym (uri, name) { + + /** + * @deprecated use {rdfFactory.namedNode} instead + */ + sym (uri, name?) { if (name) { throw new Error('This feature (kb.sym with 2 args) is removed. Do not assume prefix mappings.') } return this.rdfFactory.namedNode(uri) } + + /** + * Gets the node matching the specified pattern. Throws when no match could be made. + */ the (s, p, o, g) { - var x = this.any(s, p, o, g) + let x = this.any(s, p, o, g) if (x == null) { log.error('No value found for the() {' + s + ' ' + p + ' ' + o + '}.') } return x } + /** * RDFS Inference * These are hand-written implementations of a backward-chaining reasoner * over the RDFS axioms. - * @param seeds {Object} a hash of NTs of classes to start with - * @param predicate The property to trace though - * @param inverse trace inverse direction + * @param seeds {Object} - A hash of NTs of classes to start with + * @param predicate - The property to trace though + * @param inverse - Trace inverse direction */ transitiveClosure (seeds, predicate, inverse) { - var elt, i, len, s, sups, t - var agenda = {} + let elt, i, len, s, sups, t + let agenda = {} Object.assign(agenda, seeds) // make a copy - var done = {} // classes we have looked up + let done = {} // classes we have looked up while (true) { t = (function () { - for (var p in agenda) { + for (let p in agenda) { if (!agenda.hasOwnProperty(p)) continue return p } @@ -625,26 +717,30 @@ export default class Formula extends Node { delete agenda[t] } } + /** * Finds the types in the list which have no *stored* supertypes * We exclude the universal class, owl:Things and rdf:Resource, as it is * information-free. */ topTypeURIs (types) { - var i - var j - var k - var len - var n - var ref - var tops - var v + let i + let j + let k + let len + let n + let ref + let tops + let v tops = [] for (k in types) { if (!types.hasOwnProperty(k)) continue v = types[k] n = 0 - ref = this.each(this.sym(k), this.sym('http://www.w3.org/2000/01/rdf-schema#subClassOf')) + ref = this.each( + this.rdfFactory.namedNode(k), + this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#subClassOf') + ) for (i = 0, len = ref.length; i < len; i++) { j = ref[i] if (j.uri !== 'http://www.w3.org/2000/01/rdf-schema#Resource') { @@ -664,17 +760,30 @@ export default class Formula extends Node { } return tops } - toString () { + + /** + * Serializes this formula to a string + */ + toString(): string { return '{' + this.statements.join('\n') + '}' } - whether (s, p, o, g) { + + /** + * Gets a new variable + * @param name - The variable's name + */ + public variable(name: string): Variable { + return new Variable(name) + } + + /** + * Gets the number of statements in this formula that matches the specified pattern + * @param s - The subject + * @param p - The predicate + * @param o - The object + * @param g - The graph that contains the statement + */ + whether (s, p, o, g): number { return this.statementsMatching(s, p, o, g, false).length } } -Formula.termType = 'Graph' - -Formula.prototype.classOrder = ClassOrder['Graph'] -Formula.prototype.isVar = 0 - -Formula.prototype.ns = Namespace -Formula.prototype.variable = name => new Variable(name) diff --git a/src/node-internal.ts b/src/node-internal.ts index fa519b7d6..208409683 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -7,7 +7,7 @@ * @class Node */ export default class Node { - static fromValue: (value: any) => unknown; + static fromValue: (value: any) => T; static toJS: (term: any) => Date | Number | string | boolean | object | Array; /** diff --git a/src/statement.js b/src/statement.js deleted file mode 100644 index 006eb7f12..000000000 --- a/src/statement.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict' -import Node from './node' - -export default class Statement { - /* Construct a new statment - ** - ** @param {Term} subject - The subject of the triple. What the efact is about - ** @ param {Term} predciate - The relationship which is assrted between the subject and object - ** @param {Term} object - The thing or data value which is asserted to be related to the subject - ** @param {NamedNode} why - The document where thr triple is or was or will be stored on the web. - ** - ** The why param is a named node of the document in which the triple when - ** it is stored on the web. - ** It is called “why” because when you have read data from varou slaces the - ** “why” tells you why you have the triple. (At the moment, it is just the - ** document, in future it could be an inference step). When you do - ** UpdateManager.update() then the why’s of all the statmemts must be the same, - ** and give the document you are patching. In future, we may have a more - ** powerful update() which can update more than one docment. - */ - constructor (subject, predicate, object, graph) { - this.subject = Node.fromValue(subject) - this.predicate = Node.fromValue(predicate) - this.object = Node.fromValue(object) - this.why = graph // property currently used by rdflib - } - get graph () { - return this.why - } - set graph (g) { - this.why = g - } - equals (other) { - return other.subject.equals(this.subject) && other.predicate.equals(this.predicate) && - other.object.equals(this.object) && other.graph.equals(this.graph) - } - substitute (bindings) { - const y = new Statement( - this.subject.substitute(bindings), - this.predicate.substitute(bindings), - this.object.substitute(bindings), - this.why.substitute(bindings)) // 2016 - console.log('@@@ statement substitute:' + y) - return y - } - toCanonical () { - let terms = [ - this.subject.toCanonical(), - this.predicate.toCanonical(), - this.object.toCanonical() - ] - if (this.graph && this.graph.termType !== 'DefaultGraph') { - terms.push(this.graph.toCanonical()) - } - return terms.join(' ') + ' .' - } - toNT () { - return [this.subject.toNT(), this.predicate.toNT(), - this.object.toNT()].join(' ') + ' .' - } - toString () { - return this.toNT() - } -} diff --git a/src/statement.ts b/src/statement.ts new file mode 100644 index 000000000..01a04420d --- /dev/null +++ b/src/statement.ts @@ -0,0 +1,120 @@ +import BlankNode from './blank-node' +import Collection from './collection' +import DefaultGraph from './default-graph' +import Empty from './empty' +import NamedNode from './named-node' +import Node from './node-internal' +import IndexedFormula from './store' +import Variable from './variable' + +/** A Statement represents an RDF Triple or Quad. */ +export default class Statement { + /** The subject of the triple. What the Statement is about. */ + subject: NamedNode | BlankNode | Variable + + /** The relationship which is asserted between the subject and object */ + predicate: NamedNode | Variable + + /** The thing or data value which is asserted to be related to the subject */ + object: NamedNode | BlankNode | Collection | Empty | Variable + + /** + * The graph param is a named node of the document in which the triple when + * it is stored on the web. + */ + graph: NamedNode | DefaultGraph | IndexedFormula + + + /** + * Construct a new statement + * + * @param subject - The subject of the triple. What the fact is about + * @param predicate - The relationship which is asserted between the subject and object + * @param object - The thing or data value which is asserted to be related to the subject + * @param {NamedNode} graph - The document where the triple is or was or will be stored on the web. + * + * The graph param is a named node of the document in which the triple when it is stored + * on the web. It exists because when you have read data from various places in the web, + * the “graph” tells you _why_ you have the triple. (At the moment, it is just the + * document, in future it could be an inference step) + * + * When you do UpdateManager.update() then the graph’s of all the statements must be the same, + * and give the document you are patching. In future, we may have a more + * powerful update() which can update more than one document. + */ + constructor (subject, predicate, object, graph) { + this.subject = Node.fromValue(subject) + this.predicate = Node.fromValue(predicate) + this.object = Node.fromValue(object) + this.graph = graph // property currently used by rdflib + } + + /** @deprecated use {graph} instead */ + get why () { + return this.graph + } + + set why (g) { + this.graph = g + } + + /** + * Checks whether two statements are the same + * @param other - The other statement + */ + equals (other): boolean { + return other.subject.equals(this.subject) && other.predicate.equals(this.predicate) && + other.object.equals(this.object) && other.graph.equals(this.graph) + } + + /** + * Creates a statement with the bindings substituted + * @param bindings - The bindings + */ + substitute (bindings): Statement { + const y = new Statement( + this.subject.substitute(bindings), + this.predicate.substitute(bindings), + this.object.substitute(bindings), + this.graph.substitute(bindings)) // 2016 + console.log('@@@ statement substitute:' + y) + return y + } + + /** Creates a canonical string representation of this statement. */ + toCanonical (): string { + let terms = [ + this.subject.toCanonical(), + this.predicate.toCanonical(), + this.object.toCanonical() + ] + if (this.graph && this.graph.termType !== 'DefaultGraph') { + terms.push(this.graph.toCanonical()) + } + return terms.join(' ') + ' .' + } + + /** Creates a n-triples string representation of this statement */ + toNT (): string { + return [ + this.subject.toNT(), + this.predicate.toNT(), + this.object.toNT(), + ].join(' ') + ' .' + } + + /** Creates a n-quads string representation of this statement */ + toNQ (): string { + return [ + this.subject.toNT(), + this.predicate.toNT(), + this.object.toNT(), + this.graph.toNT(), + ].join(' ') + ' .' + } + + /** Creates a string representation of this statement */ + toString (): string { + return this.toNT() + } +} From 5e7c08c0c0dcc36420f27ec0c8bc9d9ed41fba77 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 22 Oct 2019 14:49:11 +0200 Subject: [PATCH 05/29] Migrate to Typescript #355 (includes #363) Some utils to TS #355 Add datafactory type to formula #355 Taskforce compatibility #355 Add fork-ts-checker-webpack-plugin #355 URI to typescript #355 moved typeguards to utils Datafactory & serializer types, enums #355 WIP Fetcher & UpdateManager #355 Fetcher WIP #355 WIP Fetcher #355 Continue working on typescript migration #355 Data factory types fix #355 Various minor type improvements #355 Add documentation to typeguards #355 Various fetcher type improvements, use enums #355 Use doc.value instead of doc.uri in update-manager Update typescript #355 Don't use const enums util supported #355 Fixed tests, improved typeguards, fetcher #355 Rename uriCreator to nodeValue, various ts #355 Fether typings #355 Fetcher & Formula typing improvements #355 Update manager typing #355 Data factory, store, fether types #355 Various type changes, almost no type errors left #355 Replace .sym with factory.namednode Cleanup Move typing/type-testing utils to separate file --- .npmignore | 6 - package-lock.json | 412 ++++--- package.json | 20 +- reference/fetcher-classes.js | 5 +- src/blank-node.ts | 34 +- src/class-order.ts | 6 +- src/collection.ts | 40 +- src/data-factory-internal.js | 91 -- src/data-factory-internal.ts | 161 +++ src/data-factory-type.ts | 104 ++ src/data-factory.js | 72 -- src/data-factory.ts | 138 +++ src/default-graph.ts | 6 +- src/empty.ts | 7 +- src/{fetcher.js => fetcher.ts} | 1018 ++++++++++++------ src/formula.ts | 329 ++++-- src/jsonldparser.js | 2 +- src/literal.ts | 70 +- src/n3parser.js | 2 +- src/named-node.ts | 20 +- src/namespace.ts | 7 +- src/node-internal.ts | 33 +- src/node.ts | 26 +- src/{parse.js => parse.ts} | 72 +- src/rdfaparser.js | 2 +- src/{serialize.js => serialize.ts} | 64 +- src/statement.ts | 56 +- src/{store.js => store.ts} | 571 +++++++--- src/types.ts | 240 ++++- src/{update-manager.js => update-manager.ts} | 363 ++++--- src/{uri.js => uri.ts} | 61 +- src/util.js | 106 +- src/utils.ts | 114 ++ src/utils/terms.ts | 117 +- src/variable.ts | 21 +- src/wip.md | 111 ++ tests/unit/fetcher-test.js | 1 + tests/unit/parse-test.js | 1 - tests/unit/typings-test.ts | 18 + tests/unit/util-test.js | 8 +- tsconfig.json | 8 +- typedoc.js | 18 + types-temp.ts | 293 +++++ webpack.config.js | 4 + 44 files changed, 3440 insertions(+), 1418 deletions(-) delete mode 100644 .npmignore delete mode 100644 src/data-factory-internal.js create mode 100644 src/data-factory-internal.ts create mode 100644 src/data-factory-type.ts delete mode 100644 src/data-factory.js create mode 100644 src/data-factory.ts rename src/{fetcher.js => fetcher.ts} (65%) rename src/{parse.js => parse.ts} (50%) rename src/{serialize.js => serialize.ts} (54%) rename src/{store.js => store.ts} (58%) rename src/{update-manager.js => update-manager.ts} (76%) rename src/{uri.js => uri.ts} (75%) create mode 100644 src/utils.ts create mode 100644 src/wip.md create mode 100644 tests/unit/typings-test.ts create mode 100644 typedoc.js create mode 100644 types-temp.ts diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 98d40b1c3..000000000 --- a/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -doc -.vscode/ -npm-debug.log -# The following (and `.npmignore` in general) can be removed again once this bug is fixed: -# https://github.com/linkeddata/rdflib.js/issues/369 -lib/index.d.ts diff --git a/package-lock.json b/package-lock.json index 64cdc1a5f..ebe5a3c07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1249,6 +1249,12 @@ "@types/node": "*" } }, + "@types/jsonld": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/jsonld/-/jsonld-1.5.0.tgz", + "integrity": "sha512-EG2N8JLQ1xDfO6Z/1QRdiUcYX3428CqVRqmY7LyK5or5J1RQ16dpKH6qQ4umVD0vBHU47xHlMeyMbQ6o+6tiYg==", + "dev": true + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -1735,6 +1741,65 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -1791,6 +1856,15 @@ } } }, + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "dev": true, + "requires": { + "underscore": ">=1.8.3" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2035,7 +2109,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2080,7 +2154,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -2200,15 +2274,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "catharsis": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", - "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -3001,12 +3066,6 @@ "tapable": "^1.0.0" } }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -3537,6 +3596,22 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, + "fork-ts-checker-webpack-plugin": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.6.0.tgz", + "integrity": "sha512-vqOY5gakcoon2s12V7MMe01OPwfgqulUWFzm+geQaPPOBKjW1I7aqqoBVlU0ECn97liMB0ECs16pRdIGe9qdRw==", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^2.0.4", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + } + }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -4398,6 +4473,26 @@ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, + "handlebars": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.2.tgz", + "integrity": "sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -4421,6 +4516,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4490,6 +4602,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "version": "9.15.10", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", + "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5004,6 +5122,12 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==", + "dev": true + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -5026,59 +5150,11 @@ "esprima": "^4.0.0" } }, - "js2xmlparser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.0.tgz", - "integrity": "sha512-WuNgdZOXVmBk5kUPMcTcVUpbGRzLfNkv7+7APq7WiDihpXVKrgxo6wwRpRl9OQeEBgKCVk9mR7RbzrnNWC8oBw==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.0" - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, - "jsdoc": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.3.tgz", - "integrity": "sha512-Yf1ZKA3r9nvtMWHO1kEuMZTlHOF8uoQ0vyo5eH7SQy5YeIiHM+B0DgKnn+X6y6KDYZcF7G2SPkKF+JORCXWE/A==", - "dev": true, - "requires": { - "@babel/parser": "^7.4.4", - "bluebird": "^3.5.4", - "catharsis": "^0.8.11", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.0", - "klaw": "^3.0.0", - "markdown-it": "^8.4.2", - "markdown-it-anchor": "^5.0.2", - "marked": "^0.7.0", - "mkdirp": "^0.5.1", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.0.1", - "taffydb": "2.6.2", - "underscore": "~1.9.1" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - } - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5185,15 +5261,6 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -5203,15 +5270,6 @@ "invert-kv": "^2.0.0" } }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" - } - }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -5305,6 +5363,12 @@ "yallist": "^3.0.2" } }, + "lunr": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.7.tgz", + "integrity": "sha512-HjFSiy0Y0qZoW5OA1I6qBi7OnsDdqQnaUr03jhorh30maQoaP+4lQCKklYE3Nq3WJMSUfuBl6N+bKY5wxCb9hw==", + "dev": true + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -5345,25 +5409,6 @@ "object-visit": "^1.0.0" } }, - "markdown-it": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", - "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~1.1.1", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } - }, - "markdown-it-anchor": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.2.4.tgz", - "integrity": "sha512-n8zCGjxA3T+Mx1pG8HEgbJbkB8JFUuRkeTZQuIM8iPY6oQ8sWOPRZJDFC9a/pNg2QkHEjjGkhBEl/RSyzaDZ3A==", - "dev": true - }, "marked": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", @@ -5381,12 +5426,6 @@ "safe-buffer": "^5.1.2" } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5458,6 +5497,12 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -5535,7 +5580,7 @@ }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, @@ -6252,6 +6297,16 @@ "is-wsl": "^1.1.0" } }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -6625,6 +6680,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -6840,6 +6901,15 @@ } } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -6985,15 +7055,6 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -7299,6 +7360,17 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -7912,12 +7984,6 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7927,12 +7993,6 @@ "has-flag": "^3.0.0" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -8181,17 +8241,70 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedoc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.15.0.tgz", + "integrity": "sha512-NOtfq5Tis4EFt+J2ozhVq9RCeUnfEYMFKoU6nCXCXUULJz1UQynOM+yH3TkfZCPLzigbqB0tQYGVlktUWweKlw==", + "dev": true, + "requires": { + "@types/minimatch": "3.0.3", + "fs-extra": "^8.1.0", + "handlebars": "^4.1.2", + "highlight.js": "^9.15.8", + "lodash": "^4.17.15", + "marked": "^0.7.0", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "shelljs": "^0.8.3", + "typedoc-default-themes": "^0.6.0", + "typescript": "3.5.x" + }, + "dependencies": { + "typescript": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true + } + } + }, + "typedoc-default-themes": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.6.0.tgz", + "integrity": "sha512-MdTROOojxod78CEv22rIA69o7crMPLnVZPefuDLt/WepXqJwgiSu8Xxq+H36x0Jj3YGc7lOglI2vPJ2GhoOybw==", + "dev": true, + "requires": { + "backbone": "^1.4.0", + "jquery": "^3.4.1", + "lunr": "^2.3.6", + "underscore": "^1.9.1" + } + }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "uglify-js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.1.tgz", + "integrity": "sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==", + "dev": true, + "optional": true, + "requires": { + "commander": "2.20.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } }, "underscore": { "version": "1.9.1", @@ -8837,6 +8950,12 @@ "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", "dev": true }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -8846,9 +8965,18 @@ "errno": "~0.1.7" } }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "dev": true, + "requires": { + "microevent.ts": "~0.1.1" + } + }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -8917,12 +9045,6 @@ "async-limiter": "~1.0.0" } }, - "xmlcreate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.1.tgz", - "integrity": "sha512-MjGsXhKG8YjTKrDCXseFo3ClbMGvUD4en29H2Cev1dv4P/chlpw6KdYmlCWDkhosBVKRDjM836+3e3pm1cBNJA==", - "dev": true - }, "xmldom": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", diff --git a/package.json b/package.json index 9e0bc61bd..afcdfe146 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,18 @@ { "name": "Daniel Friedman", "url": "https://github.com/dan-f/" + }, + { + "name": "Cénotélie", + "url": "https://github.com/cenotelie/" + }, + { + "name": "Joep Meindertsma", + "url": "https://github.com/joepio/" + }, + { + "name": "Thom van Kalkeren", + "url": "https://github.com/fletcher91/" } ], "license": "MIT", @@ -50,13 +62,14 @@ "@babel/register": "^7.5.5", "@types/chai": "^4.2.3", "@types/express": "^4.17.1", + "@types/jsonld": "^1.5.0", "@types/mocha": "^5.2.7", "babel-loader": "^8.0.6", "chai": "^4.2.0", "diff": "^4.0.1", "dirty-chai": "^2.0.1", + "fork-ts-checker-webpack-plugin": "^1.6.0", "fs-grep": "^0.0.5", - "jsdoc": "^3.6.3", "mocha": "^6.2.0", "nock": "^10.0.6", "node-fetch": "^2.6.0", @@ -65,7 +78,8 @@ "sinon": "^7.4.1", "sinon-chai": "^3.3.0", "source-map-loader": "^0.2.4", - "typescript": "^3.6.3", + "typedoc": "^0.15.3", + "typescript": "^3.7.2", "webpack": "^4.39.2", "webpack-cli": "^3.3.6", "webpack-dev-server": "^3.8.0", @@ -75,7 +89,7 @@ "build": "babel src --extensions \".ts,.js\" -d lib", "build:browser": "webpack --progress", "build:types": "tsc --emitDeclarationOnly -d --declarationDir lib --allowJs false", - "doc": "rm -r doc ; jsdoc -d doc README.md src/*.js", + "doc": "rm -r doc ; typedoc", "prepare": "npm run build && npm run build:browser", "start": "webpack-dev-server --https --port 4800", "test": "npm run test:unit && npm run test:serialize", diff --git a/reference/fetcher-classes.js b/reference/fetcher-classes.js index 9f11f47b6..7635a9847 100644 --- a/reference/fetcher-classes.js +++ b/reference/fetcher-classes.js @@ -1,8 +1,7 @@ -import { isNamedNode } from '../src/util' +import { isTFNamedNode } from '../src/utils' const log = require('./log') const N3Parser = require('./n3parser') -const NamedNode = require('./named-node') const Namespace = require('./namespace') const rdfParse = require('./parse') const parseRDFaDOM = require('./rdfaparser').parseRDFaDOM @@ -668,7 +667,7 @@ class Fetcher { userCallback = p2 } else if (typeof p2 === 'undefined') { // original calling signature // referingTerm = undefined - } else if (isNamedNode(p2)) { + } else if (isTFNamedNode(p2)) { // referingTerm = p2 options = {referingTerm: p2} } else { diff --git a/src/blank-node.ts b/src/blank-node.ts index 927793fa8..ac0d5e050 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -1,13 +1,19 @@ -'use strict' import ClassOrder from './class-order' import Node from './node-internal' -import { TermType } from './types' +import IndexedFormula from './store' +import { BlankNodeTermType, TermType, TFBlankNode } from './types' -export default class BlankNode extends Node { - static termType = TermType.BlankNode - static NTAnonymousNodePrefix = '_:' - /** The next unique identifier for blank nodes */ - static nextId = 0 +/** + * An RDF blank node is a Node without a URI + * @link https://rdf.js.org/data-model-spec/#blanknode-interface + */ +export default class BlankNode extends Node implements TFBlankNode { + /** + * The next unique identifier for blank nodes + */ + static nextId: number = 0; + static NTAnonymousNodePrefix: string = '_:' + static termType: BlankNodeTermType; private static getId (id: string | unknown): string { if (id) { @@ -30,18 +36,18 @@ export default class BlankNode extends Node { classOrder = ClassOrder.BlankNode /** Whether this is a blank node */ - isBlank = 1 + isBlank: number = 1 /** * This type of node is a variable. * * Note that the existence of this property already indicates that it is a variable. */ isVar = 1 - termType = BlankNode.termType + termType: BlankNodeTermType = TermType.BlankNode; /** * Initializes this node - * @param [id] - The identifier for the blank node + * @param [id] The identifier for the blank node */ constructor (id?: string | unknown) { super(BlankNode.getId(id)) @@ -59,7 +65,7 @@ export default class BlankNode extends Node { this.value = value } - compareTerm (other) { + compareTerm (other: BlankNode): number { if (this.classOrder < other.classOrder) { return -1 } @@ -75,7 +81,11 @@ export default class BlankNode extends Node { return 0 } - copy (formula) { // depends on the formula + /** + * Gets a copy of this blank node in the specified formula + * @param formula The formula + */ + copy (formula: IndexedFormula): BlankNode { // depends on the formula var bnodeNew = new BlankNode() formula.copyTo(this, bnodeNew) return bnodeNew diff --git a/src/class-order.ts b/src/class-order.ts index 6801115bb..d614b56e5 100644 --- a/src/class-order.ts +++ b/src/class-order.ts @@ -1,7 +1,9 @@ /** * Class orders */ -export default { +const ClassOrder: { + [id: string]: number; +} = { 'Literal': 1, 'Collection': 3, 'Graph': 4, @@ -9,3 +11,5 @@ export default { 'BlankNode': 6, 'Variable': 7 } + +export default ClassOrder diff --git a/src/collection.ts b/src/collection.ts index 3bebb69dd..16125ef21 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,31 +1,33 @@ -'use strict' import BlankNode from './blank-node' import ClassOrder from './class-order' -import Literal from "./literal"; +import Literal from './literal' import Node from './node-internal' -import { TermType } from './types'; -import Variable from "./variable"; +import { Bindings, FromValueReturns, TermType, ValueType } from './types' +import Variable from './variable' +import { isTFTerm } from './utils/terms' /** * Creates an RDF Node from a native javascript value. * RDF Nodes are returned unchanged, undefined returned as itself. + * Arrays return Collections. + * Strings, numbers and booleans return Literals. * @param value {Node|Date|String|Number|Boolean|Undefined} * @return {Node|Collection} */ -export function fromValue (value) { +export function fromValue = any, C extends Node = any>(value: ValueType): T { if (typeof value === 'undefined' || value === null) { - return value + return value as T } - const isNode = value && value.termType - if (isNode) { // a Node subclass or a Collection - return value + + if (isTFTerm(value)) { // a Node subclass or a Collection + return value as T } if (Array.isArray(value)) { - return new Collection(value) + return new Collection(value) as T } - return Literal.fromValue(value) + return Literal.fromValue(value) } /** @@ -46,7 +48,7 @@ export default class Collection) { super((BlankNode.nextId++).toString()) if (initial && initial.length > 0) { @@ -68,7 +70,7 @@ export default class Collection ea.substitute(bindings)) return new Collection(elementsCopy) } @@ -119,7 +119,7 @@ export default class Collection' - case "BlankNode": - return '_:' + term.value - case "Literal": - return Literal.toNT(term) - case "Variable": - return Variable.toString(term) - default: - return undefined - } -} - -function literal (value, languageOrDatatype) { - if (typeof value !== "string" && !languageOrDatatype) { - return Literal.fromValue(value) - } - - const strValue = typeof value === 'string' ? value : '' + value - if (typeof languageOrDatatype === 'string') { - if (languageOrDatatype.indexOf(':') === -1) { - return new Literal(strValue, languageOrDatatype) - } else { - return new Literal(strValue, null, namedNode(languageOrDatatype)) - } - } else { - return new Literal(strValue, null, languageOrDatatype) - } -} -function namedNode (value) { - return new NamedNode(value) -} -function quad (subject, predicate, object, graph) { - graph = graph || defaultGraph() - return new Statement(subject, predicate, object, graph) -} -function variable (name) { - return new Variable(name) -} - -/** Contains the factory methods as defined in the spec, plus id */ -export default { - blankNode, - defaultGraph, - literal, - namedNode, - quad, - variable, - id, - supports: { - COLLECTIONS: false, - DEFAULT_GRAPH_TYPE: true, - EQUALS_METHOD: true, - NODE_LOOKUP: false, - VARIABLE_TYPE: true, - } -} diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts new file mode 100644 index 000000000..d0bd7f463 --- /dev/null +++ b/src/data-factory-internal.ts @@ -0,0 +1,161 @@ +import BlankNode from './blank-node' +import Literal from './literal' +import NamedNode from './named-node' +import Statement from './statement' +import Variable from './variable' +import { + TFNamedNode, + SubjectType, + PredicateType, + ObjectType, + GraphType, + TermType, + TFDataFactory, + TFTerm, +} from './types' +import { Feature, IdentityFactory, Indexable } from './data-factory-type' +import Node from './node-internal' + +export const defaultGraphURI = 'chrome:theSession' +const defaultGraphNode = new NamedNode(defaultGraphURI) + +/** + * Creates a new blank node + * @param value The blank node's identifier + */ +function blankNode(value?: string): BlankNode { + return new BlankNode(value) +} + +/** + * Gets the default graph + */ +export function defaultGraph(): NamedNode { + return defaultGraphNode +} + +/** + * Generates a unique identifier for the object. + * + * Equivalent to {Term.hashString} + */ +function id (term: TFTerm): string | undefined { + if (!term) { + return term + } + if (Object.prototype.hasOwnProperty.call(term, "id") && typeof (term as NamedNode).id === "function") { + return (term as NamedNode).id() + } + if (Object.prototype.hasOwnProperty.call(term, "hashString")) { + return (term as Node).hashString() + } + + switch (term.termType) { + case TermType.NamedNode: + return '<' + term.value + '>' + case TermType.BlankNode: + return '_:' + term.value + case TermType.Literal: + return Literal.toNT(term as Literal) + case TermType.Variable: + return Variable.toString(term) + default: + return undefined + } +} + +/** + * Creates a new literal node + * @param value The lexical value + * @param languageOrDatatype Either the language or the datatype + */ +function literal( + value: string, + languageOrDatatype?: string | TFNamedNode +): Literal { + if (typeof value !== "string" && !languageOrDatatype) { + return Literal.fromValue(value) as Literal + } + + const strValue = typeof value === 'string' ? value : '' + value + if (typeof languageOrDatatype === 'string') { + if (languageOrDatatype.indexOf(':') === -1) { + return new Literal(strValue, languageOrDatatype) + } else { + return new Literal(strValue, null, namedNode(languageOrDatatype)) + } + } else { + return new Literal(strValue, null, languageOrDatatype) + } +} + +/** + * Creates a new named node + * @param value The new named node + */ +function namedNode(value: string): NamedNode { + return new NamedNode(value) +} + +/** +* Creates a new statement +* @param subject The subject +* @param predicate The predicate +* @param object The object +* @param graph The containing graph +*/ +function quad( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType +): Statement { + graph = graph || defaultGraph() + return new Statement(subject, predicate, object, graph) +} + +/** + * Creates a new variable + * @param name The name for the variable + */ +function variable(name?: string): Variable { + return new Variable(name) +} + +/** The internal RDFlib datafactory, which uses Collections */ +const CanonicalDataFactory: TFDataFactory< + NamedNode, + BlankNode, + Literal, + SubjectType, + PredicateType, + ObjectType, + GraphType, + // DefaultGraph is: + NamedNode | BlankNode, + Statement +> & IdentityFactory < + Statement, + NamedNode | BlankNode | Literal | Variable +> = { + blankNode, + defaultGraph, + literal, + namedNode, + quad, + variable, + // @ts-ignore id should return an Indexable value, but in RDFlib can return `undefined` + id, + supports: { + [Feature.collections]: false, + [Feature.defaultGraphType]: true, + [Feature.equalsMethod]: true, + [Feature.identity]: false, + [Feature.id]: true, + [Feature.reversibleId]: false, + [Feature.variableType]: true, + } +} + +/** Contains the factory methods as defined in the spec, plus id */ +export default CanonicalDataFactory diff --git a/src/data-factory-type.ts b/src/data-factory-type.ts new file mode 100644 index 000000000..7e0e5a185 --- /dev/null +++ b/src/data-factory-type.ts @@ -0,0 +1,104 @@ +import { TFNamedNode, TFBlankNode, TFLiteral, TFQuad, TFTerm, TFDataFactory, TFSubject, TFPredicate, TFObject, TFGraph, TFVariable } from "./types" + +/** + * Defines a strict subset of the DataFactory as defined in the RDF/JS: Data model specification + * Non RDF-native features have been removed (e.g. no Variable, no Literal as predicate, etc.). + * bnIndex is optional but useful. + */ +export interface DataFactory< + NamedNode extends TFNamedNode = TFNamedNode, + BlankNode extends TFBlankNode = TFBlankNode, + Literal extends TFLiteral = TFLiteral, + Quad = TFQuad, + FactoryTypes = NamedNode | TFBlankNode | Literal | Quad, + Subject = TFSubject, + Predicate = TFPredicate, + Object = TFObject, + Graph = TFGraph, + DefaultGraph = NamedNode | BlankNode, +> extends TFDataFactory< + NamedNode, + BlankNode, + Literal, + Subject, + Predicate, + Object, + Graph, + DefaultGraph, + Quad +> { + /** + * BlankNode index + * @private + */ + bnIndex?: number + + supports: SupportTable + + literal(value: string, languageOrDatatype?: string | TFNamedNode): Literal + + isQuad(obj: any): obj is Quad + + equals(a: Comparable, b: Comparable): boolean + + toNQ(term: FactoryTypes): string +} + +export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm + +export interface IdentityFactory< + Quad = TFQuad, + IDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | Quad | TFVariable | TFTerm, + IndexType = Indexable, +> { + /** + * Generates a unique session-idempotent identifier for the given object. + * + * @example NQ serialization (reversible from value) + * @example MD5 hash of termType + value (irreversible from value, map needed) + * + * @return {Indexable} A unique value which must also be a valid JS object key type. + */ + id(obj: IDFactoryTypes): IndexType +} + +/** + * Factory type which supports reverse id lookups. + * + * It should be able to resolve the value for any given id which it handed out. Passing an id not + * generated by the same instance might result in a value or an exception depending on the + * implementation. + */ +export interface ReversibleIdentityFactory< + IndexType = Indexable, + FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad +> extends IdentityFactory { + fromId(id: IndexType): FactoryTypes; +} + +export type Namespace = (term:string) => TFNamedNode +export type NamespaceCreator = (ns: string) => Namespace + +/** A set of features that may be supported by a Data Factory */ +export type SupportTable = Record + +export enum Feature { + /** Whether the factory supports termType:Collection terms */ + collections = "COLLECTIONS", + /** Whether the factory supports termType:DefaultGraph terms */ + defaultGraphType = "DEFAULT_GRAPH_TYPE", + /** Whether the factory supports equals on produced instances */ + equalsMethod = "EQUALS_METHOD", + /** Whether the factory can create a unique idempotent identifier for the given term. */ + id = "ID", + /** Whether the factory will return the same instance for subsequent calls (implies `===`). */ + identity = "IDENTITY", + /** Whether the factory supports mapping ids back to instances (should adhere to the identity setting) */ + reversibleId = "REVERSIBLE_ID", + /** Whether the factory supports termType:Variable terms */ + variableType = "VARIABLE_TYPE", +} + +export type Comparable = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null + +export type Indexable = number | string diff --git a/src/data-factory.js b/src/data-factory.js deleted file mode 100644 index 73293f38f..000000000 --- a/src/data-factory.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict' -import Collection from './collection' -import CanonicalDataFactory from './data-factory-internal' -import Fetcher from './fetcher' -import Literal from './literal' -import Statement from './statement' -import IndexedFormula from './store' - -/** - * Data factory which also supports Collections - * - * Necessary for preventing circular dependencies. - */ -const ExtendedTermFactory = { - ...CanonicalDataFactory, - collection, - id, - supports: { - COLLECTIONS: true, - DEFAULT_GRAPH_TYPE: true, - EQUALS_METHOD: true, - NODE_LOOKUP: false, - VARIABLE_TYPE: true, - } -} - -/** Full RDFLib.js Data Factory */ -const DataFactory = { - ...ExtendedTermFactory, - fetcher, - graph, - lit, - st, - triple, -} -export default DataFactory - -function id (term) { - if (!term) { - return term - } - if (Object.prototype.hasOwnProperty.call(term, "id") && typeof term.id === "function") { - return term.id() - } - if (Object.prototype.hasOwnProperty.call(term, "hashString")) { - return term.hashString() - } - - if (term.termType === "Collection") { - Collection.toNT(term) - } - - return CanonicalDataFactory.id(term) -} -function collection (elements) { - return new Collection(elements) -} -function fetcher (store, options) { - return new Fetcher(store, options) -} -function graph (features = undefined, opts = undefined) { - return new IndexedFormula(features, opts || { rdfFactory: ExtendedTermFactory }) -} -function lit (val, lang, dt) { - return new Literal('' + val, lang, dt) -} -function st (subject, predicate, object, graph) { - return new Statement(subject, predicate, object, graph) -} -function triple (subject, predicate, object) { - return CanonicalDataFactory.quad(subject, predicate, object) -} diff --git a/src/data-factory.ts b/src/data-factory.ts new file mode 100644 index 000000000..6f3bb22b8 --- /dev/null +++ b/src/data-factory.ts @@ -0,0 +1,138 @@ +import Collection from './collection' +import CanonicalDataFactory from './data-factory-internal' +import Fetcher from './fetcher' +import Literal from './literal' +import Statement from './statement' +import { ValueType, TFNamedNode, SubjectType, PredicateType, ObjectType, GraphType, TFBlankNode, TFLiteral } from './types' +import IndexedFormula from './store' +import { DataFactory, Indexable, SupportTable } from './data-factory-type' +import NamedNode from './named-node' + +const RDFlibjsSupports: SupportTable = { + COLLECTIONS: true, + DEFAULT_GRAPH_TYPE: true, + EQUALS_METHOD: true, + VARIABLE_TYPE: true, + IDENTITY: false, + REVERSIBLE_ID: false, + ID: false, +} + +/** + * Data factory which also supports Collections + * + * Necessary for preventing circular dependencies. + */ +const ExtendedTermFactory = { + ...CanonicalDataFactory, + collection, + id, + supports: RDFlibjsSupports +} + +interface IRDFlibDataFactory extends DataFactory { + fetcher: (store: IndexedFormula, options: any) => Fetcher + graph: (features, opts) => IndexedFormula + lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal + st: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType + ) => Statement + triple: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType + ) => Statement +} + +/** Full RDFLib.js Data Factory + * @todo Add missing functions (isQuad, equals, toNQ), so Partial can be removed + */ +const RDFlibDataFactory: Partial = { + ...ExtendedTermFactory, + fetcher, + graph, + lit, + st, + triple, +} + +export default RDFlibDataFactory + +function id (term: TFNamedNode | TFBlankNode | TFLiteral | Collection ): Indexable { + if (!term) { + return term + } + if (Object.prototype.hasOwnProperty.call(term, "id") && typeof (term as NamedNode).id === "function") { + return (term as NamedNode).id() + } + if (Object.prototype.hasOwnProperty.call(term, "hashString")) { + return (term as NamedNode).hashString() + } + + if (term.termType === "Collection") { + Collection.toNT(term) + } + + return CanonicalDataFactory.id(term as NamedNode) +} +/** + * Creates a new collection + * @param elements - The initial element + */ +function collection(elements: ReadonlyArray): Collection { + return new Collection(elements) +} +/** + * Creates a new fetcher + * @param store - The store to use + * @param options - The options + */ +function fetcher(store: IndexedFormula, options: any): Fetcher { + return new Fetcher(store, options) +} +/** + * Creates a new graph (store) + */ +function graph (features = undefined, opts = undefined): IndexedFormula { + return new IndexedFormula(features, opts || { rdfFactory: ExtendedTermFactory }) +} +/** + * Creates a new literal node + * @param val The lexical value + * @param lang The language + * @param dt The datatype + */ +function lit(val: string, lang?: string, dt?: TFNamedNode): Literal { + return new Literal('' + val, lang, dt) +} +/** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The containing graph + */ +function st( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType +): Statement { + return new Statement(subject, predicate, object, graph) +} +/** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + */ +function triple( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType +): Statement { + return CanonicalDataFactory.quad(subject, predicate, object) +} diff --git a/src/default-graph.ts b/src/default-graph.ts index 193481213..fe7d53cab 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -1,12 +1,12 @@ 'use strict' import Node from './node-internal' -import { TermType } from "./types"; +import { TFDefaultGraph, TermType, DefaultGraphTermType } from './types' /** The RDF default graph */ -export default class DefaultGraph extends Node { +export default class DefaultGraph extends Node implements TFDefaultGraph { static termType = TermType.DefaultGraph; - termType = TermType.DefaultGraph; + termType: DefaultGraphTermType = TermType.DefaultGraph; constructor () { super('') diff --git a/src/empty.ts b/src/empty.ts index 80dbf7441..2067bdf55 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,10 +1,9 @@ -'use strict' import Node from './node-internal' -import { TermType } from "./types"; +import { TermType } from './types' /** - * Singleton subclass of an empty Collection. - */ +* An empty node +*/ export default class Empty extends Node { static termType = TermType.Empty diff --git a/src/fetcher.js b/src/fetcher.ts similarity index 65% rename from src/fetcher.js rename to src/fetcher.ts index 5a37eca51..1d3d8539a 100644 --- a/src/fetcher.js +++ b/src/fetcher.ts @@ -34,12 +34,23 @@ import rdfParse from './parse' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import * as Uri from './uri' -import { isNamedNode } from './utils/terms' import * as Util from './util' +import { isCollection, isTFNamedNode, termValue } from './utils/terms' import serialize from './serialize' +// @ts-ignore This is injected import { fetch as solidAuthCli } from 'solid-auth-cli' +// @ts-ignore This is injected import { fetch as solidAuthClient } from 'solid-auth-client' +import { + TFBlankNode, + TFNamedNode, + TFGraph, + TFSubject, + ContentType, + TFDataFactory, + TFPredicate +} from './types' // This is a special fetch which does OIDC auth, catching 401 errors const fetch = typeof window === 'undefined' ? solidAuthCli : solidAuthClient @@ -70,7 +81,7 @@ const CONTENT_TYPE_BY_EXT = { // make its own list and not rely on the prefixes used here, // and not be tempted to add to them, and them clash with those of another // application. -const getNS = (factory) => { +const getNS = (factory?: TFDataFactory) => { return { link: Namespace('http://www.w3.org/2007/ont/link#', factory), http: Namespace('http://www.w3.org/2007/ont/http#', factory), @@ -83,10 +94,117 @@ const getNS = (factory) => { } const ns = getNS() +interface FetchError extends Error { + statusText?: string + status?: StatusValues + response?: ExtendedResponse +} + +/** An extended interface of Response, since RDFlib.js adds some properties. */ +interface ExtendedResponse extends Response { + /** String representation of the Body */ + responseText?: string + /** Identifier of the reqest */ + req?: TFSubject + size?: number + timeout?: number + /** Used in UpdateManager.updateDav */ + error?: string +} + +/** tell typescript that a 'panes' child may exist on Window */ +declare global { + interface Window { + panes?: any + } +} + +declare var $SolidTestEnvironment: { + localSiteMap?: any +} + +type UserCallback = ( + ok: boolean, + message: string, + response?: any +) => void + +type HTTPMethods = 'GET' | 'PUT' | 'POST' | 'PATCH' | 'HEAD' | 'DELETE' | 'CONNECT' | 'TRACE' | 'OPTIONS' + +/** All valid inputs for initFetchOptions */ +type Options = Partial + +/** Initiated by initFetchOptions, which runs on load */ +interface AutoInitOptions extends RequestInit{ + /** The used Fetch function */ + fetch?: Fetch + /** + * Referring term, the resource which + * referred to this (for tracking bad links). + * The document in which this link was found. + */ + referringTerm?: TFNamedNode + /** Provided content type (for writes) */ + contentType?: string + /** + * Override the incoming header to + * force the data to be treated as this content-type (for reads) + */ + forceContentType?: ContentType + /** + * Load the data even if loaded before. + * Also sets the `Cache-Control:` header to `no-cache` + */ + force?: boolean + /** + * Original uri to preserve + * through proxying etc (`xhr.original`). + */ + baseURI: string + /** + * Whether this request is a retry via + * a proxy (generally done from an error handler) + */ + proxyUsed?: boolean + actualProxyURI?: string + /** flag for XHR/CORS etc */ + withCredentials?: boolean + /** Before we parse new data, clear old, but only on status 200 responses */ + clearPreviousData?: boolean + /** Prevents the addition of various metadata triples (about the fetch request) to the store*/ + noMeta?: boolean + noRDFa?: boolean + handlers?: Handler[] + timeout?: number + method?: HTTPMethods + retriedWithNoCredentials?: boolean + requestedURI?: string + // Seems to be required in some functions, such as XHTML parse and RedirectToProxy + resource: TFSubject + /** The serialized resource in the body*/ + // Used for storing metadata of requests + original: TFNamedNode + // Like requeststatus? Can contain text with error. + data?: string + // Probably an identifier for request?s + req: TFBlankNode + // Might be the same as Options.data + body?: string + headers: Headers + credentials?: 'include' | 'omit' +} + class Handler { - constructor (response, dom) { + // TODO: Document, type + response: ExtendedResponse + // TODO: Document, type + dom: Document + static pattern: RegExp + + constructor (response: ExtendedResponse, dom?: Document) { this.response = response - this.dom = dom + // The bang operator here might need to be removed. + this.dom = dom! } } @@ -95,13 +213,22 @@ class RDFXMLHandler extends Handler { return 'RDFXMLHandler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['application/rdf+xml'] = { 'q': 0.9 } } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + /** An XML String */ + responseText: String, + /** Requires .original */ + options: { + original: TFSubject + req: TFSubject + } & Options, + ) { let kb = fetcher.store if (!this.dom) { this.dom = Util.parseXML(responseText) @@ -110,11 +237,11 @@ class RDFXMLHandler extends Handler { if (root.nodeName === 'parsererror') { // Mozilla only See issue/issue110 // have to fail the request return fetcher.failFetch(options, 'Badly formed XML in ' + - options.resource.uri, 'parse_error') + (options as any).resource.value, 'parse_error') } let parser = new RDFParser(kb) try { - parser.parse(this.dom, options.original.uri, options.original, response) + parser.parse(this.dom, options.original.value, options.original) } catch (err) { return fetcher.failFetch(options, 'Syntax error parsing RDF/XML! ' + err, 'parse_error') @@ -133,12 +260,19 @@ class XHTMLHandler extends Handler { return 'XHTMLHandler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['application/xhtml+xml'] = {} } - parse (fetcher, responseText, options, response) { - let relation, reverse + parse ( + fetcher: Fetcher, + responseText: string, + options: { + resource: TFSubject + original: TFSubject + } & Options, + ): Promise | ExtendedResponse { + let relation, reverse: boolean if (!this.dom) { this.dom = Util.parseXML(responseText) } @@ -147,7 +281,7 @@ class XHTMLHandler extends Handler { // dc:title let title = this.dom.getElementsByTagName('title') if (title.length > 0) { - kb.add(options.resource, ns.dc('title'), kb.literal(title[0].textContent), + kb.add(options.resource, ns.dc('title'), kb.rdfFactory.literal(title[0].textContent as string), options.resource) // log.info("Inferring title of " + xhr.resource) } @@ -163,7 +297,7 @@ class XHTMLHandler extends Handler { } if (relation) { fetcher.linkData(options.original, relation, - links[x].getAttribute('href'), options.resource, reverse) + links[x].getAttribute('href') as string, options.resource, reverse) } } @@ -171,9 +305,11 @@ class XHTMLHandler extends Handler { let scripts = this.dom.getElementsByTagName('script') for (let i = 0; i < scripts.length; i++) { let contentType = scripts[i].getAttribute('type') - if (Parsable[contentType]) { - rdfParse(scripts[i].textContent, kb, options.original.uri, contentType) - rdfParse(scripts[i].textContent, kb, options.original.uri, contentType) + if (Parsable[contentType!]) { + // @ts-ignore incompatibility between Store.add and Formula.add + rdfParse(scripts[i].textContent as string, kb, options.original.value, contentType) + // @ts-ignore incompatibility between Store.add and Formula.add + rdfParse(scripts[i].textContent as string, kb, options.original.value, contentType) } } @@ -183,15 +319,15 @@ class XHTMLHandler extends Handler { if (!options.noRDFa && parseRDFaDOM) { // enable by default try { - parseRDFaDOM(this.dom, kb, options.original.uri) + parseRDFaDOM(this.dom, kb, options.original.value) } catch (err) { let msg = 'Error trying to parse ' + options.resource + ' as RDFa:\n' + err + ':\n' + err.stack - return fetcher.failFetch(options, msg, 'parse_error') + return fetcher.failFetch(options as AutoInitOptions, msg, 'parse_error') } } - return fetcher.doneFetch(options, this.response) + return fetcher.doneFetch(options as AutoInitOptions, this.response) } } XHTMLHandler.pattern = new RegExp('application/xhtml') @@ -201,12 +337,20 @@ class XMLHandler extends Handler { return 'XMLHandler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['text/xml'] = { 'q': 0.5 } fetcher.mediatypes['application/xml'] = { 'q': 0.5 } } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + responseText: string, + options: { + original: TFSubject + req: TFBlankNode + resource: TFSubject + } & Options, + ): ExtendedResponse | Promise { let dom = Util.parseXML(responseText) // XML Semantics defined by root element namespace @@ -223,7 +367,7 @@ class XMLHandler extends Handler { 'Has XML root element in the RDF namespace, so assume RDF/XML.') let rdfHandler = new RDFXMLHandler(this.response, dom) - return rdfHandler.parse(fetcher, responseText, options, response) + return rdfHandler.parse(fetcher, responseText, options) } break @@ -241,7 +385,7 @@ class XMLHandler extends Handler { 'Has XHTML DOCTYPE. Switching to XHTML Handler.\n') let xhtmlHandler = new XHTMLHandler(this.response, dom) - return xhtmlHandler.parse(fetcher, responseText, options, response) + return xhtmlHandler.parse(fetcher, responseText, options) } } @@ -254,7 +398,7 @@ class XMLHandler extends Handler { 'Has a default namespace for ' + 'XHTML. Switching to XHTMLHandler.\n') let xhtmlHandler = new XHTMLHandler(this.response, dom) - return xhtmlHandler.parse(fetcher, responseText, options, response) + return xhtmlHandler.parse(fetcher, responseText, options) } } @@ -275,13 +419,21 @@ class HTMLHandler extends Handler { return 'HTMLHandler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['text/html'] = { 'q': 0.9 } } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + responseText: string, + options: { + req: TFBlankNode, + resource: TFSubject, + original: TFSubject, + } & Options + ): Promise | ExtendedResponse { let kb = fetcher.store // We only handle XHTML so we have to figure out if this is XML @@ -291,7 +443,7 @@ class HTMLHandler extends Handler { "it's XHTML as the content-type was text/html.\n") let xhtmlHandler = new XHTMLHandler(this.response) - return xhtmlHandler.parse(fetcher, responseText, options, response) + return xhtmlHandler.parse(fetcher, responseText, options) } // DOCTYPE html @@ -300,7 +452,7 @@ class HTMLHandler extends Handler { 'Has XHTML DOCTYPE. Switching to XHTMLHandler.\n') let xhtmlHandler = new XHTMLHandler(this.response) - return xhtmlHandler.parse(fetcher, responseText, options, response) + return xhtmlHandler.parse(fetcher, responseText, options) } // xmlns @@ -309,14 +461,14 @@ class HTMLHandler extends Handler { 'Has default namespace for XHTML, so switching to XHTMLHandler.\n') let xhtmlHandler = new XHTMLHandler(this.response) - return xhtmlHandler.parse(fetcher, responseText, options, response) + return xhtmlHandler.parse(fetcher, responseText, options) } // dc:title // no need to escape '/' here let titleMatch = (new RegExp('([\\s\\S]+?)', 'im')).exec(responseText) if (titleMatch) { - kb.add(options.resource, ns.dc('title'), kb.literal(titleMatch[1]), + kb.add(options.resource, ns.dc('title'), kb.rdfFactory.literal(titleMatch[1]), options.resource) // think about xml:lang later } kb.add(options.resource, ns.rdf('type'), ns.link('WebPage'), fetcher.appNode) @@ -332,13 +484,21 @@ class TextHandler extends Handler { return 'TextHandler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['text/plain'] = { 'q': 0.5 } } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + responseText: string, + options: { + req: TFSubject + original: TFSubject + resource: TFSubject + } & Options + ): ExtendedResponse | Promise { // We only speak dialects of XML right now. Is this XML? // Look for an XML declaration @@ -348,7 +508,7 @@ class TextHandler extends Handler { "it's XML but its content-type wasn't XML.\n") let xmlHandler = new XMLHandler(this.response) - return xmlHandler.parse(fetcher, responseText, options, response) + return xmlHandler.parse(fetcher, responseText, options) } // Look for an XML declaration @@ -357,7 +517,7 @@ class TextHandler extends Handler { "it's XML but its content-type wasn't XML.\n") let xmlHandler = new XMLHandler(this.response) - return xmlHandler.parse(fetcher, responseText, options, response) + return xmlHandler.parse(fetcher, responseText, options) } // We give up finding semantics - this is not an error, just no data @@ -373,7 +533,7 @@ class N3Handler extends Handler { return 'N3Handler' } - static register (fetcher) { + static register (fetcher: Fetcher) { fetcher.mediatypes['text/n3'] = { 'q': '1.0' } // as per 2008 spec @@ -387,10 +547,18 @@ class N3Handler extends Handler { } // post 2008 } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + responseText: string, + options: { + original: TFNamedNode + req: TFSubject + } & Options, + response: ExtendedResponse + ): ExtendedResponse | Promise { // Parse the text of this N3 file let kb = fetcher.store - let p = N3Parser(kb, kb, options.original.uri, options.original.uri, + let p = N3Parser(kb, kb, options.original.value, options.original.value, null, null, '', null) // p.loadBuf(xhr.responseText) try { @@ -409,7 +577,7 @@ class N3Handler extends Handler { } N3Handler.pattern = new RegExp('(application|text)/(x-)?(rdf\\+)?(n3|turtle)') -const HANDLERS = { +const defaultHandlers = { RDFXMLHandler, XHTMLHandler, XMLHandler, HTMLHandler, TextHandler, N3Handler } @@ -422,12 +590,78 @@ function isXHTML (responseText) { return responseText.substr(docTypeStart, docTypeEnd - docTypeStart).indexOf('XHTML') !== -1 } -function isXML (responseText) { - return responseText.match(/\s*<\?xml\s+version\s*=[^<>]+\?>/) +function isXML (responseText: string): boolean { + const match = responseText.match(/\s*<\?xml\s+version\s*=[^<>]+\?>/) + return match ? true : false } -function isXMLNS (responseText) { - return responseText.match(/[^(/) +function isXMLNS (responseText: string): boolean { + const match = responseText.match(/[^(/) + return match ? true : false +} + +type StatusValues = + /** No record of web access or record reset */ + undefined | + /** Has been requested, fetch in progress */ + true | + /** Received, OK */ + 'done' | + /** Not logged in */ + 401 | + /** HTTP status unauthorized */ + 403 | + /** Not found, resource does not exist */ + 404 | + /** In attempt to counter CORS problems retried */ + 'redirected' | + /** If it did fail */ + 'failed' | + 'parse_error' | + /** + * URI is not a protocol Fetcher can deal with + * other strings mean various other errors. + */ + 'unsupported_protocol' | + 'timeout' | + /** Any other HTTP status code */ + number + +interface MediatypesMap { + [id: string]: { + // Either string '1.0' or number 1.0 is allowed + 'q'?: number | string + }; +} + +interface RequestedMap { + [uri: string]: StatusValues +} + +interface TimeOutsMap { + [uri: string]: number[] +} + +interface FetchQueue { + [uri: string]: Promise +} + +interface FetchCallbacks { + [uri: string]: UserCallback[] +} + +interface BooleanMap { + [uri: string]: boolean +} + +// Not sure about the shapes of this. Response? FetchError? +type Result = Response + +/** Differs from normal Fetch, has an extended Response type */ +type Fetch = (input: RequestInfo, init?: RequestInit) => Promise; + +interface CallbackifyInterface { + fireCallbacks: Function } /** Fetcher @@ -438,11 +672,50 @@ function isXMLNS (responseText) { * figuring how to parse them. It will also refresh, remove, the data * and put back the fata to the web. */ -export default class Fetcher { +export default class Fetcher implements CallbackifyInterface { + store: IndexedFormula + timeout: number + _fetch: Fetch + mediatypes: MediatypesMap + /** Denoting this session */ + appNode: TFBlankNode /** - * @constructor - */ - constructor (store, options = {}) { + * this.requested[uri] states: + * undefined no record of web access or records reset + * true has been requested, fetch in progress + * 'done' received, Ok + * 401 Not logged in + * 403 HTTP status unauthorized + * 404 Resource does not exist. Can be created etc. + * 'redirected' In attempt to counter CORS problems retried. + * 'parse_error' Parse error + * 'unsupported_protocol' URI is not a protocol Fetcher can deal with + * other strings mean various other errors. + */ + requested: RequestedMap + /** List of timeouts associated with a requested URL */ + timeouts: TimeOutsMap + /** Redirected from *key uri* to *value uri* */ + redirectedTo: Record + fetchQueue: FetchQueue + /** fetchCallbacks[uri].push(callback) */ + fetchCallbacks: FetchCallbacks + /** Keep track of explicit 404s -> we can overwrite etc */ + nonexistent: BooleanMap + lookedUp: BooleanMap + handlers: Array + ns: { [k: string]: (ln: string) => TFPredicate } + static HANDLERS: { + [handlerName: number]: Handler + } + static CONTENT_TYPE_BY_EXT: Record + // TODO: Document this + static crossSiteProxyTemplate: any + + /** Methods added by calling Util.callbackify in the constructor*/ + fireCallbacks!: Function + + constructor (store: IndexedFormula, options: Options = {}) { this.store = store || new IndexedFormula() this.ns = getNS(this.store.rdfFactory) this.timeout = options.timeout || 30000 @@ -453,27 +726,14 @@ export default class Fetcher { throw new Error('No _fetch function availble for Fetcher') } - this.appNode = this.store.bnode() // Denoting this session + this.appNode = this.store.rdfFactory.blankNode() this.store.fetcher = this // Bi-linked this.requested = {} - // this.requested[uri] states: - // undefined no record of web access or records reset - // true has been requested, fetch in progress - // 'done' received, Ok - // 401 Not logged in - // 403 HTTP status unauthorized - // 404 Resource does not exist. Can be created etc. - // 'redirected' In attempt to counter CORS problems retried. - // 'parse_error' Parse error - // 'unsupported_protocol' URI is not a protocol Fetcher can deal with - // other strings mean various other errors. - // - this.timeouts = {} // list of timeouts associated with a requested URL - this.redirectedTo = {} // When 'redirected' + this.timeouts = {} + this.redirectedTo = {} this.fetchQueue = {} - this.fetchCallbacks = {} // fetchCallbacks[uri].push(callback) - - this.nonexistent = {} // keep track of explicit 404s -> we can overwrite etc + this.fetchCallbacks = {} + this.nonexistent = {} this.lookedUp = {} this.handlers = [] this.mediatypes = { @@ -486,10 +746,10 @@ export default class Fetcher { // In switching to fetch(), 'recv', 'headers' and 'load' do not make sense Util.callbackify(this, ['request', 'fail', 'refresh', 'retract', 'done']) - Object.keys(HANDLERS).map(key => this.addHandler(HANDLERS[key])) + Object.keys(options.handlers || defaultHandlers).map(key => this.addHandler(defaultHandlers[key])) } - static crossSiteProxy (uri) { + static crossSiteProxy (uri: string): undefined | any { if (Fetcher.crossSiteProxyTemplate) { return Fetcher.crossSiteProxyTemplate .replace('{uri}', encodeURIComponent(uri)) @@ -498,12 +758,7 @@ export default class Fetcher { } } - /** - * @param uri {string} - * - * @returns {string} - */ - static offlineOverride (uri) { + static offlineOverride (uri: string): string { // Map the URI to a localhost proxy if we are running on localhost // This is used for working offline, e.g. on planes. // Is the script itself is running in localhost, then access all @@ -529,9 +784,14 @@ export default class Fetcher { return requestedURI } - static proxyIfNecessary (uri) { + static proxyIfNecessary (uri: string) { var UI - if (typeof window !== 'undefined' && window.panes && (UI = window.panes.UI) && UI.isExtension) { + if ( + typeof window !== 'undefined' && + (window as any).panes && + (UI = (window as any).panes.UI) && + UI.isExtension + ) { return uri } // Extension does not need proxy @@ -577,24 +837,19 @@ export default class Fetcher { /** * Tests whether the uri's protocol is supported by the Fetcher. - * - * @param uri {string} - * - * @returns {boolean} + * @param uri */ - static unsupportedProtocol (uri) { + static unsupportedProtocol (uri: string): boolean { let pcol = Uri.protocol(uri) return (pcol === 'tel' || pcol === 'mailto' || pcol === 'urn') } /** Decide on credentials using old XXHR api or new fetch() one - * @param requestedURI {string} - * @param options {Object} - * - * @returns {} + * @param requestedURI + * @param options */ - static setCredentials (requestedURI, options = {}) { + static setCredentials (requestedURI: string, options: Options = {}) { // 2014 CORS problem: // XMLHttpRequest cannot load http://www.w3.org/People/Berners-Lee/card. // A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' @@ -618,7 +873,7 @@ export default class Fetcher { * Loads a web resource or resources into the store. * * A resource may be given as NamedNode object, or as a plain URI. - * an arrsy of resources will be given, in which they will be fetched in parallel. + * an array of resources will be given, in which they will be fetched in parallel. * By default, the HTTP headers are recorded also, in the same store, in a separate graph. * This allows code like editable() for example to test things about the resource. * @@ -657,29 +912,31 @@ export default class Fetcher { * * @returns {Promise} */ - load (uri, options = {}) { + load ( + uri: TFNamedNode | string | Array, + options: Options = {} + ): Promise | Promise[] { options = Object.assign({}, options) // Take a copy as we add stuff to the options!! if (uri instanceof Array) { return Promise.all( + // @ts-ignore Returns an array of promises. Without this ignore, the type is recursive uri.map(x => { return this.load(x, Object.assign({}, options)) }) ) } - let docuri = uri.uri || uri + let docuri = termValue(uri as NamedNode) docuri = docuri.split('#')[0] options = this.initFetchOptions(docuri, options) - return this.pendingFetchPromise(docuri, options.baseURI, options) + return this.pendingFetchPromise(docuri, (options as AutoInitOptions).baseURI, (options as AutoInitOptions)) } - /** - * @param uri {string} - * @param originalUri {string} - * @param options {Object} - * @returns {Promise} - */ - pendingFetchPromise (uri, originalUri, options) { + pendingFetchPromise ( + uri: string, + originalUri: string, + options: AutoInitOptions + ): Promise { let pendingPromise // Check to see if some request is already dealing with this uri @@ -694,7 +951,7 @@ export default class Fetcher { this.fetchQueue[originalUri] = pendingPromise // Clean up the queued promise after a time, if it's resolved - this.cleanupFetchRequest(originalUri, options, this.timeout) + this.cleanupFetchRequest(originalUri, this.timeout) } return pendingPromise.then(x => { @@ -706,21 +963,18 @@ export default class Fetcher { }) } - cleanupFetchRequest (originalUri, options, timeout) { + cleanupFetchRequest (originalUri: string, timeout: number) { this.timeouts[originalUri] = (this.timeouts[originalUri] || []).concat(setTimeout(() => { if (!this.isPending(originalUri)) { delete this.fetchQueue[originalUri] } - }, timeout)) + }, timeout) as unknown as number) } - /** - * @param uri {string} - * @param options {Object} - * - * @returns {Object} - */ - initFetchOptions (uri, options) { + initFetchOptions ( + uri: string, + options: Options + ): AutoInitOptions { let kb = this.store let isGet = !options.method || options.method.toUpperCase() === 'GET' @@ -728,11 +982,11 @@ export default class Fetcher { options.force = true } - options.resource = kb.sym(uri) // This might be proxified + options.resource = kb.rdfFactory.namedNode(uri) // This might be proxified options.baseURI = options.baseURI || uri // Preserve though proxying etc - options.original = kb.sym(options.baseURI) + options.original = kb.rdfFactory.namedNode(options.baseURI) options.req = kb.bnode() - options.headers = options.headers || {} + options.headers = options.headers || new Headers if (options.contentType) { options.headers['content-type'] = options.contentType @@ -756,7 +1010,7 @@ export default class Fetcher { } options.actualProxyURI = actualProxyURI - return options + return options as AutoInitOptions } /** @@ -767,7 +1021,7 @@ export default class Fetcher { * * @returns {Promise} fetch() result or an { error, status } object */ - fetchUri (docuri, options) { + fetchUri (docuri: string, options: AutoInitOptions): Promise { if (!docuri) { return Promise.reject(new Error('Cannot fetch an empty uri')) } @@ -781,17 +1035,24 @@ export default class Fetcher { if (!options.force) { if (state === 'fetched') { // URI already fetched and added to store return Promise.resolve( - this.doneFetch(options, {status: 200, ok: true, statusText: 'Already loaded into quadstore.'}) + // @ts-ignore This is not a valid response object + this.doneFetch(options, { + status: 200, + ok: true, + statusText: 'Already loaded into quadstore.' + }) ) } if (state === 'failed' && this.requested[docuri] === 404) { // Remember nonexistence let message = 'Previously failed: ' + this.requested[docuri] - let dummyResponse = { + // @ts-ignore This is not a valid response object + let dummyResponse: ExtendedResponse = { url: docuri, - status: this.requested[docuri], + // This does not comply to Fetch spec, it can be a string value in rdflib + status: this.requested[docuri] as number, statusText: message, responseText: message, - headers: {}, // Headers() ??? + headers: new Headers, // Headers() ??? ok: false, body: null, bodyUsed: false, @@ -816,31 +1077,32 @@ export default class Fetcher { let { actualProxyURI } = options - return this._fetch(actualProxyURI, options) + return this._fetch((actualProxyURI as string), options) .then(response => this.handleResponse(response, docuri, options), - error => { // @@ handleError? - let dummyResponse = { - url: actualProxyURI, - status: 999, // @@ what number/string should fetch failures report? - statusText: (error.name || 'network failure') + ': ' + - (error.errno || error.code || error.type), - responseText: error.message, - headers: {}, // Headers() ??? - ok: false, - body: null, - bodyUsed: false, - size: 0, - timeout: 0 - } - console.log('Fetcher: <' + actualProxyURI + '> Non-HTTP fetch exception: ' + error) - return this.handleError(dummyResponse, docuri, options) // possible credentials retry - // return this.failFetch(options, 'fetch failed: ' + error, 999, dummyResponse) // Fake status code: fetch exception - - // handleError expects a response so we fake some important bits. - /* - this.handleError(, docuri, options) - */ - } + error => { // @@ handleError? + // @ts-ignore Invalid response object + let dummyResponse: ExtendedResponse = { + url: actualProxyURI as string, + status: 999, // @@ what number/string should fetch failures report? + statusText: (error.name || 'network failure') + ': ' + + (error.errno || error.code || error.type), + responseText: error.message, + headers: new Headers(), // Headers() ??? + ok: false, + body: null, + bodyUsed: false, + size: 0, + timeout: 0 + } + console.log('Fetcher: <' + actualProxyURI + '> Non-HTTP fetch exception: ' + error) + return this.handleError(dummyResponse, docuri, options) // possible credentials retry + // return this.failFetch(options, 'fetch failed: ' + error, 999, dummyResponse) // Fake status code: fetch exception + + // handleError expects a response so we fake some important bits. + /* + this.handleError(, docuri, options) + */ + } ) } @@ -871,15 +1133,20 @@ export default class Fetcher { * response The fetch Response object (was: XHR) if there was was one * includes response.status as the HTTP status if any. */ - nowOrWhenFetched (uri, p2, userCallback, options = {}) { - uri = uri.uri || uri // allow symbol object or string to be passed + nowOrWhenFetched ( + uriIn: string | TFNamedNode, + p2?: UserCallback | Options, + userCallback?: UserCallback, + options: Options = {} + ): void { + const uri = termValue(uriIn) if (typeof p2 === 'function') { // nowOrWhenFetched (uri, userCallback) userCallback = p2 } else if (typeof p2 === 'undefined') { // original calling signature // referringTerm = undefined - } else if (isNamedNode(p2)) { + } else if (isTFNamedNode(p2)) { // referringTerm = p2 options.referringTerm = p2 } else { @@ -887,11 +1154,11 @@ export default class Fetcher { options = p2 } - this.load(uri, options) - .then(fetchResponse => { + (this.load(uri, options) as Promise) + .then((fetchResponse: ExtendedResponse) => { if (userCallback) { if (fetchResponse) { - if (fetchResponse.ok) { + if ((fetchResponse as Response).ok) { userCallback(true, 'OK', fetchResponse) } else { // console.log('@@@ fetcher.js Should not take this path !!!!!!!!!!!!') @@ -908,14 +1175,14 @@ export default class Fetcher { userCallback(false, oops) } } - }, function (err) { + }, function (err: FetchError) { var message = err.message || err.statusText message = 'Failed to load <' + uri + '> ' + message console.log(message) if (err.response && err.response.status) { message += ' status: ' + err.response.status } - userCallback(false, message, err.response) + (userCallback as any)(false, message, err.response) }) } @@ -923,10 +1190,8 @@ export default class Fetcher { * Records a status message (as a literal node) by appending it to the * request's metadata status collection. * - * @param req {BlankNode} - * @param statusMessage {string} */ - addStatus (req, statusMessage) { + addStatus (req: TFBlankNode, statusMessage: string) { // let now = new Date() statusMessage = '[' + now.getHours() + ':' + now.getMinutes() + ':' + @@ -934,9 +1199,9 @@ export default class Fetcher { // let kb = this.store - let statusNode = kb.the(req, this.ns.link('status')) - if (statusNode && statusNode.append) { - statusNode.append(kb.literal(statusMessage)) + const statusNode = kb.the(req, this.ns.link('status')) + if (isCollection(statusNode)) { + statusNode.append(kb.rdfFactory.literal(statusMessage)) } else { log.warn('web.js: No list to add to: ' + statusNode + ',' + statusMessage) } @@ -949,35 +1214,40 @@ export default class Fetcher { * - Adds an error triple with the fail message to the metadata * - Fires the 'fail' callback * - Rejects with an error result object, which has a response object if any - * - * @param options {Object} - * @param errorMessage {string} - * @param statusCode {number} - * @param response {Response} // when an fetch() error - * - * @returns {Promise} */ - failFetch (options, errorMessage, statusCode, response) { + failFetch ( + options: { + req: TFBlankNode + original: TFSubject + } & Options, + errorMessage: string, + statusCode: StatusValues, + response?: ExtendedResponse + ): Promise { this.addStatus(options.req, errorMessage) if (!options.noMeta) { - this.store.add(options.original, this.ns.link('error'), errorMessage) + this.store.add( + options.original, + this.ns.link('error'), + this.store.rdfFactory.literal(errorMessage) + ) } let meth = (options.method || 'GET').toUpperCase() let isGet = meth === 'GET' || meth === 'HEAD' if (isGet) { // only cache the status code on GET or HEAD - if (!options.resource.equals(options.original)) { + if (!(options as any).resource.equals(options.original)) { // console.log('@@ Recording failure ' + meth + ' original ' + options.original +option '( as ' + options.resource + ') : ' + statusCode) } else { // console.log('@@ Recording ' + meth + ' failure for ' + options.original + ': ' + statusCode) } - this.requested[Uri.docpart(options.original.uri)] = statusCode - this.fireCallbacks('fail', [options.original.uri, errorMessage]) + this.requested[Uri.docpart(options.original.value)] = statusCode + this.fireCallbacks('fail', [options.original.value, errorMessage]) } - var err = new Error('Fetcher: ' + errorMessage) + var err: FetchError = new Error('Fetcher: ' + errorMessage) // err.ok = false // Is taken as a response, will work too @@ phase out? err.status = statusCode @@ -989,24 +1259,30 @@ export default class Fetcher { // in the why part of the quad distinguish between HTML and HTTP header // Reverse is set iif the link was rev= as opposed to rel= - linkData (originalUri, rel, uri, why, reverse) { + linkData ( + originalUri: TFNamedNode, + rel: string, + uri: string, + why: TFGraph, + reverse?: boolean + ) { if (!uri) return let kb = this.store let predicate // See http://www.w3.org/TR/powder-dr/#httplink for describedby 2008-12-10 - let obj = kb.sym(Uri.join(uri, originalUri.uri)) + let obj = kb.rdfFactory.namedNode(Uri.join(uri, originalUri.value)) if (rel === 'alternate' || rel === 'seeAlso' || rel === 'meta' || rel === 'describedby') { - if (obj.uri === originalUri.uri) { return } + if (obj.value === originalUri.value) { return } predicate = this.ns.rdfs('seeAlso') } else if (rel === 'type') { - predicate = kb.sym('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') + predicate = kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') } else { // See https://www.iana.org/assignments/link-relations/link-relations.xml // Alas not yet in RDF yet for each predicate // encode space in e.g. rel="shortcut icon" - predicate = kb.sym( + predicate = kb.rdfFactory.namedNode( Uri.join(encodeURIComponent(rel), 'http://www.iana.org/assignments/link-relations/') ) @@ -1018,7 +1294,11 @@ export default class Fetcher { } } - parseLinkHeader (linkHeader, originalUri, reqNode) { + parseLinkHeader ( + linkHeader: string, + originalUri: TFNamedNode, + reqNode: TFGraph + ): void { if (!linkHeader) { return } // const linkexp = /<[^>]*>\s*(\s*;\s*[^()<>@,;:"/[\]?={} \t]+=(([^()<>@,;:"/[]?={} \t]+)|("[^"]*")))*(,|$)/g @@ -1033,11 +1313,14 @@ export default class Fetcher { const matches = linkHeader.match(linkexp) + if (matches == null) return; + for (let i = 0; i < matches.length; i++) { let split = matches[i].split('>') let href = split[0].substring(1) let ps = split[1] let s = ps.match(paramexp) + if (s == null) return for (let j = 0; j < s.length; j++) { let p = s[j] let paramsplit = p.split('=') @@ -1048,11 +1331,17 @@ export default class Fetcher { } } - doneFetch (options, response) { + doneFetch ( + options: { + req: TFSubject, + original: TFSubject + } & Options, + response: ExtendedResponse + ): Response { this.addStatus(options.req, 'Done.') - this.requested[options.original.uri] = 'done' + this.requested[options.original.value] = 'done' - this.fireCallbacks('done', [options.original.uri]) + this.fireCallbacks('done', [options.original.value]) response.req = options.req // Set the request meta blank node @@ -1064,14 +1353,14 @@ export default class Fetcher { * If only one was flagged as looked up, then the new node is looked up again, * which will make sure all the URIs are dereferenced */ - nowKnownAs (was, now) { - if (this.lookedUp[was.uri]) { + nowKnownAs (was: TFSubject, now: TFSubject): void { + if (this.lookedUp[was.value]) { // Transfer userCallback - if (!this.lookedUp[now.uri]) { + if (!this.lookedUp[now.value]) { this.lookUpThing(now, was) } - } else if (this.lookedUp[now.uri]) { - if (!this.lookedUp[was.uri]) { + } else if (this.lookedUp[now.value]) { + if (!this.lookedUp[was.value]) { this.lookUpThing(was, now) } } @@ -1079,21 +1368,19 @@ export default class Fetcher { /** * Writes back to the web what we have in the store for this uri - * - * @param uri {Node|string} - * @param [options={}] - * - * @returns {Promise} */ - putBack (uri, options = {}) { - uri = uri.uri || uri // Accept object or string + putBack ( + uri: TFNamedNode | string, + options: Options = {} + ): Promise { + uri = (uri as TFNamedNode).value || uri // Accept object or string let doc = new NamedNode(uri).doc() // strip off # options.contentType = options.contentType || 'text/turtle' - options.data = serialize(doc, this.store, doc.uri, options.contentType) + options.data = serialize(doc, this.store, doc.value, options.contentType) as string return this.webOperation('PUT', uri, options) } - webCopy (here, there, contentType) { + webCopy (here: string, there: string, contentType): Promise { return this.webOperation('GET', here) .then((result) => { return this.webOperation( @@ -1102,18 +1389,12 @@ export default class Fetcher { }) } - /** - * @param uri {string} - * @param [options] {Object} - * - * @returns {Promise} - */ - delete (uri, options) { + delete (uri: string, options: Options): Promise { return this.webOperation('DELETE', uri, options) .then(response => { this.requested[uri] = 404 this.nonexistent[uri] = true - this.unload(this.store.sym(uri)) + this.unload(this.store.rdfFactory.namedNode(uri)) return response }) @@ -1122,23 +1403,26 @@ export default class Fetcher { /** Create an empty resource if it really does not exist * Be absolutely sure something does not exist before creating a new empty file * as otherwise existing could be deleted. - * @param doc {NamedNode} - The resource - * @returns {Promise} + * @param doc - The resource */ - async createIfNotExists (doc, contentType = 'text/turtle', data = '') { + async createIfNotExists ( + doc: NamedNode, + contentType = 'text/turtle', + data = '' + ): Promise { const fetcher = this try { - var response = await fetcher.load(doc) + var response = await fetcher.load(doc as TFNamedNode) } catch (err) { if (err.response.status === 404) { console.log('createIfNotExists: doc does NOT exist, will create... ' + doc) try { - response = await fetcher.webOperation('PUT', doc.uri, {data, contentType}) + response = await fetcher.webOperation('PUT', doc.value, {data, contentType}) } catch (err) { console.log('createIfNotExists doc FAILED: ' + doc + ': ' + err) throw err } - delete fetcher.requested[doc.uri] // delete cached 404 error + delete fetcher.requested[doc.value] // delete cached 404 error // console.log('createIfNotExists doc created ok ' + doc) return response } else { @@ -1147,20 +1431,22 @@ export default class Fetcher { } } // console.log('createIfNotExists: doc exists, all good: ' + doc) - return response + return response as Response } /** - * @param parentURI {string} URI of parent container - * @param [folderName] {string} Optional folder name (slug) - * @param [data] {string} Optional folder metadata - * - * @returns {Promise} + * @param parentURI URI of parent container + * @param folderName - Optional folder name (slug) + * @param data - Optional folder metadata */ - createContainer (parentURI, folderName, data) { + createContainer ( + parentURI: string, + folderName: string, + data: string + ): Promise { let headers = { // Force the right mime type for containers - 'content-type': 'text/turtle', + 'content-type': ContentType.turtle, 'link': this.ns.ldp('BasicContainer') + '; rel="type"' } @@ -1168,7 +1454,8 @@ export default class Fetcher { headers['slug'] = folderName } - let options = { headers } + // @ts-ignore These headers lack some of the required operators. + let options: Options = { headers } if (data) { options.body = data @@ -1177,13 +1464,13 @@ export default class Fetcher { return this.webOperation('POST', parentURI, options) } - invalidateCache (uri) { - uri = uri.uri || uri // Allow a NamedNode to be passed as it is very common + invalidateCache (iri: string | TFNamedNode): void { + const uri = termValue(iri) const fetcher = this if (fetcher.fetchQueue && fetcher.fetchQueue[uri]) { console.log('Internal error - fetchQueue exists ' + uri) var promise = fetcher.fetchQueue[uri] - if (promise.PromiseStatus === 'resolved') { + if (promise['PromiseStatus'] === 'resolved') { delete fetcher.fetchQueue[uri] } else { // pending delete fetcher.fetchQueue[uri] @@ -1199,21 +1486,21 @@ export default class Fetcher { delete fetcher.nonexistent[uri] } } + /** * A generic web opeation, at the fetch() level. * does not invole the quadstore. * * Returns promise of Response * If data is returned, copies it to response.responseText before returning - * - * @param method - * @param uri or NamedNode - * @param options - * - * @returns {Promise} */ - webOperation (method, uri, options = {}) { - uri = uri.uri || uri // Allow a NamedNode to be passed as it is very common + webOperation ( + method: HTTPMethods, + uriIn: string | TFNamedNode, + // Not sure about this type. Maybe this Options is different? + options: Options = {} + ): Promise { + const uri = termValue(uriIn) options.method = method options.body = options.data || options.body options.force = true @@ -1223,8 +1510,8 @@ export default class Fetcher { throw new Error('Web operation sending data must have a defined contentType.') } if (options.contentType) { - options.headers = options.headers || {} - options.headers['content-type'] = options.contentType + (options as any).headers = options.headers || {}; + (options as any).headers['content-type'] = options.contentType } Fetcher.setCredentials(uri, options) @@ -1247,11 +1534,11 @@ export default class Fetcher { if (response.statusText) msg += ' (' + response.statusText + ')' msg += ' on ' + method + ' of <' + uri + '>' if (response.responseText) msg += ': ' + response.responseText - let e2 = new Error(msg) + let e2: FetchError = new Error(msg) e2.response = response reject(e2) } - }, err => { + }, (err: Error) => { let msg = 'Fetch error for ' + method + ' of <' + uri + '>:' + err reject(new Error(msg)) }) @@ -1262,14 +1549,15 @@ export default class Fetcher { * Looks up something. * Looks up all the URIs a things has. * - * @param term {NamedNode} canonical term for the thing whose URI is + * @param term - canonical term for the thing whose URI is * to be dereferenced - * @param rterm {NamedNode} the resource which referred to this + * @param rterm - the resource which referred to this * (for tracking bad links) - * - * @returns {Promise} */ - lookUpThing (term, rterm) { + lookUpThing ( + term: TFSubject, + rterm: TFSubject + ): Promise | Promise[] { let uris = this.store.uris(term) // Get all URIs uris = uris.map(u => Uri.docpart(u)) // Drop hash fragments @@ -1277,30 +1565,30 @@ export default class Fetcher { this.lookedUp[u] = true }) + // @ts-ignore Recursive type return this.load(uris, { referringTerm: rterm }) } /** * Looks up response header. * - * @param doc - * @param header - * * @returns {Array|undefined} a list of header values found in a stored HTTP * response, or [] if response was found but no header found, * or undefined if no response is available. * Looks for { [] link:requestedURI ?uri; link:response [ httph:header-name ?value ] } */ - getHeader (doc, header) { + getHeader ( + doc: TFNamedNode, + header: string + ): undefined | string[] { const kb = this.store - const requests = kb.each(undefined, this.ns.link('requestedURI'), doc.uri) + const requests = kb.each(undefined, this.ns.link('requestedURI'), doc) as TFSubject[] for (let r = 0; r < requests.length; r++) { let request = requests[r] if (request !== undefined) { - let response = kb.any(request, this.ns.link('response')) - - if (response !== undefined && kb.anyValue(response, this.ns.http('status')) && kb.anyValue(response, this.ns.http('status')).startsWith('2')) { + let response = kb.any(request, this.ns.link('response')) as TFSubject + if (response !== undefined && kb.anyValue(response, this.ns.http('status')) && (kb.anyValue(response, this.ns.http('status')) as string).startsWith('2')) { // Only look at success returns - not 401 error messagess etc let results = kb.each(response, this.ns.httph(header.toLowerCase())) @@ -1315,24 +1603,22 @@ export default class Fetcher { return undefined } - /** - * - * @param docuri - * @param options - */ - saveRequestMetadata (docuri, options) { + saveRequestMetadata ( + docuri: string, + options: AutoInitOptions + ) { let req = options.req let kb = this.store let rterm = options.referringTerm this.addStatus(options.req, 'Accept: ' + options.headers['accept']) - if (rterm && rterm.uri) { - kb.add(docuri, this.ns.link('requestedBy'), rterm.uri, this.appNode) + if (isTFNamedNode(rterm)) { + kb.add(kb.rdfFactory.namedNode(docuri), this.ns.link('requestedBy'), rterm, this.appNode) } - if (options.original && options.original.uri !== docuri) { - kb.add(req, this.ns.link('orginalURI'), kb.literal(options.original.uri), + if (options.original && options.original.value !== docuri) { + kb.add(req, this.ns.link('orginalURI'), kb.rdfFactory.literal(options.original.value), this.appNode) } @@ -1341,35 +1627,41 @@ export default class Fetcher { now.getSeconds() + '] ' kb.add(req, this.ns.rdfs('label'), - kb.literal(timeNow + ' Request for ' + docuri), this.appNode) - kb.add(req, this.ns.link('requestedURI'), kb.literal(docuri), this.appNode) + kb.rdfFactory.literal(timeNow + ' Request for ' + docuri), this.appNode) + kb.add(req, this.ns.link('requestedURI'), kb.rdfFactory.literal(docuri), this.appNode) kb.add(req, this.ns.link('status'), kb.collection(), this.appNode) } - saveResponseMetadata (response, options) { + saveResponseMetadata ( + response: Response, + options: { + req: TFBlankNode, + resource: TFSubject + } & Options + ): TFBlankNode { const kb = this.store let responseNode = kb.bnode() kb.add(options.req, this.ns.link('response'), responseNode, responseNode) kb.add(responseNode, this.ns.http('status'), - kb.literal(response.status), responseNode) + kb.rdfFactory.literal(response.status as any), responseNode) kb.add(responseNode, this.ns.http('statusText'), - kb.literal(response.statusText), responseNode) + kb.rdfFactory.literal(response.statusText), responseNode) - if (!options.resource.uri.startsWith('http')) { + if (!options.resource.value.startsWith('http')) { return responseNode } // Save the response headers response.headers.forEach((value, header) => { - kb.add(responseNode, this.ns.httph(header), value, responseNode) + kb.add(responseNode, this.ns.httph(header), this.store.rdfFactory.literal(value), responseNode) if (header === 'content-type') { kb.add( options.resource, this.ns.rdf('type'), - kb.namedNode(Util.mediaTypeClass(value).value), + kb.rdfFactory.namedNode(Util.mediaTypeClass(value).value), responseNode ) } @@ -1378,11 +1670,11 @@ export default class Fetcher { return responseNode } - objectRefresh (term) { + objectRefresh (term: TFNamedNode): void { let uris = this.store.uris(term) // Get all URIs if (typeof uris !== 'undefined') { for (let i = 0; i < uris.length; i++) { - this.refresh(this.store.sym(Uri.docpart(uris[i]))) + this.refresh(this.store.rdfFactory.namedNode(Uri.docpart(uris[i]))) // what about rterm? } } @@ -1390,10 +1682,13 @@ export default class Fetcher { /* refresh Reload data from a given document ** - ** @param {NamedNode} term - An RDF Named Node for the eodcument in question - ** @param {function } userCallback - A function userCallback(ok, message, response) + ** @param term - An RDF Named Node for the eodcument in question + ** @param userCallback - A function userCallback(ok, message, response) */ - refresh (term, userCallback) { // sources_refresh + refresh ( + term: TFNamedNode, + userCallback?: UserCallback + ): void { // sources_refresh this.fireCallbacks('refresh', arguments) this.nowOrWhenFetched(term, { force: true, clearPreviousData: true }, @@ -1402,27 +1697,30 @@ export default class Fetcher { /* refreshIfExpired Conditional refresh if Expired ** - ** @param {NamedNode} term - An RDF Named Node for the eodcument in question - ** @param {function } userCallback - A function userCallback(ok, message, response) + ** @param term - An RDF Named Node for the eodcument in question + ** @param userCallback - A function userCallback(ok, message, response) */ - refreshIfExpired (term, userCallback) { + refreshIfExpired ( + term: TFNamedNode, + userCallback: UserCallback + ): void { let exp = this.getHeader(term, 'Expires') - if (!exp || (new Date(exp).getTime()) <= (new Date().getTime())) { + if (!exp || (new Date(exp[0]).getTime()) <= (new Date().getTime())) { this.refresh(term, userCallback) } else { userCallback(true, 'Not expired', {}) } } - retract (term) { // sources_retract + retract (term: TFGraph) { // sources_retract this.store.removeMany(undefined, undefined, undefined, term) - if (term.uri) { - delete this.requested[Uri.docpart(term.uri)] + if (term.value) { + delete this.requested[Uri.docpart(term.value)] } this.fireCallbacks('retract', arguments) } - getState (docuri) { + getState (docuri: string) { if (typeof this.requested[docuri] === 'undefined') { return 'unrequested' } else if (this.requested[docuri] === true) { @@ -1436,24 +1734,27 @@ export default class Fetcher { } } - isPending (docuri) { // sources_pending + isPending (docuri: string) { // sources_pending // doing anyStatementMatching is wasting time // if it's not pending: false -> flailed // 'done' -> done 'redirected' -> redirected return this.requested[docuri] === true } - unload (term) { + unload (term: TFNamedNode) { this.store.removeDocument(term) - delete this.requested[term.uri] // So it can be loaded again + delete this.requested[term.value] // So it can be load2ed again } - addHandler (handler) { - this.handlers.push(handler) - handler.register(this) + addHandler (handler: typeof Handler) { + this.handlers.push(handler); + (handler as any).register(this) } - retryNoCredentials (docuri, options) { + retryNoCredentials ( + docuri: string, + options + ): Promise { console.log('Fetcher: CORS: RETRYING with NO CREDENTIALS for ' + options.resource) options.retriedWithNoCredentials = true // protect against being called twice @@ -1466,18 +1767,14 @@ export default class Fetcher { this.addStatus(options.req, 'Abort: Will retry with credentials SUPPRESSED to see if that helps') - return this.load(docuri, newOptions) + return this.load(docuri, newOptions) as Promise } /** * Tests whether a request is being made to a cross-site URI (for purposes * of retrying with a proxy) - * - * @param uri {string} - * - * @returns {boolean} */ - isCrossSite (uri) { + isCrossSite (uri: string): boolean { // Mashup situation, not node etc if (typeof document === 'undefined' || !document.location) { return false @@ -1485,20 +1782,18 @@ export default class Fetcher { const hostpart = Uri.hostpart const here = '' + document.location - return hostpart(here) && hostpart(uri) && hostpart(here) !== hostpart(uri) + return (hostpart(here) && hostpart(uri) && hostpart(here)) !== hostpart(uri) } /** * Called when there's a network error in fetch(), or a response * with status of 0. - * - * @param response {Response|Error} - * @param docuri {string} - * @param options {Object} - * - * @returns {Promise} */ - handleError (response, docuri, options) { + handleError ( + response: ExtendedResponse | Error, + docuri: string, + options: AutoInitOptions + ): Promise { if (this.isCrossSite(docuri)) { // Make sure we haven't retried already if (options.credentials && options.credentials === 'include' && !options.retriedWithNoCredentials) { @@ -1516,7 +1811,7 @@ export default class Fetcher { } var message - if (response.message) { + if (response instanceof Error) { message = 'Fetch error: ' + response.message } else { message = response.statusText @@ -1526,43 +1821,50 @@ export default class Fetcher { } // This is either not a CORS error, or retries have been made - return this.failFetch(options, message, response.status || 998, response) + return this.failFetch(options, message, (response as Response).status || 998, (response as Response)) } // deduce some things from the HTTP transaction - addType (rdfType, req, kb, locURI) { // add type to all redirected resources too + addType ( + rdfType: TFNamedNode, + req: TFSubject, + kb: IndexedFormula, + locURI: string + ): void { // add type to all redirected resources too let prev = req if (locURI) { var reqURI = kb.any(prev, this.ns.link('requestedURI')) - if (reqURI && reqURI !== locURI) { - kb.add(kb.sym(locURI), this.ns.rdf('type'), rdfType, this.appNode) + if (reqURI && reqURI.value !== locURI) { + kb.add(kb.rdfFactory.namedNode(locURI), this.ns.rdf('type'), rdfType, this.appNode) } } for (;;) { const doc = kb.any(prev, this.ns.link('requestedURI')) if (doc && doc.value) { - kb.add(kb.sym(doc.value), this.ns.rdf('type'), rdfType, this.appNode) + kb.add(kb.rdfFactory.namedNode(doc.value), this.ns.rdf('type'), rdfType, this.appNode) } // convert Literal - prev = kb.any(undefined, kb.sym('http://www.w3.org/2007/ont/link#redirectedRequest'), prev) + prev = kb.any(undefined, kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#redirectedRequest'), prev) as TFSubject if (!prev) { break } - var response = kb.any(prev, kb.sym('http://www.w3.org/2007/ont/link#response')) + var response = kb.any(prev, kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#response')) if (!response) { break } - var redirection = kb.any(response, kb.sym('http://www.w3.org/2007/ont/http#status')) + var redirection = kb.any((response as TFNamedNode), kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/http#status')) if (!redirection) { break } - if (redirection !== '301' && redirection !== '302') { break } + // @ts-ignore always true? + if ((redirection !== '301') && (redirection !== '302')) { break } } } /** * Handle fetch() response - * - * @param response {Response} fetch() response object - * @param docuri {string} - * @param options {Object} */ - handleResponse (response, docuri, options) { + handleResponse ( + response: ExtendedResponse, + docuri: string, + options: AutoInitOptions + ): Promise | ExtendedResponse { + const kb = this.store - const headers = response.headers + const headers: Headers = (response as Response).headers const reqNode = options.req @@ -1583,7 +1885,7 @@ export default class Fetcher { if (response.status >= 400) { if (response.status === 404) { - this.nonexistent[options.original.uri] = true + this.nonexistent[options.original.value] = true this.nonexistent[docuri] = true } @@ -1595,8 +1897,8 @@ export default class Fetcher { }) } - var diffLocation = null - var absContentLocation = null + var diffLocation: null | string = null + var absContentLocation: null | string = null if (contentLocation) { absContentLocation = Uri.join(contentLocation, docuri) if (absContentLocation !== docuri) { @@ -1604,9 +1906,9 @@ export default class Fetcher { } } if (response.status === 200) { - this.addType(this.ns.link('Document'), reqNode, kb, docuri) + this.addType(this.ns.link('Document') as TFNamedNode, reqNode, kb, docuri) if (diffLocation) { - this.addType(this.ns.link('Document'), reqNode, kb, + this.addType(this.ns.link('Document') as TFNamedNode, reqNode, kb, diffLocation) } @@ -1619,10 +1921,10 @@ export default class Fetcher { contentType.includes('application/pdf') if (contentType && isImage) { - this.addType(kb.sym('http://purl.org/dc/terms/Image'), reqNode, kb, + this.addType(kb.rdfFactory.namedNode('http://purl.org/dc/terms/Image'), reqNode, kb, docuri) if (diffLocation) { - this.addType(kb.sym('http://purl.org/dc/terms/Image'), reqNode, kb, + this.addType(kb.rdfFactory.namedNode('http://purl.org/dc/terms/Image'), reqNode, kb, diffLocation) } } @@ -1630,7 +1932,7 @@ export default class Fetcher { // If we have already got the thing at this location, abort if (contentLocation) { - if (!options.force && diffLocation && this.requested[absContentLocation] === 'done') { + if (!options.force && diffLocation && this.requested[absContentLocation as string] === 'done') { // we have already fetched this // should we smush too? // log.info("HTTP headers indicate we have already" + " retrieved " + @@ -1638,12 +1940,12 @@ export default class Fetcher { return this.doneFetch(options, response) } - this.requested[absContentLocation] = true + this.requested[absContentLocation as string] = true } - this.parseLinkHeader(headers.get('link'), options.original, reqNode) + this.parseLinkHeader(headers.get('link') as string, options.original, reqNode) - let handler = this.handlerForContentType(contentType, response) + let handler = this.handlerForContentType(contentType, response) as Handler if (!handler) { // Not a problem, we just don't extract data @@ -1651,30 +1953,30 @@ export default class Fetcher { return this.doneFetch(options, response) } - return response.text() + return response + .text() + // @ts-ignore Types seem right .then(responseText => { response.responseText = responseText - return handler.parse(this, responseText, options, response) + return (handler as N3Handler).parse(this, responseText, options, response) }) } - saveErrorResponse (response, responseNode) { + saveErrorResponse ( + response: ExtendedResponse, + responseNode: TFSubject + ): Promise { let kb = this.store return response.text() .then(content => { if (content.length > 10) { - kb.add(responseNode, this.ns.http('content'), kb.literal(content), responseNode) + kb.add(responseNode, this.ns.http('content'), kb.rdfFactory.literal(content), responseNode) } }) } - /** - * @param contentType {string} - * - * @returns {Handler|null} - */ - handlerForContentType (contentType, response) { + handlerForContentType (contentType: string, response: ExtendedResponse): Handler | null { if (!contentType) { return null } @@ -1683,39 +1985,32 @@ export default class Fetcher { return contentType.match(handler.pattern) }) + // @ts-ignore in practice all Handlers have constructors. return Handler ? new Handler(response) : null } - /** - * @param uri {string} - * - * @returns {string} - */ - guessContentType (uri) { - return CONTENT_TYPE_BY_EXT[uri.split('.').pop()] + guessContentType (uri: string): ContentType | undefined { + return CONTENT_TYPE_BY_EXT[uri.split('.').pop() as string] } - /** - * @param options {Object} - * @param headers {Headers} - * - * @returns {string} - */ - normalizedContentType (options, headers) { + normalizedContentType ( + options: AutoInitOptions, + headers: Headers + ): ContentType | string | null { if (options.forceContentType) { return options.forceContentType } let contentType = headers.get('content-type') if (!contentType || contentType.includes('application/octet-stream')) { - let guess = this.guessContentType(options.resource.uri) + let guess = this.guessContentType(options.resource.value) if (guess) { return guess } } - let protocol = Uri.protocol(options.resource.uri) + let protocol = Uri.protocol(options.resource.value) as string if (!contentType && ['file', 'chrome'].includes(protocol)) { return 'text/xml' @@ -1726,13 +2021,11 @@ export default class Fetcher { /** * Sends a new request to the specified uri. (Extracted from `onerrorFactory()`) - * - * @param newURI {string} - * @param options {Object} - * - * @returns {Promise} */ - redirectToProxy (newURI, options) { + redirectToProxy ( + newURI: string, + options: AutoInitOptions + ): Promise { this.addStatus(options.req, 'BLOCKED -> Cross-site Proxy to <' + newURI + '>') options.proxyUsed = true @@ -1741,15 +2034,15 @@ export default class Fetcher { const oldReq = options.req // request metadata blank node if (!options.noMeta) { - kb.add(oldReq, this.ns.link('redirectedTo'), kb.sym(newURI), oldReq) + kb.add(oldReq, this.ns.link('redirectedTo'), kb.rdfFactory.namedNode(newURI), oldReq) this.addStatus(oldReq, 'redirected to new request') // why } - this.requested[options.resource.uri] = 'redirected' - this.redirectedTo[options.resource.uri] = newURI + this.requested[options.resource.value] = 'redirected' + this.redirectedTo[options.resource.value] = newURI let newOptions = Object.assign({}, options) - newOptions.baseURI = options.resource.uri + newOptions.baseURI = options.resource.value return this.fetchUri(newURI, newOptions) .then(response => { @@ -1761,7 +2054,13 @@ export default class Fetcher { }) } - setRequestTimeout (uri, options) { + setRequestTimeout ( + uri: string, + options: { + req: TFSubject + original: TFSubject + } & Options + ): Promise { return new Promise((resolve) => { this.timeouts[uri] = (this.timeouts[uri] || []).concat(setTimeout(() => { if (this.isPending(uri) && @@ -1769,11 +2068,14 @@ export default class Fetcher { !options.proxyUsed) { resolve(this.failFetch(options, `Request to ${uri} timed out`, 'timeout')) } - }, this.timeout)) + }, this.timeout) as unknown as number) }) } - addFetchCallback (uri, callback) { + addFetchCallback ( + uri: string, + callback: UserCallback + ): void { if (!this.fetchCallbacks[uri]) { this.fetchCallbacks[uri] = [callback] } else { @@ -1803,5 +2105,5 @@ export default class Fetcher { // whether we want to track it ot not. including ontologies loaed though the XSSproxy } -Fetcher.HANDLERS = HANDLERS +Fetcher.HANDLERS = defaultHandlers Fetcher.CONTENT_TYPE_BY_EXT = CONTENT_TYPE_BY_EXT diff --git a/src/formula.ts b/src/formula.ts index 859038cb4..573a36a02 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -1,4 +1,3 @@ -'use strict' import BlankNode from './blank-node' import ClassOrder from './class-order' import Collection from './collection' @@ -6,18 +5,50 @@ import CanonicalDataFactory from './data-factory-internal' import log from './log' import NamedNode from './named-node' import Namespace from './namespace' -import Node from './node' +import Node from './node-internal' import Serializer from './serialize' import Statement from './statement' -import { TermType } from "./types"; -import { appliedFactoryMethods, arrayToStatements } from './util' +import { + Bindings, + TermType, + TFBlankNode, + TFGraph, + TFObject, + TFPredicate, + TFQuad, + TFSubject, + TFTerm +} from './types' import { isStatement } from './utils/terms' import Variable from './variable' - -/** @module formula */ +import { + DataFactory, + IdentityFactory, + Indexable, + TFIDFactoryTypes +} from './data-factory-type' +import { appliedFactoryMethods, arrayToStatements } from './utils' + +export function isFormula(value: T | TFTerm): value is Formula { + return (value as Node).termType === TermType.Graph +} export interface FormulaOpts { - rdfFactory?: any + dataCallback?: (q: TFQuad) => void + rdfArrayRemove?: (arr: TFQuad[], q: TFQuad) => void + rdfFactory?: IdentityFactory & DataFactory +} + +interface BooleanMap { + [uri: string]: boolean; +} + +interface MembersMap { + [uri: string]: TFQuad; +} + +interface UriMap { + [uri: string]: string; } /** @@ -27,22 +58,35 @@ export default class Formula extends Node { static termType = TermType.Graph classOrder = ClassOrder.Graph + /** The additional constraints */ constraints: ReadonlyArray; + /** * The accompanying fetcher instance. * * Is set by the fetcher when initialized. */ fetcher?: any - initBindings: { [id: string]: Node; } + + initBindings: ReadonlyArray + isVar = 0 + + /** + * A namespace for the specified namespace's URI + * @param nsuri The URI for the namespace + */ ns = Namespace - optional: any[] + + optional: ReadonlyArray + /** The factory used to generate statements and terms */ rdfFactory: any + /** The stored statements */ - statements: Statement[]; + statements: TFQuad[]; + termType = TermType.Graph /** @@ -53,9 +97,15 @@ export default class Formula extends Node { * @param initBindings - initial bindings used in Query * @param optional - optional * @param opts - * @param {DataFactory} opts.rdfFactory - The rdf factory that should be used by the store + * @param opts.rdfFactory - The rdf factory that should be used by the store */ - constructor (statements?: Statement[], constraints?, initBindings?, optional?, opts: Partial = {}) { + constructor ( + statements?: TFQuad[], + constraints?: ReadonlyArray, + initBindings?: ReadonlyArray, + optional?: ReadonlyArray, + opts: FormulaOpts = {} + ) { super('') this.termType = Formula.termType this.statements = statements || [] @@ -69,25 +119,32 @@ export default class Formula extends Node { this[factoryMethod] = (...args) => this.rdfFactory[factoryMethod](...args) } } + /** Add a statement from its parts - * @param {Node} subject - the first part of the statement - * @param {Node} predicate - the second part of the statement - * @param {Node} object - the third part of the statement - * @param {Node} graph - the last part of the statement + * @param subject - the first part of the statement + * @param predicate - the second part of the statement + * @param object - the third part of the statement + * @param graph - the last part of the statement */ - add (subject, predicate, object, graph) { - return this.statements.push(this.rdfFactory.quad(subject, predicate, object, graph)) + add ( + subject: TFSubject, + predicate: TFPredicate, + object: TFObject, + graph?: TFGraph + ): number { + return this.statements + .push(this.rdfFactory.quad(subject, predicate, object, graph)) } /** Add a statment object * @param {Statement} statement - An existing constructed statement to add */ - addStatement (statement) { + addStatement (statement: TFQuad): number { return this.statements.push(statement) } /** @deprecated use {this.rdfFactory.blankNode} instead */ - bnode (id) { + bnode (id?: string): TFBlankNode { return this.rdfFactory.blankNode(id) } @@ -95,7 +152,7 @@ export default class Formula extends Node { * Adds all the statements to this formula * @param statements - A collection of statements */ - addAll (statements) { + addAll (statements: TFQuad[]): void { statements.forEach(quad => { this.add(quad.subject, quad.predicate, quad.object, quad.graph) }) @@ -107,14 +164,19 @@ export default class Formula extends Node { * any(me, knows, null, null) - a person I know accoring to anything in store . * any(null, knows, me, null) - a person who know me accoring to anything in store . * - * @param {Node} s - A node to search for as subject, or if null, a wildcard - * @param {Node} p - A node to search for as predicate, or if null, a wildcard - * @param {Node} o - A node to search for as object, or if null, a wildcard - * @param {Node} g - A node to search for as graph, or if null, a wildcard - * @returns {Node} - A node which match the wildcard position, or null + * @param s - A node to search for as subject, or if null, a wildcard + * @param p - A node to search for as predicate, or if null, a wildcard + * @param o - A node to search for as object, or if null, a wildcard + * @param g - A node to search for as graph, or if null, a wildcard + * @returns A node which match the wildcard position, or null */ - any (s, p, o, g): any | undefined { - let st = this.anyStatementMatching(s, p, o, g) + any( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): TFTerm | null | undefined { + const st = this.anyStatementMatching(s, p, o, g) if (st == null) { return void 0 } else if (s == null) { @@ -130,28 +192,52 @@ export default class Formula extends Node { /** * Gets the value of a node that matches the specified pattern + * @param s The subject + * @param p The predicate + * @param o The object + * @param g The graph that contains the statement */ - anyValue (s, p, o, g) { - let y = this.any(s, p, o, g) + anyValue( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): string | void { + const y = this.any(s, p, o, g) return y ? y.value : void 0 } /** * Gets the first JavaScript object equivalent to a node based on the specified pattern + * @param s The subject + * @param p The predicate + * @param o The object + * @param g The graph that contains the statement */ - anyJS (s, p, o, g) { - let y = this.any(s, p, o, g) + anyJS( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): any { + const y = this.any(s, p, o, g) return y ? Node.toJS(y) : void 0 } /** * Gets the first statement that matches the specified pattern */ - anyStatementMatching (subj, pred, obj, why) { - let x = this.statementsMatching(subj, pred, obj, why, true) + anyStatementMatching( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): TFQuad | undefined { + let x = this.statementsMatching(s, p, o, g, true) if (!x || x.length === 0) { return undefined } + return x[0] } @@ -160,7 +246,7 @@ export default class Formula extends Node { * * Falls back to the rdflib hashString implementation if the given factory doesn't support id. */ - id (term): string | number { + id (term: TFIDFactoryTypes): Indexable { return this.rdfFactory.id(term) } @@ -168,20 +254,32 @@ export default class Formula extends Node { * Search the Store * This is really a teaching method as to do this properly you would use IndexedFormula * - * @param {Node} subj - A node to search for as subject, or if null, a wildcard - * @param {Node} pred - A node to search for as predicate, or if null, a wildcard - * @param {Node} obj - A node to search for as object, or if null, a wildcard - * @param {Node} graph - A node to search for as graph, or if null, a wildcard - * @param {Boolean} justOne - flag - stop when found one rather than get all of them? + * @param s - A node to search for as subject, or if null, a wildcard + * @param p - A node to search for as predicate, or if null, a wildcard + * @param o - A node to search for as object, or if null, a wildcard + * @param g - A node to search for as graph, or if null, a wildcard + * @param justOne - flag - stop when found one rather than get all of them? * @returns {Array} - An array of nodes which match the wildcard position */ - statementsMatching (subj?, pred?, obj?, graph?, justOne = false): Statement[] { - return this.statements.filter(st => - (!subj || subj.equals(st.subject)) && - (!pred || pred.equals(st.predicate)) && - (!obj || subj.equals(st.object)) && - (!graph || graph.equals(st.subject)) - ) + statementsMatching( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null, + justOne?: boolean + ): TFQuad[] { + const sts = this.statements.filter(st => + (!s || s.equals(st.subject)) && + (!p || p.equals(st.predicate)) && + (!o || o.equals(st.object)) && + (!g || g.equals(st.subject)) + ) + + if (justOne) { + return sts.length === 0 ? [] : [sts[0]] + } + + return sts } /** @@ -237,14 +335,19 @@ export default class Formula extends Node { * each(me, knows, null, null) - people I know accoring to anything in store . * each(null, knows, me, null) - people who know me accoring to anything in store . * - * @param {Node} s - A node to search for as subject, or if null, a wildcard - * @param {Node} p - A node to search for as predicate, or if null, a wildcard - * @param {Node} o - A node to search for as object, or if null, a wildcard - * @param {Node} g - A node to search for as graph, or if null, a wildcard + * @param s - A node to search for as subject, or if null, a wildcard + * @param p - A node to search for as predicate, or if null, a wildcard + * @param o - A node to search for as object, or if null, a wildcard + * @param g - A node to search for as graph, or if null, a wildcard * @returns {Array} - An array of nodes which match the wildcard position */ - each (s?: any | null, p?: any | null, o?: any | null, g?: any | null) { - const results: any[] = [] + each( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): TFTerm[] { + const results: TFTerm[] = [] let sts = this.statementsMatching(s, p, o, g, false) if (s == null) { for (let i = 0, len = sts.length; i < len; i++) { @@ -288,20 +391,20 @@ export default class Formula extends Node { * @return a hash of URIs */ findMembersNT (thisClass) { - let len2 - let len4 - let m - let members - let pred + let len2: number + let len4: number + let m: number + let members: MembersMap + let pred: TFPredicate let ref - let ref1 - let ref2 - let ref3 - let ref4 - let ref5 + let ref1: TFQuad[] + let ref2: TFTerm[] + let ref3: TFQuad[] + let ref4: TFTerm[] + let ref5: TFQuad[] let seeds let st - let u + let u: number seeds = {} seeds[thisClass.toNT()] = true members = {} @@ -319,7 +422,7 @@ export default class Formula extends Node { this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#domain'), this.fromNT(t)) for (let l = 0, len1 = ref2.length; l < len1; l++) { - pred = ref2[l] + pred = ref2[l] as TFPredicate ref3 = this.statementsMatching(void 0, pred) for (m = 0, len2 = ref3.length; m < len2; m++) { st = ref3[m] @@ -330,7 +433,7 @@ export default class Formula extends Node { this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#range'), this.fromNT(t)) for (let q = 0, len3 = ref4.length; q < len3; q++) { - pred = ref4[q] + pred = ref4[q] as TFPredicate ref5 = this.statementsMatching(void 0, pred) for (u = 0, len4 = ref5.length; u < len4; u++) { st = ref5[u] @@ -351,7 +454,7 @@ export default class Formula extends Node { * Get all the Classes of which we can RDFS-infer the subject is a member * @param subject - A named node */ - findMemberURIs (subject) { + findMemberURIs(subject: Node): UriMap { return this.NTtoURI(this.findMembersNT(subject)) } @@ -362,7 +465,7 @@ export default class Formula extends Node { * Does NOT return terms, returns URI strings. * We use NT representations in this version because they handle blank nodes. */ - findSubClassesNT (subject) { + findSubClassesNT(subject: Node): { [uri: string]: boolean } { let types = {} types[subject.toNT()] = true return this.transitiveClosure( @@ -380,7 +483,7 @@ export default class Formula extends Node { * Does NOT return terms, returns URI strings. * We use NT representations in this version because they handle blank nodes. */ - findSuperClassesNT (subject) { + findSuperClassesNT(subject: Node): { [uri: string]: boolean } { let types = {} types[subject.toNT()] = true return this.transitiveClosure(types, @@ -443,22 +546,36 @@ export default class Formula extends Node { ) } - findTypeURIs (subject) { + /** + * Get all the Classes of which we can RDFS-infer the subject is a member + * todo: This will loop is there is a class subclass loop (Sublass loops are + * not illegal) + * Returns a hash table where key is NT of type and value is statement why we + * think so. + * Does NOT return terms, returns URI strings. + * We use NT representations in this version because they handle blank nodes. + * @param subject - A subject node + */ + findTypeURIs(subject: TFSubject): UriMap { return this.NTtoURI(this.findTypesNT(subject)) } /** Trace statements which connect directly, or through bnodes * - * @param {NamedNode} subject - The node to start looking for statments - * @param {NamedNode} doc - The document to be searched, or null to search all documents + * @param subject - The node to start looking for statments + * @param doc - The document to be searched, or null to search all documents * @returns an array of statements, duplicate statements are suppresssed. */ - connectedStatements (subject, doc, excludePredicateURIs): Statement[] { + connectedStatements( + subject: TFSubject, + doc: TFGraph, + excludePredicateURIs?: ReadonlyArray + ): TFQuad[] { excludePredicateURIs = excludePredicateURIs || [] let todo = [subject] let done: { [k: string]: boolean } = {} let doneArcs: { [k: string]: boolean } = {} - let result: Statement[] = [] + let result: TFQuad[] = [] let self = this let follow = function (x) { let queue = function (x) { @@ -469,15 +586,15 @@ export default class Formula extends Node { } let sts = self.statementsMatching(null, null, x, doc) .concat(self.statementsMatching(x, null, null, doc)) - sts = sts.filter(function (st) { - if (excludePredicateURIs[st.predicate.uri]) return false - let hash = st.toNT() + sts = sts.filter(function (st): boolean { + if (excludePredicateURIs![st.predicate.value]) return false + let hash = (st as Statement).toNT() if (doneArcs[hash]) return false doneArcs[hash] = true return true } ) - sts.forEach(function (st, i) { + sts.forEach(function (st) { queue(st.subject) queue(st.object) }) @@ -490,7 +607,12 @@ export default class Formula extends Node { return result } - formula () { + /** + * Creates a new empty formula + * + * @param _features - Not applicable, but necessary for typing to pass + */ + formula(_features?: ReadonlyArray): Formula { return new Formula() } @@ -546,7 +668,7 @@ export default class Formula extends Node { } return true } else if (isStatement(s)) { - return this.holds(s.subject, s.predicate, s.object, s.why) + return this.holds(s.subject, s.predicate, s.object, s.graph) } else if (s.statements) { return this.holds(s.statements) } @@ -646,13 +768,14 @@ export default class Formula extends Node { * Creates a new formula with the substituting bindings applied * @param bindings - The bindings to substitute */ - substitute (bindings) { + //@ts-ignore signature not compatible with Node + substitute(bindings: Bindings): Formula { let statementsCopy = this.statements.map(function (ea) { - return ea.substitute(bindings) + return (ea as Statement).substitute(bindings) }) console.log('Formula subs statmnts:' + statementsCopy) const y = new Formula() - y.addAll(statementsCopy) + y.addAll(statementsCopy as TFQuad[]) console.log('indexed-form subs formula:' + y) return y } @@ -669,12 +792,22 @@ export default class Formula extends Node { /** * Gets the node matching the specified pattern. Throws when no match could be made. + * @param s - The subject + * @param p - The predicate + * @param o - The object + * @param g - The graph that contains the statement */ - the (s, p, o, g) { + the ( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): TFTerm | null | undefined { let x = this.any(s, p, o, g) if (x == null) { log.error('No value found for the() {' + s + ' ' + p + ' ' + o + '}.') } + return x } @@ -682,11 +815,17 @@ export default class Formula extends Node { * RDFS Inference * These are hand-written implementations of a backward-chaining reasoner * over the RDFS axioms. - * @param seeds {Object} - A hash of NTs of classes to start with + * @param seeds - A hash of NTs of classes to start with * @param predicate - The property to trace though * @param inverse - Trace inverse direction */ - transitiveClosure (seeds, predicate, inverse) { + transitiveClosure( + seeds: BooleanMap, + predicate: TFPredicate, + inverse?: boolean + ): { + [uri: string]: boolean; + } { let elt, i, len, s, sups, t let agenda = {} Object.assign(agenda, seeds) // make a copy @@ -701,7 +840,9 @@ export default class Formula extends Node { if (t == null) { return done } - sups = inverse ? this.each(void 0, predicate, this.fromNT(t)) : this.each(this.fromNT(t), predicate) + sups = inverse ? + this.each(void 0, predicate, this.fromNT(t)) + : this.each(this.fromNT(t) as TFPredicate, predicate) for (i = 0, len = sups.length; i < len; i++) { elt = sups[i] s = elt.toNT() @@ -722,8 +863,13 @@ export default class Formula extends Node { * Finds the types in the list which have no *stored* supertypes * We exclude the universal class, owl:Things and rdf:Resource, as it is * information-free. + * @param types - The types */ - topTypeURIs (types) { + topTypeURIs(types: { + [id: string]: string | NamedNode; + }): { + [id: string]: string | NamedNode; + } { let i let j let k @@ -783,7 +929,12 @@ export default class Formula extends Node { * @param o - The object * @param g - The graph that contains the statement */ - whether (s, p, o, g): number { + whether( + s?: TFSubject | null, + p?: TFPredicate | null, + o?: TFObject | null, + g?: TFGraph | null + ): number { return this.statementsMatching(s, p, o, g, false).length } } diff --git a/src/jsonldparser.js b/src/jsonldparser.js index 928414526..0a1818891 100644 --- a/src/jsonldparser.js +++ b/src/jsonldparser.js @@ -1,6 +1,6 @@ import jsonld from 'jsonld' -import { arrayToStatements } from './util' +import { arrayToStatements } from './utils' /** * Parses json-ld formatted JS objects to a rdf Term. diff --git a/src/literal.ts b/src/literal.ts index 5af121984..257e28e6c 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -1,27 +1,46 @@ -'use strict' import ClassOrder from './class-order' import NamedNode from './named-node' import Node from './node-internal' -import { TermType } from './types'; +import { + LiteralTermType, + TermType, + TFLiteral, + TFTerm, + ValueType +} from './types' +import { isTFLiteral } from './utils/terms' import XSD from './xsd-internal' /** * An RDF literal, containing some value which isn't expressed as an IRI. * @link https://rdf.js.org/data-model-spec/#literal-interface */ -export default class Literal extends Node { +// @ts-ignore Incorrectly extends due to fromValue() +export default class Literal extends Node implements TFLiteral { static termType = TermType.Literal classOrder = ClassOrder.Literal - datatype = XSD.string + + /** + * The literal's datatype as a named node + */ + datatype: NamedNode = XSD.string + isVar = 0 + /** * The language for the literal */ language: string = '' - termType = TermType.Literal + termType: LiteralTermType = TermType.Literal + /** + * Initializes a literal + * @param value - The literal's lexical value + * @param language - The language for the literal. Defaults to ''. + * @param datatype - The literal's datatype as a named node. Defaults to xsd:string. + */ constructor (value: string, language?: string | null, datatype?) { super(value) @@ -46,31 +65,31 @@ export default class Literal extends Node { * Gets whether two literals are the same * @param other The other statement */ - equals (other: any): boolean { + equals (other: TFTerm): boolean { if (!other) { return false } return (this.termType === other.termType) && (this.value === other.value) && - (this.language === other.language) && - ((!this.datatype && !other.datatype) || - (this.datatype && this.datatype.equals(other.datatype))) + (this.language === (other as Literal).language) && + ((!this.datatype && !(other as Literal).datatype) || + (this.datatype && this.datatype.equals((other as Literal).datatype))) } /** * The language for the literal * @deprecated use {language} instead */ - get lang () { + get lang (): string { return this.language } - set lang (language) { + set lang (language: string) { this.language = language || '' } - toNT() { + toNT(): string { return Literal.toNT(this) } @@ -103,7 +122,7 @@ export default class Literal extends Node { /** * Builds a literal node from a boolean value - * @param value {Boolean} The value + * @param value - The value */ static fromBoolean (value: boolean): Literal { let strValue = value ? '1' : '0' @@ -129,13 +148,13 @@ export default class Literal extends Node { /** * Builds a literal node from a number value - * @param value The value + * @param value - The value */ static fromNumber(value: number): Literal { if (typeof value !== 'number') { throw new TypeError('Invalid argument to Literal.fromNumber()') } - let datatype + let datatype: NamedNode const strValue = value.toString() if (strValue.indexOf('e') < 0 && Math.abs(value) <= Number.MAX_SAFE_INTEGER) { datatype = Number.isInteger(value) ? XSD.integer : XSD.decimal @@ -147,29 +166,26 @@ export default class Literal extends Node { /** * Builds a literal node from an input value - * @param value The input value + * @param value - The input value */ - static fromValue (value) { - if (typeof value === 'undefined' || value === null) { - return value - } - if (typeof value === 'object' && value.termType) { // this is a Node instance - return value + static fromValue(value: ValueType): T { + if (isTFLiteral(value)) { + return value as T } switch (typeof value) { case 'object': if (value instanceof Date) { - return Literal.fromDate(value) + return Literal.fromDate(value) as T } case 'boolean': - return Literal.fromBoolean(value) + return Literal.fromBoolean(value as boolean) as T case 'number': - return Literal.fromNumber(value) + return Literal.fromNumber(value as number) as T case 'string': - return new Literal(value) + return new Literal(value) as T } + throw new Error("Can't make literal from " + value + ' of type ' + typeof value) - } } diff --git a/src/n3parser.js b/src/n3parser.js index 56152a9a6..779d7c1c9 100644 --- a/src/n3parser.js +++ b/src/n3parser.js @@ -5,7 +5,7 @@ * **/ import * as Uri from './uri' -import { ArrayIndexOf } from './util' +import { ArrayIndexOf } from './utils' export default (function () { diff --git a/src/named-node.ts b/src/named-node.ts index d22ab5848..2b333c0b1 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -1,17 +1,17 @@ 'use strict' import ClassOrder from './class-order' import Node from './node-internal' -import { TermType } from './types'; -import { termValue } from "./utils/terms"; +import { NamedNodeTermType, TermType, TFNamedNode } from './types' +import { termValue } from './utils/terms' /** * A named (IRI) RDF node */ -export default class NamedNode extends Node { +export default class NamedNode extends Node implements TFNamedNode { static termType = TermType.NamedNode classOrder = ClassOrder.NamedNode - termType = TermType.NamedNode + termType: NamedNodeTermType = TermType.NamedNode /** * Create a named (IRI) RDF Node @@ -39,7 +39,7 @@ export default class NamedNode extends Node { * Returns an $rdf node for the containing directory, ending in slash. */ dir () { - var str = this.uri.split('#')[0] + var str = this.value.split('#')[0] var p = str.slice(0, -1).lastIndexOf('/') var q = str.indexOf('//') if ((q >= 0 && p < q + 2) || p < 0) return null @@ -51,7 +51,7 @@ export default class NamedNode extends Node { * Contrast with the "origin" which does NOT have a trailing slash */ site () { - var str = this.uri.split('#')[0] + var str = this.value.split('#')[0] var p = str.indexOf('//') if (p < 0) throw new Error('This URI does not have a web site part (origin)') var q = str.indexOf('/', p+2) @@ -67,10 +67,10 @@ export default class NamedNode extends Node { * Removes everything from the # anchor tag. */ doc () { - if (this.uri.indexOf('#') < 0) { + if (this.value.indexOf('#') < 0) { return this } else { - return new NamedNode(this.uri.split('#')[0]) + return new NamedNode(this.value.split('#')[0]) } } @@ -78,12 +78,12 @@ export default class NamedNode extends Node { * Returns the URI including */ toString () { - return '<' + this.uri + '>' + return '<' + this.value + '>' } /** The local identifier with the document */ id () { - return this.uri.split('#')[1] + return this.value.split('#')[1] } /** diff --git a/src/namespace.ts b/src/namespace.ts index 814b3c225..63066df4b 100644 --- a/src/namespace.ts +++ b/src/namespace.ts @@ -1,14 +1,15 @@ import NamedNode from './named-node' +import { TFDataFactory, TFNamedNode } from './types' /** * Gets a namespace for the specified namespace's URI * @param nsuri - The URI for the namespace * @param [factory] - The factory for creating named nodes with */ -export default function Namespace (nsuri: string, factory?) { - const dataFactory = factory || { namedNode: (value) => new NamedNode(value) } +export default function Namespace (nsuri: string, factory?: TFDataFactory): (ln: string) => TFNamedNode { + const dataFactory = factory || { namedNode: (value) => new NamedNode(value) as TFNamedNode } - return function (ln: string) { + return function (ln: string): TFNamedNode { return dataFactory.namedNode(nsuri + (ln || '')) } } diff --git a/src/node-internal.ts b/src/node-internal.ts index 208409683..b3ffda72f 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -1,3 +1,5 @@ +import { ValueType, Bindings, TFTerm, FromValueReturns } from './types' + /** * The superclass of all RDF Statement objects, that is * NamedNode, Literal, BlankNode, etc. @@ -6,23 +8,23 @@ * @link https://rdf.js.org/data-model-spec/#term-interface * @class Node */ -export default class Node { - static fromValue: (value: any) => T; +export default abstract class Node { + // Specified in './node.ts' to prevent circular dependency + static fromValue: (value: ValueType) => T + // Specified in './node.ts' to prevent circular dependency static toJS: (term: any) => Date | Number | string | boolean | object | Array; - /** - * The type of node + * The nodes in this collection */ + elements!: Node[]; + + /** The type of node */ termType!: string; - /** - * The class order for this node - */ + /** The class order for this node */ classOrder!: number; - /** - * The node's value - */ + /** The node's value */ value: string; constructor(value: string) { @@ -33,16 +35,16 @@ export default class Node { * Creates the substituted node for this one, according to the specified bindings * @param bindings - Bindings of identifiers to nodes */ - substitute (bindings): Node | any { + substitute (bindings: Bindings): T { console.log('@@@ node substitute' + this) - return this + return this as unknown as T } /** * Compares this node with another * @param other - The other node */ - compareTerm (other): number { + compareTerm (other: Node): number { if (this.classOrder < other.classOrder) { return -1 } @@ -62,7 +64,7 @@ export default class Node { * Compares whether the two nodes are equal * @param other The other node */ - equals (other): boolean { + equals (other: TFTerm): boolean { if (!other) { return false } @@ -82,8 +84,9 @@ export default class Node { * Compares whether this node is the same as the other one * @param other - Another node */ - sameTerm (other): boolean { + sameTerm(other: Node): boolean { return this.equals(other) + } /** diff --git a/src/node.ts b/src/node.ts index 55c6b26b4..9d360c0ba 100644 --- a/src/node.ts +++ b/src/node.ts @@ -1,11 +1,10 @@ -'use strict' - // This file attaches all functionality to Node // that would otherwise require circular dependencies. +import { fromValue } from './collection' import Node from './node-internal' -import Collection, { fromValue } from "./collection"; - -export default Node +import { TFTerm } from './types' +import Namespace from './namespace' +import { isCollection, isTFLiteral } from './utils/terms' /** * Creates an RDF Node from a native javascript value. @@ -15,18 +14,23 @@ export default Node * @param value {Node|Date|String|Number|Boolean|Undefined} * @return {Node|Collection} */ -Node.fromValue = fromValue +Node.fromValue = fromValue; + +export default Node -import Namespace from './namespace' const ns = { xsd: Namespace('http://www.w3.org/2001/XMLSchema#') } -Node.toJS = function toJS (term) { - if (term.elements) { +/** + * Gets the javascript object equivalent to a node + * @param term The RDF node + */ +Node.toJS = function (term: TFTerm): TFTerm | boolean | number | Date | string | any[] { + if (isCollection(term)) { return term.elements.map(Node.toJS) // Array node (not standard RDFJS) } - if (!term.datatype) return term // Objects remain objects + if (!isTFLiteral(term)) return term if (term.datatype.equals(ns.xsd('boolean'))) { - return term.value === '1' + return term.value === '1' || term.value === 'true' } if (term.datatype.equals(ns.xsd('dateTime')) || term.datatype.equals(ns.xsd('date'))) { diff --git a/src/parse.js b/src/parse.ts similarity index 50% rename from src/parse.js rename to src/parse.ts index ae6c47012..db22fba15 100644 --- a/src/parse.js +++ b/src/parse.ts @@ -1,45 +1,63 @@ import DataFactory from './data-factory' import jsonldParser from './jsonldparser' +// @ts-ignore is this injected? import { Parser as N3jsParser } from 'n3' // @@ Goal: remove this dependency import N3Parser from './n3parser' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import sparqlUpdateParser from './patch-parser' import * as Util from './util' +import Formula from './formula' +import { TFQuad, ContentType } from './types' + +type CallbackFunc = (error: any, kb: Formula | null) => void /** * Parse a string and put the result into the graph kb. * Normal method is sync. * Unfortunately jsdonld is currently written to need to be called async. * Hence the mess below with executeCallback. + * @param str - The input string to parse + * @param kb - The store to use + * @param base - The base URI to use + * @param contentType - The MIME content type string for the input + * @param callback - The callback to call when the data has been loaded */ -export default function parse (str, kb, base, contentType, callback) { - contentType = contentType || 'text/turtle' - contentType = contentType.split(';')[0] +export default function parse ( + str: string, + kb: Formula, + base: string, + contentType: string | ContentType, + callback?: CallbackFunc +) { + contentType = contentType || ContentType.turtle + contentType = contentType.split(';')[0] as ContentType try { - if (contentType === 'text/n3' || contentType === 'text/turtle') { + if (contentType === ContentType.n3 || contentType === ContentType.turtle) { var p = N3Parser(kb, kb, base, base, null, null, '', null) p.loadBuf(str) executeCallback() - } else if (contentType === 'application/rdf+xml') { + } else if (contentType === ContentType.rdfxml) { var parser = new RDFParser(kb) parser.parse(Util.parseXML(str), base, kb.sym(base)) executeCallback() - } else if (contentType === 'application/xhtml+xml') { - parseRDFaDOM(Util.parseXML(str, {contentType: 'application/xhtml+xml'}), kb, base) + } else if (contentType === ContentType.xhtml) { + parseRDFaDOM(Util.parseXML(str, {contentType: ContentType.xhtml}), kb, base) executeCallback() - } else if (contentType === 'text/html') { - parseRDFaDOM(Util.parseXML(str, {contentType: 'text/html'}), kb, base) + } else if (contentType === ContentType.html) { + parseRDFaDOM(Util.parseXML(str, {contentType: ContentType.html}), kb, base) executeCallback() - } else if (contentType === 'application/sparql-update') { // @@ we handle a subset + } else if (contentType === ContentType.sparqlupdate) { // @@ we handle a subset sparqlUpdateParser(str, kb, base) executeCallback() - } else if (contentType === 'application/ld+json') { + } else if (contentType === ContentType.jsonld) { jsonldParser(str, kb, base, executeCallback) - } else if (contentType === 'application/nquads' || - contentType === 'application/n-quads') { + } else if (contentType === ContentType.nQuads || + contentType === ContentType.nQuadsAlt) { var n3Parser = new N3jsParser({ factory: DataFactory }) nquadCallback(null, str) + } else if (contentType === undefined) { + throw new Error("contentType is undefined") } else { throw new Error("Don't know how to parse " + contentType + ' yet') } @@ -47,7 +65,7 @@ export default function parse (str, kb, base, contentType, callback) { executeErrorCallback(e) } - parse.handled = { + (parse as any).handled= { 'text/n3': true, 'text/turtle': true, 'application/rdf+xml': true, @@ -67,14 +85,20 @@ export default function parse (str, kb, base, contentType, callback) { } } - function executeErrorCallback (e) { - if (contentType !== 'application/ld+json' || - contentType !== 'application/nquads' || - contentType !== 'application/n-quads') { + function executeErrorCallback (e: Error): void { + if ( + // TODO: Always true, what is the right behavior + contentType !== ContentType.jsonld || + // @ts-ignore always true? + contentType !== ContentType.nQuads || + // @ts-ignore always true? + contentType !== ContentType.nQuadsAlt + ) { if (callback) { callback(e, kb) } else { let e2 = new Error('' + e + ' while trying to parse <' + base + '> as ' + contentType) + //@ts-ignore .cause is not a default error property e2.cause = e throw e2 } @@ -91,22 +115,22 @@ export default function parse (str, kb, base, contentType, callback) { doc['@context']['@base'] = base } */ - function nquadCallback (err, nquads) { + function nquadCallback (err?: Error | null, nquads?: string): void { if (err) { - callback(err, kb) + (callback as CallbackFunc)(err, kb) } try { n3Parser.parse(nquads, tripleCallback) } catch (err) { - callback(err, kb) + (callback as CallbackFunc)(err, kb) } } - function tripleCallback (err, triple, prefixes) { + function tripleCallback (err: Error, triple: TFQuad) { if (triple) { - kb.add(triple) + kb.add(triple.subject, triple.predicate, triple.object, triple.graph) } else { - callback(err, kb) + (callback as CallbackFunc)(err, kb) } } } diff --git a/src/rdfaparser.js b/src/rdfaparser.js index ef3e4763b..5283f2055 100644 --- a/src/rdfaparser.js +++ b/src/rdfaparser.js @@ -14,10 +14,10 @@ import BlankNode from './blank-node' import Literal from './literal' -import rdf from './data-factory' import NamedNode from './named-node' import * as Uri from './uri' import * as Util from './util' +import rdf from './data-factory-internal' if (typeof Node === 'undefined') { // @@@@@@ Global. Interface to xmldom. var Node = { diff --git a/src/serialize.js b/src/serialize.ts similarity index 54% rename from src/serialize.js rename to src/serialize.ts index ea48d8879..b380b8c7b 100644 --- a/src/serialize.js +++ b/src/serialize.ts @@ -1,54 +1,74 @@ import * as convert from './convert' import Serializer from './serializer' +import { ContentType, TFNamedNode, TFBlankNode } from './types' +import IndexedFormula from './store' +import { Formula } from './index' /** * Serialize to the appropriate format - * @@ Currently NQuads and JSON/LD are deal with extrelemently inefficiently - * through mutiple conversions. */ -export default function serialize (target, kb, base, contentType, callback, options) { - base = base || target.uri - options = options || {} - contentType = contentType || 'text/turtle' // text/n3 if complex? - var documentString = null +export default function serialize ( + /** The graph or nodes that should be serialized */ + target: Formula | TFNamedNode | TFBlankNode, + /** The store */ + kb?: IndexedFormula, + base?: unknown, + /** + * The mime type. + * Defaults to Turtle. + */ + contentType?: string | ContentType, + callback?: (err?: Error | null, result?: string ) => any, + options?: { + /** + * A string of letters, each of which set an options + * e.g. `deinprstux` + */ + flags: string + } +): string | undefined { + base = base || target.value + const opts = options || {} + contentType = contentType || ContentType.turtle // text/n3 if complex? + var documentString: string | null = null try { var sz = Serializer(kb) - if (options.flags) sz.setFlags(options.flags) - var newSts = kb.statementsMatching(undefined, undefined, undefined, target) - var n3String - sz.suggestNamespaces(kb.namespaces) + if ((opts as any).flags) sz.setFlags((opts as any).flags) + var newSts = kb!.statementsMatching(undefined, undefined, undefined, target as TFNamedNode) + var n3String: string + sz.suggestNamespaces(kb!.namespaces) sz.setBase(base) switch (contentType) { - case 'application/rdf+xml': + case ContentType.rdfxml: documentString = sz.statementsToXML(newSts) return executeCallback(null, documentString) - case 'text/n3': - case 'application/n3': // Legacy + case ContentType.n3: + case ContentType.n3Legacy: documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case 'text/turtle': - case 'application/x-turtle': // Legacy + case ContentType.turtle: + case ContentType.turtleLegacy: sz.setFlags('si') // Suppress = for sameAs and => for implies documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case 'application/n-triples': + case ContentType.nTriples: sz.setFlags('deinprstux') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) return executeCallback(null, documentString) - case 'application/ld+json': + case ContentType.jsonld: sz.setFlags('deinprstux') // Use adapters to connect to incmpatible parser n3String = sz.statementsToNTriples(newSts) // n3String = sz.statementsToN3(newSts) convert.convertToJson(n3String, callback) break - case 'application/n-quads': - case 'application/nquads': // @@@ just outpout the quads? Does not work for collections + case ContentType.nQuads: + case ContentType.nQuadsAlt: // @@@ just outpout the quads? Does not work for collections sz.setFlags('deinprstux q') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) // q in flag means actually quads return executeCallback(null, documentString) // n3String = sz.statementsToN3(newSts) // documentString = convert.convertToNQuads(n3String, callback) - break + // break default: throw new Error('Serialize: Content-type ' + contentType + ' not supported for data write.') } @@ -59,7 +79,7 @@ export default function serialize (target, kb, base, contentType, callback, opti throw err // Don't hide problems from caller in sync mode } - function executeCallback (err, result) { + function executeCallback (err?: Error | null, result?: string) { if (callback) { callback(err, result) return diff --git a/src/statement.ts b/src/statement.ts index 01a04420d..67c7c33d5 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -1,29 +1,33 @@ -import BlankNode from './blank-node' -import Collection from './collection' -import DefaultGraph from './default-graph' -import Empty from './empty' +import { defaultGraph } from './data-factory-internal' import NamedNode from './named-node' import Node from './node-internal' -import IndexedFormula from './store' -import Variable from './variable' +import { + Bindings, + GraphType, + ObjectType, + PredicateType, + SubjectType, + TermType, + TFQuad, +} from './types' +import Literal from './literal' /** A Statement represents an RDF Triple or Quad. */ -export default class Statement { +export default class Statement implements TFQuad { /** The subject of the triple. What the Statement is about. */ - subject: NamedNode | BlankNode | Variable + subject: SubjectType /** The relationship which is asserted between the subject and object */ - predicate: NamedNode | Variable + predicate: PredicateType /** The thing or data value which is asserted to be related to the subject */ - object: NamedNode | BlankNode | Collection | Empty | Variable + object: ObjectType /** * The graph param is a named node of the document in which the triple when * it is stored on the web. */ - graph: NamedNode | DefaultGraph | IndexedFormula - + graph: GraphType /** * Construct a new statement @@ -42,11 +46,16 @@ export default class Statement { * and give the document you are patching. In future, we may have a more * powerful update() which can update more than one document. */ - constructor (subject, predicate, object, graph) { + constructor ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType, + ) { this.subject = Node.fromValue(subject) this.predicate = Node.fromValue(predicate) this.object = Node.fromValue(object) - this.graph = graph // property currently used by rdflib + this.graph = graph == undefined ? defaultGraph() : graph // property currently used by rdflib } /** @deprecated use {graph} instead */ @@ -62,21 +71,26 @@ export default class Statement { * Checks whether two statements are the same * @param other - The other statement */ - equals (other): boolean { - return other.subject.equals(this.subject) && other.predicate.equals(this.predicate) && - other.object.equals(this.object) && other.graph.equals(this.graph) + equals (other: TFQuad): boolean { + return ( + other.subject.equals(this.subject) && + other.predicate.equals(this.predicate) && + other.object.equals(this.object as Literal) && + other.graph.equals(this.graph) + ) } /** * Creates a statement with the bindings substituted - * @param bindings - The bindings + * @param bindings The bindings */ - substitute (bindings): Statement { + substitute (bindings: Bindings): Statement { const y = new Statement( this.subject.substitute(bindings), this.predicate.substitute(bindings), this.object.substitute(bindings), - this.graph.substitute(bindings)) // 2016 + this.graph.substitute(bindings), + ) // 2016 console.log('@@@ statement substitute:' + y) return y } @@ -88,7 +102,7 @@ export default class Statement { this.predicate.toCanonical(), this.object.toCanonical() ] - if (this.graph && this.graph.termType !== 'DefaultGraph') { + if (this.graph && this.graph.termType !== TermType.DefaultGraph) { terms.push(this.graph.toCanonical()) } return terms.join(' ') + ' .' diff --git a/src/store.js b/src/store.ts similarity index 58% rename from src/store.js rename to src/store.ts index 88a01ce06..ef400695b 100644 --- a/src/store.js +++ b/src/store.ts @@ -10,6 +10,7 @@ * * 2005-10 Written Tim Berners-Lee * 2007 Changed so as not to munge statements from documents when smushing + * 2019 Converted to typescript * * */ @@ -18,22 +19,42 @@ import ClassOrder from './class-order' import { defaultGraphURI } from './data-factory-internal' -import DataFactory from './data-factory' -import Formula from './formula' -import { ArrayIndexOf, RDFArrayRemove } from './util' -import { isStatement, isStore } from './utils/terms' -import Statement from './statement' +import Formula, { FormulaOpts } from './formula' +import { ArrayIndexOf } from './utils' +import { RDFArrayRemove } from './util' +import { + isRDFObject, + isStore, + isTFGraph, + isTFPredicate, + isTFStatement, + isTFSubject +} from './utils/terms' import Node from './node' import Variable from './variable' import { Query, indexedFormulaQuery } from './query' +import UpdateManager from './update-manager' +import { Bindings, TFTerm, TFPredicate, TFSubject, TFObject, TFGraph, TFQuad, TFNamedNode, TFBlankNode } from './types' +import Statement from './statement' +import { Indexable } from './data-factory-type' +import NamedNode from './named-node' +import Collection from './collection' +import Fetcher from './fetcher' const owlNamespaceURI = 'http://www.w3.org/2002/07/owl#' +type FeaturesType = Array<('sameAs' | 'InverseFunctionalProperty' | 'FunctionalProperty')> | undefined + export { defaultGraphURI } // var link_ns = 'http://www.w3.org/2007/ont/link#' // Handle Functional Property -function handleFP (formula, subj, pred, obj) { +function handleFP ( + formula: IndexedFormula, + subj: TFSubject, + pred: TFPredicate, + obj: TFObject +): boolean { var o1 = formula.any(subj, pred, undefined) if (!o1) { return false // First time with this value @@ -44,7 +65,12 @@ function handleFP (formula, subj, pred, obj) { } // handleFP // Handle Inverse Functional Property -function handleIFP (formula, subj, pred, obj) { +function handleIFP ( + formula: IndexedFormula, + subj: TFSubject, + pred: TFPredicate, + obj: TFObject +): boolean { var s1 = formula.any(undefined, pred, obj) if (!s1) { return false // First time with this value @@ -54,9 +80,16 @@ function handleIFP (formula, subj, pred, obj) { return true } // handleIFP -function handleRDFType (formula, subj, pred, obj, why) { +function handleRDFType ( + formula: IndexedFormula, + subj: TFSubject, + pred: TFPredicate, + obj: TFObject, + why: TFGraph +) { + //@ts-ignore this method does not seem to exist in this library if (formula.typeCallback) { - formula.typeCallback(formula, obj, why) + (formula as any).typeCallback(formula, obj, why) } var x = formula.classActions[formula.id(obj)] @@ -68,32 +101,76 @@ function handleRDFType (formula, subj, pred, obj, why) { } return done // statement given is not needed if true } + /** * Indexed Formula aka Store */ export default class IndexedFormula extends Formula { // IN future - allow pass array of statements to constructor /** - * @constructor - * @param {Array} features - What sort of autmatic processing to do? Array of string - * @param {Boolean} features.sameAs - Smush together A and B nodes whenever { A sameAs B } + * An UpdateManager initialised to this store + */ + updater?: UpdateManager + + /** + * Dictionary of namespace prefixes + */ + namespaces: {[key: string]: string} + + /** Map of iri predicates to functions to call when adding { s type X } */ + classActions: { [k: string]: Function[] } + /** Map of iri predicates to functions to call when getting statement with {s X o} */ + propertyActions: { [k: string]: Function[] } + /** Redirect to lexically smaller equivalent symbol */ + redirections: any[] + /** Reverse mapping to redirection: aliases for this */ + aliases: any[] + /** Redirections we got from HTTP */ + HTTPRedirects: TFQuad[] + /** Array of statements with this X as subject */ + subjectIndex: TFQuad[] + /** Array of statements with this X as predicate */ + predicateIndex: TFQuad[] + /** Array of statements with this X as object */ + objectIndex: TFQuad[] + /** Array of statements with X as provenance */ + whyIndex: TFQuad[] + index: [ + TFQuad[], + TFQuad[], + TFQuad[], + TFQuad[] + ] + features: FeaturesType + static handleRDFType: Function + _universalVariables?: TFNamedNode[] + _existentialVariables?: TFBlankNode[] + + /** Function to remove quads from the store arrays with */ + private rdfArrayRemove: (arr: TFQuad[], q: TFQuad) => void + /** Callbacks which are triggered after a statement has been added to the store */ + private dataCallbacks?: Array<(q: TFQuad) => void> + + /** + * Creates a new formula + * @param features - What sort of autmatic processing to do? Array of string + * @param features.sameAs - Smush together A and B nodes whenever { A sameAs B } * @param opts - * @param {DataFactory} [opts.rdfFactory] - The data factory that should be used by the store - * @param {DataFactory} [opts.rdfArrayRemove] - Function which removes statements from the store - * @param {DataFactory} [opts.dataCallback] - Callback when a statement is added to the store, will not trigger when adding duplicates + * @param [opts.rdfFactory] - The data factory that should be used by the store + * @param [opts.rdfArrayRemove] - Function which removes statements from the store + * @param [opts.dataCallback] - Callback when a statement is added to the store, will not trigger when adding duplicates */ - constructor (features, opts = {}) { + constructor (features?: FeaturesType, opts: FormulaOpts = {}) { super(undefined, undefined, undefined, undefined, opts) - this.propertyActions = [] // Array of functions to call when getting statement with {s X o} - // maps to [f(F,s,p,o),...] - this.classActions = [] // Array of functions to call when adding { s type X } - this.redirections = [] // redirect to lexically smaller equivalent symbol - this.aliases = [] // reverse mapping to redirection: aliases for this - this.HTTPRedirects = [] // redirections we got from HTTP - this.subjectIndex = [] // Array of statements with this X as subject - this.predicateIndex = [] // Array of statements with this X as subject - this.objectIndex = [] // Array of statements with this X as object - this.whyIndex = [] // Array of statements with X as provenance + this.propertyActions = {} + this.classActions = {} + this.redirections = [] + this.aliases = [] + this.HTTPRedirects = [] + this.subjectIndex = [] + this.predicateIndex = [] + this.objectIndex = [] + this.whyIndex = [] this.index = [ this.subjectIndex, this.predicateIndex, @@ -114,41 +191,68 @@ export default class IndexedFormula extends Formula { // IN future - allow pass this.initPropertyActions(this.features) } - static get defaultGraphURI () { + /** + * Gets the URI of the default graph + */ + static get defaultGraphURI(): string { return defaultGraphURI } - substitute (bindings) { - var statementsCopy = this.statements.map(function (ea) { - return ea.substitute(bindings) + /** + * Gets this graph with the bindings substituted + * @param bindings The bindings + */ + //@ts-ignore different from signature in Formula + substitute(bindings: Bindings): IndexedFormula { + var statementsCopy = this.statements.map(function (ea: TFQuad) { + return (ea as Statement).substitute(bindings) }) var y = new IndexedFormula() y.add(statementsCopy) return y } - addDataCallback(cb) { + /** + * Add a callback which will be triggered after a statement has been added to the store. + * @param cb + */ + addDataCallback(cb: (q: TFQuad) => void): void { if (!this.dataCallbacks) { this.dataCallbacks = [] } this.dataCallbacks.push(cb) } - applyPatch (patch, target, patchCallback) { // patchCallback(err) + /** + * Apply a set of statements to be deleted and to be inserted + * + * @param patch - The set of statements to be deleted and to be inserted + * @param target - The name of the document to patch + * @param patchCallback - Callback to be called when patching is complete + */ + applyPatch( + patch: { + delete?: ReadonlyArray, + patch?: ReadonlyArray, + where?: any + }, + target: TFNamedNode, + patchCallback: (errorString: string) => void + ): void { var targetKB = this var ds - var binding = null + var binding: Bindings | null = null - function doPatch (onDonePatch) { + function doPatch (onDonePatch: Function) { if (patch['delete']) { ds = patch['delete'] // console.log(bindingDebug(binding)) // console.log('ds before substitute: ' + ds) if (binding) ds = ds.substitute(binding) // console.log('applyPatch: delete: ' + ds) - ds = ds.statements - var bad = [] - var ds2 = ds.map(function (st) { // Find the actual statemnts in the store + ds = ds.statements as Statement[] + var bad: TFQuad[] = [] + var ds2 = ds.map(function (st: TFQuad) { // Find the actual statemnts in the store var sts = targetKB.statementsMatching(st.subject, st.predicate, st.object, target) if (sts.length === 0) { // log.info("NOT FOUND deletable " + st) @@ -164,7 +268,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // console.log('despite ' + targetKB.statementsMatching(bad[0].subject, bad[0].predicate)[0]) return patchCallback('Could not find to delete: ' + bad.join('\n or ')) } - ds2.map(function (st) { + ds2.map(function (st: TFQuad) { targetKB.remove(st) }) } @@ -173,9 +277,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass ds = patch['insert'] if (binding) ds = ds.substitute(binding) ds = ds.statements - ds.map(function (st) { - st.why = target - targetKB.add(st.subject, st.predicate, st.object, st.why) + ds.map(function (st: TFQuad) { + st.graph = target + targetKB.add(st.subject, st.predicate, st.object, st.graph) }) } onDonePatch() @@ -185,13 +289,16 @@ export default class IndexedFormula extends Formula { // IN future - allow pass var query = new Query('patch') query.pat = patch.where query.pat.statements.map(function (st) { - st.why = target + st.graph = target }) + //@ts-ignore TODO: add sync property to Query when converting Query to typescript query.sync = true - var bindingsFound = [] + var bindingsFound: Bindings[] = [] - targetKB.query(query, function onBinding (binding) { + targetKB.query( + query, + function onBinding (binding) { bindingsFound.push(binding) // console.log(' got a binding: ' + bindingDebug(binding)) }, @@ -211,13 +318,21 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } } - declareExistential (x) { + /** + * N3 allows for declaring blank nodes, this function enables that support + * + * @param x The blank node to be declared, supported in N3 + */ + declareExistential(x: TFBlankNode): TFBlankNode { if (!this._existentialVariables) this._existentialVariables = [] this._existentialVariables.push(x) return x } - initPropertyActions (features) { + /** + * @param features + */ + initPropertyActions(features: FeaturesType) { // If the predicate is #type, use handleRDFType to create a typeCallback on the object this.propertyActions[this.rdfFactory.id(this.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'))] = [ handleRDFType ] @@ -250,44 +365,68 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** @deprecated Use {add} instead */ - addStatement (st) { - return this.add(st.subject, st.predicate, st.object, st.graph) + addStatement (st: TFQuad): number { + this.add(st.subject, st.predicate, st.object, st.graph) + return this.statements.length } /** * Adds a triple (quad) to the store. * - * @param {Term} subject - The thing about which the fact a relationship is asserted - * @param {namedNode} predicate - The relationship which is asserted - * @param {Term} object - The object of the relationship, e.g. another thing or avalue - * @param {namedNode} why - The document in which the triple (S,P,O) was or will be stored on the web - * @returns {Statement} The statement added to the store + * @param subj - The thing about which the fact a relationship is asserted. + * Also accepts a statement or an array of Statements. + * @param pred - The relationship which is asserted + * @param obj - The object of the relationship, e.g. another thing or avalue + * @param why - The document in which the triple (S,P,O) was or will be stored on the web + * @returns The statement added to the store, or the store */ - add (subj, pred, obj, why) { - var i + // @ts-ignore differs from signature in Formula + add ( + subj: TFSubject | TFQuad | TFQuad[] | Statement | Statement[], + pred?: TFPredicate, + obj?: TFObject | Collection, + why?: TFGraph + ): TFQuad | null | IndexedFormula { + var i: number if (arguments.length === 1) { if (subj instanceof Array) { for (i = 0; i < subj.length; i++) { this.add(subj[i]) } - } else if (isStatement(subj)) { - this.add(subj.subject, subj.predicate, subj.object, subj.why) + } else if (isTFStatement(subj)) { + this.add(subj.subject, subj.predicate, subj.object, subj.graph) } else if (isStore(subj)) { this.add(subj.statements) } return this } - var actions - var st + var actions: Function[] + var st: TFQuad if (!why) { // system generated - why = this.fetcher ? this.fetcher.appNode : this.defaultGraph() + why = this.fetcher ? this.fetcher.appNode : this.rdfFactory.defaultGraph() + } + if (typeof subj == 'string') { + subj = this.rdfFactory.namedNode(subj) } - subj = Node.fromValue(subj) pred = Node.fromValue(pred) obj = Node.fromValue(obj) why = Node.fromValue(why) + if (!isTFSubject(subj)) { + throw new Error('Subject is not a subject type') + } + if (!isTFPredicate(pred)) { + throw new Error(`Predicate ${pred} is not a predicate type`) + } + if (!isRDFObject(obj)) { + throw new Error(`Object ${obj} is not an object type`) + } + if (!isTFGraph(why)) { + throw new Error("Why is not a graph type") + } + //@ts-ignore This is not used internally if (this.predicateCallback) { + //@ts-ignore This is not used internally this.predicateCallback(this, pred, why) } // Action return true if the statement does not need to be added @@ -309,11 +448,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // Don't put it in the store // still return this statement for owl:sameAs input var hash = [ - this.id(this.canon(subj)), + this.id(this.canon(subj as TFSubject)), predHash, - this.id(this.canon(obj)), - this.id(this.canon(why)) + this.id(this.canon(obj as TFObject)), + this.id(this.canon(why as TFGraph)) ] + // @ts-ignore this will fail if you pass a collection and the factory does not allow Collections st = this.rdfFactory.quad(subj, pred, obj, why) for (i = 0; i < 4; i++) { var ix = this.index[i] @@ -338,8 +478,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Returns the symbol with canonical URI as smushed + * @param term - An RDF node */ - canon (term) { + canon(term: TFTerm): TFTerm { if (!term) { return term } @@ -350,12 +491,17 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return y } - check () { + + /** + * Checks this formula for consistency + */ + check(): void { this.checkStatementList(this.statements) for (var p = 0; p < 4; p++) { var ix = this.index[p] for (var key in ix) { if (ix.hasOwnProperty(key)) { + // @ts-ignore should this pass an array or a single statement? checkStateMentsList expects an array. this.checkStatementList(ix[key], p) } } @@ -363,28 +509,35 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** - * Self-consistency checking for diagnostis only - * Is each statement properly indexed? + * Checks a list of statements for consistency + * @param sts - The list of statements to check + * @param from - An index with the array ['subject', 'predicate', 'object', 'why'] */ - checkStatementList (sts, from) { + checkStatementList( + sts: ReadonlyArray, + from?: number + ): boolean | void { + if (from === undefined) { + from = 0 + } var names = ['subject', 'predicate', 'object', 'why'] var origin = ' found in ' + names[from] + ' index.' - var st + var st: TFQuad for (var j = 0; j < sts.length; j++) { st = sts[j] - var term = [ st.subject, st.predicate, st.object, st.why ] - var arrayContains = function (a, x) { + var term = [ st.subject, st.predicate, st.object, st.graph ] + var arrayContains = function (a: Array, x: TFQuad) { for (var i = 0; i < a.length; i++) { if (a[i].subject.equals(x.subject) && a[i].predicate.equals(x.predicate) && a[i].object.equals(x.object) && - a[i].why.equals(x.why)) { + a[i].why.equals(x.graph)) { return true } } } for (var p = 0; p < 4; p++) { - var c = this.canon(term[p]) + var c = this.canon(term[p]) as TFNamedNode var h = this.id(c) if (!this.index[p][h]) { // throw new Error('No ' + name[p] + ' index for statement ' + st + '@' + st.why + origin) @@ -395,21 +548,24 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } } if (!arrayContains(this.statements, st)) { - throw new Error('Statement list does not statement ' + st + '@' + st.why + origin) + throw new Error('Statement list does not statement ' + st + '@' + st.graph + origin) } } } - close () { + /** + * Closes this formula (and return it) + */ + close(): IndexedFormula { return this } - compareTerm(u1, u2) { + // @ts-ignore incompatible with Forumala.compareTerm + compareTerm(u1: TFTerm, u2: TFTerm): number { // Keep compatibility with downstream classOrder changes if (Object.prototype.hasOwnProperty.call(u1, "compareTerm")) { - return u1.compareTerm(u2) + return (u1 as Node).compareTerm(u2 as Node) } - if (ClassOrder[u1.termType] < ClassOrder[u2.termType]) { return -1 } @@ -425,13 +581,19 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return 0 } + /** - * replaces @template with @target and add appropriate triples (no triple - * removed) - * one-direction replication - * @method copyTo + * replaces @template with @target and add appropriate triples + * removes no triples by default and is a one-direction replication + * @param template node to copy + * @param target node to copy to + * @param flags Whether or not to do a two-directional copy and/or delete triples */ - copyTo (template, target, flags) { + copyTo( + template: TFSubject, + target: TFSubject, + flags?: Array<('two-direction' | 'delete')> + ): void { if (!flags) flags = [] var statList = this.statementsMatching(template) if (ArrayIndexOf(flags, 'two-direction') !== -1) { @@ -445,7 +607,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass break case 'Literal': case 'BlankNode': + // @ts-ignore Collections can appear here case 'Collection': + // @ts-ignore Possible bug: copy is not available on Collections this.add(target, st.predicate, st.object.copy(this)) } if (ArrayIndexOf(flags, 'delete') !== -1) { @@ -455,15 +619,17 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** - * simplify graph in store when we realize two identifiers are equivalent + * Simplify graph in store when we realize two identifiers are equivalent * We replace the bigger with the smaller. + * @param u1in The first node + * @param u2in The second node */ - equate (u1, u2) { + equate(u1in: TFTerm, u2in : TFTerm): boolean { // log.warn("Equating "+u1+" and "+u2); // @@ // @@JAMBO Must canonicalize the uris to prevent errors from a=b=c // 03-21-2010 - u1 = this.canon(u1) - u2 = this.canon(u2) + const u1 = this.canon(u1in) as TFSubject + const u2 = this.canon(u2in) as TFSubject var d = this.compareTerm(u1, u2) if (!d) { return true // No information in {a = a} @@ -477,7 +643,13 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } } - formula (features) { + /** + * Creates a new empty indexed formula + * Only applicable for IndexedFormula, but TypeScript won't allow a subclass to override a property + * @param features The list of features + */ + //@ts-ignore Incompatible signature with Formula.formula + formula(features: FeaturesType): IndexedFormula { return new IndexedFormula(features) } @@ -491,7 +663,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * ``` * @returns {Number} */ - get length () { + get length (): number { return this.statements.length } @@ -499,13 +671,17 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Returns any quads matching the given arguments. * Standard RDFJS Taskforce method for Source objects, implemented as an * alias to `statementsMatching()` - * @method match - * @param subject {Node|String|Object} - * @param predicate {Node|String|Object} - * @param object {Node|String|Object} - * @param graph {NamedNode|String} + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The graph that contains the statement */ - match (subject, predicate, object, graph) { + match( + subject?: TFSubject | null, + predicate?: TFPredicate | null, + object?: TFObject | null, + graph?: TFGraph | null + ): TFQuad[] { return this.statementsMatching( Node.fromValue(subject), Node.fromValue(predicate), @@ -516,22 +692,40 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Find out whether a given URI is used as symbol in the formula + * @param uri The URI to look for */ - mentionsURI (uri) { + mentionsURI(uri: string): boolean { var hash = '<' + uri + '>' return (!!this.subjectIndex[hash] || !!this.objectIndex[hash] || !!this.predicateIndex[hash]) } - // Existentials are BNodes - something exists without naming - newExistential (uri) { + /** + * Existentials are BNodes - something exists without naming + * @param uri An URI + */ + newExistential(uri: string): TFTerm { if (!uri) return this.bnode() var x = this.sym(uri) + // @ts-ignore x should be blanknode, but is namedNode. return this.declareExistential(x) } - newPropertyAction (pred, action) { + /** + * Adds a new property action + * @param pred the predicate that the function should be triggered on + * @param action the function that should trigger + */ + newPropertyAction( + pred: TFPredicate, + action: ( + store: IndexedFormula, + subject: TFSubject, + predicate: TFPredicate, + object: TFObject + ) => boolean + ): boolean { // log.debug("newPropertyAction: "+pred) var hash = this.id(pred) if (!this.propertyActions[hash]) { @@ -547,8 +741,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return done } - // Universals are Variables - newUniversal (uri) { + /** + * Creates a new universal node + * Universals are Variables + * @param uri An URI + */ + newUniversal(uri: string): TFNamedNode { var x = this.sym(uri) if (!this._universalVariables) this._universalVariables = [] this._universalVariables.push(x) @@ -556,39 +754,55 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } // convenience function used by N3 parser - variable (name) { + // @ts-ignore does not correctly extends from Formula + variable (name: string) { return new Variable(name) } /** * Find an unused id for a file being edited: return a symbol * (Note: Slow iff a lot of them -- could be O(log(k)) ) + * @param doc A document named node */ - nextSymbol (doc) { + nextSymbol(doc: TFNamedNode): TFNamedNode { for (var i = 0; ;i++) { - var uri = doc.uri + '#n' + i + var uri = doc.value + '#n' + i if (!this.mentionsURI(uri)) return this.sym(uri) } } - query (myQuery, callback, dummy, onDone) { + /** + * Query this store asynchronously, return bindings in callback + * + * @param myQuery The query to be run + * @param callback Function to call when bindings + * @param dummy OBSOLETE - do not use this + * @param onDone OBSOLETE - do not use this + */ + query( + myQuery: Query, + callback: (bindings: Bindings) => void, + dummy?: Fetcher | null, + onDone?: () => void + ): void { return indexedFormulaQuery.call(this, myQuery, callback, dummy, onDone) } -/** Query this store synchhronously and return bindings - * - * @param myQuery {Query} - the query object - * @returns {Array} - */ - querySync (myQuery) { - function saveBinginds (bindings) { + /** + * Query this store synchronously and return bindings + * + * @param myQuery The query to be run + */ + querySync(myQuery: Query): any[] { + var results: Bindings[] = [] + function saveBinginds (bindings: Bindings) { results.push(bindings) } function onDone () { done = true } - var results = [] var done = false + // @ts-ignore TODO: Add .sync to Query myQuery.sync = true indexedFormulaQuery.call(this, myQuery, saveBinginds, null, onDone) @@ -599,9 +813,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** - * Finds a statement object and removes it + * Removes one or multiple statement(s) from this formula + * @param st - A Statement or array of Statements to remove */ - remove (st) { + remove(st: TFQuad | TFQuad[]): IndexedFormula { if (st instanceof Array) { for (var i = 0; i < st.length; i++) { this.remove(st[i]) @@ -611,8 +826,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass if (isStore(st)) { return this.remove(st.statements) } - var sts = this.statementsMatching(st.subject, st.predicate, st.object, - st.why) + var sts = this.statementsMatching(st.subject, st.predicate, st.object, st.graph) if (!sts.length) { throw new Error('Statement to be removed is not on store: ' + st) } @@ -622,9 +836,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Removes all statemnts in a doc + * @param doc - The document / graph */ - removeDocument (doc) { - var sts = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index + removeDocument(doc: TFGraph): IndexedFormula { + var sts: TFQuad[] = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index for (var i = 0; i < sts.length; i++) { this.removeStatement(sts[i]) } @@ -632,37 +847,61 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** - * remove all statements matching args (within limit) * + * Remove all statements matching args (within limit) * + * @param subj The subject + * @param pred The predicate + * @param obj The object + * @param why The graph that contains the statement + * @param limit The number of statements to remove */ - removeMany (subj, pred, obj, why, limit) { + removeMany( + subj?: TFSubject | null, + pred?: TFPredicate | null, + obj?: TFObject | null, + why?: TFGraph | null, + limit?: number + ): void { // log.debug("entering removeMany w/ subj,pred,obj,why,limit = " + subj +", "+ pred+", " + obj+", " + why+", " + limit) var sts = this.statementsMatching(subj, pred, obj, why, false) // This is a subtle bug that occcured in updateCenter.js too. // The fact is, this.statementsMatching returns this.whyIndex instead of a copy of it // but for perfromance consideration, it's better to just do that // so make a copy here. - var statements = [] + var statements: TFQuad[] = [] for (var i = 0; i < sts.length; i++) statements.push(sts[i]) if (limit) statements = statements.slice(0, limit) for (i = 0; i < statements.length; i++) this.remove(statements[i]) } - removeMatches (subject, predicate, object, why) { - this.removeStatements(this.statementsMatching(subject, predicate, object, - why)) + /** + * Remove all matching statements + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The graph that contains the statement + */ + removeMatches( + subject?: TFSubject | null, + predicate?: TFPredicate | null, + object?: TFObject | null, + graph?: TFGraph | null + ): IndexedFormula { + this.removeStatements( + this.statementsMatching(subject, predicate, object, graph) + ) return this } /** * Remove a particular statement object from the store * - * st a statement which is already in the store and indexed. - * Make sure you only use this for these. - * Otherwise, you should use remove() above. + * @param st - a statement which is already in the store and indexed. + * Make sure you only use this for these. + * Otherwise, you should use remove() above. */ - removeStatement (st) { + removeStatement(st: TFQuad): IndexedFormula { // log.debug("entering remove w/ st=" + st) - var term = [ st.subject, st.predicate, st.object, st.why ] + var term = [ st.subject, st.predicate, st.object, st.graph ] for (var p = 0; p < 4; p++) { var c = this.canon(term[p]) var h = this.id(c) @@ -676,7 +915,11 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return this } - removeStatements (sts) { + /** + * Removes statements + * @param sts The statements to remove + */ + removeStatements(sts: ReadonlyArray): IndexedFormula { for (var i = 0; i < sts.length; i++) { this.remove(sts[i]) } @@ -686,7 +929,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Replace big with small, obsoleted with obsoleting. */ - replaceWith (big, small) { + replaceWith (big: TFSubject, small: TFSubject): boolean { // log.debug("Replacing "+big+" with "+small) // this.id(@@ var oldhash = this.id(big) var newhash = this.id(small) @@ -708,7 +951,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass moveIndex(this.index[i]) } this.redirections[oldhash] = small - if (big.uri) { + if (big.value) { // @@JAMBO: must update redirections,aliases from sub-items, too. if (!this.aliases[newhash]) { this.aliases[newhash] = [] @@ -720,7 +963,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass this.aliases[newhash].push(this.aliases[oldhash][i]) } } - this.add(small, this.sym('http://www.w3.org/2007/ont/link#uri'), big.uri) + this.add(small, this.sym('http://www.w3.org/2007/ont/link#uri'), big) // If two things are equal, and one is requested, we should request the other. if (this.fetcher) { this.fetcher.nowKnownAs(big, small) @@ -734,8 +977,9 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Return all equivalent URIs by which this is known + * @param x A named node */ - allAliases (x) { + allAliases(x: NamedNode): NamedNode[] { var a = this.aliases[this.id(this.canon(x))] || [] a.push(this.canon(x)) return a @@ -743,8 +987,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Compare by canonical URI as smushed + * @param x A named node + * @param y Another named node */ - sameThings (x, y) { + sameThings(x: NamedNode, y: NamedNode): boolean { if (x.equals(y)) { return true } @@ -754,10 +1000,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass var y1 = this.canon(y) // alert('y1='+y1); //@@ if (!y1) return false - return (x1.uri === y1.uri) + return (x1.value === y1.value) } - setPrefixForURI (prefix, nsuri) { + setPrefixForURI (prefix: string, nsuri: string): void { // TODO: This is a hack for our own issues, which ought to be fixed // post-release // See http://dig.csail.mit.edu/cgi-bin/roundup.cgi/$rdf/issue227 @@ -773,21 +1019,27 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** Search the Store * * ALL CONVENIENCE LOOKUP FUNCTIONS RELY ON THIS! - * @param {Node} subject - A node to search for as subject, or if null, a wildcard - * @param {Node} predicate - A node to search for as predicate, or if null, a wildcard - * @param {Node} object - A node to search for as object, or if null, a wildcard - * @param {Node} graph - A node to search for as graph, or if null, a wildcard - * @param {Boolean} justOne - flag - stop when found one rather than get all of them? - * @returns {Array} - An array of nodes which match the wildcard position - */ - statementsMatching (subj, pred, obj, why, justOne) { + * @param subj - A node to search for as subject, or if null, a wildcard + * @param pred - A node to search for as predicate, or if null, a wildcard + * @param obj - A node to search for as object, or if null, a wildcard + * @param why - A node to search for as graph, or if null, a wildcard + * @param justOne - flag - stop when found one rather than get all of them? + * @returns An array of nodes which match the wildcard position + */ + statementsMatching ( + subj?: TFSubject | null, + pred?: TFPredicate | null, + obj?: TFObject | null, + why?: TFGraph | null, + justOne?: boolean + ): TFQuad[] { // log.debug("Matching {"+subj+" "+pred+" "+obj+"}") var pat = [ subj, pred, obj, why ] - var pattern = [] - var hash = [] - var wild = [] // wildcards - var given = [] // Not wild - var p + var pattern: TFTerm[] = [] + var hash: Indexable[] = [] + var wild: number[] = [] // wildcards + var given: number[] = [] // Not wild + var p: number var list for (p = 0; p < 4; p++) { pattern[p] = this.canon(Node.fromValue(pat[p])) @@ -831,12 +1083,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } // Ok, we have picked the shortest index but now we have to filter it var pBest = given[iBest] - var possibles = this.index[pBest][hash[pBest]] + var possibles: TFQuad[] = this.index[pBest][hash[pBest]] var check = given.slice(0, iBest).concat(given.slice(iBest + 1)) // remove iBest - var results = [] + var results: TFQuad[] = [] var parts = [ 'subject', 'predicate', 'object', 'why' ] for (var j = 0; j < possibles.length; j++) { - var st = possibles[j] + var st: TFQuad | null = possibles[j] for (i = 0; i < check.length; i++) { // for each position to be checked p = check[i] @@ -854,13 +1106,14 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** - * A list of all the URIs by which this thing is known + * A list of all the URIs by which this thing is known + * @param term */ - uris (term) { + uris(term: TFSubject): string[] { var cterm = this.canon(term) var terms = this.aliases[this.id(cterm)] - if (!cterm.uri) return [] - var res = [ cterm.uri ] + if (!cterm.value) return [] + var res = [ cterm.value ] if (terms) { for (var i = 0; i < terms.length; i++) { res.push(terms[i].uri) diff --git a/src/types.ts b/src/types.ts index 1f6b770be..e65b55f3b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,19 +1,78 @@ +import Node from './node-internal' +import Variable from './variable' +import BlankNode from './blank-node' +import Collection from './collection' +import Literal from './literal' +import NamedNode from './named-node' +import DefaultGraph from './default-graph' +import { SupportTable } from './data-factory-type' + +/** + * Types that support both Enums (for typescript) and regular strings + */ +export type NamedNodeTermType = "NamedNode" | TermType.NamedNode +export type BlankNodeTermType = "BlankNode" | TermType.BlankNode +export type LiteralTermType = "Literal" | TermType.Literal +export type VariableTermType = "Variable" | TermType.Variable +export type CollectionTermType = "Collection" | TermType.Collection +export type DefaultGraphTermType = "DefaultGraph" | TermType.DefaultGraph + /** * All the possible TermTypes * @todo Convert these to const enums when it's supported https://github.com/babel/babel/issues/8741 */ -export const TermType = { - NamedNode: 'NamedNode', - BlankNode: 'BlankNode', - Literal: 'Literal', - Variable: 'Variable', - DefaultGraph: 'DefaultGraph', +export enum TermType { + NamedNode = 'NamedNode', + BlankNode = 'BlankNode', + Literal = 'Literal', + Variable = 'Variable', + DefaultGraph = 'DefaultGraph', // The next ones are not specified by the rdf.js taskforce - Collection: 'Collection', - Empty: 'Empty', - Graph: 'Graph', + Collection = 'Collection', + Empty = 'Empty', + Graph = 'Graph', +} + +/** + * A valid mime type header + * @todo Convert these to const enums when it's supported https://github.com/babel/babel/issues/8741 + */ +export enum ContentType { + rdfxml = "application/rdf+xml", + turtle = "text/turtle", + turtleLegacy = "application/x-turtle", + n3 = "text/n3", + n3Legacy = "application/n3", + nTriples = "application/n-triples", + nQuads = "application/n-quads", + nQuadsAlt = "application/nquads", + jsonld = "application/ld+json", + xhtml = "application/xhtml+xml", + html = "text/html", + sparqlupdate = "application/sparql-update", } +/** A type for values that serves as inputs */ +export type ValueType = TFTerm | Node | Date | string | number | boolean | undefined | null | Collection + +/** + * In this project, there exist two types for the same kind of RDF concept. + * We have RDF/JS Taskforce types (standardized, generic), and RDFlib types (internal, specific). + * When deciding which type to use in a function, it is preferable to accept generic inputs, + * whenever possible, and provide strict outputs. + * In some ways, the TF types in here are a bit more strict. + * Variables are missing, and the statement requires specific types of terms (e.g. NamedNode instead of Term). + */ + +/** An RDF/JS Subject */ +export type SubjectType = BlankNode | NamedNode | Variable +/** An RDF/JS Predicate */ +export type PredicateType = NamedNode | Variable +/** An RDF/JS Object */ +export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable // | Empty +/** An RDF/JS Graph */ +export type GraphType = DefaultGraph | NamedNode | Variable // | Formula + /** * RDF/JS taskforce Term * @link https://rdf.js.org/data-model-spec/#term-interface @@ -23,3 +82,166 @@ export interface TFTerm { value: string equals(other: TFTerm): boolean } + +/** + * RDF/JS taskforce NamedNode + * @link https://rdf.js.org/data-model-spec/#namednode-interface + */ +export interface TFNamedNode extends TFTerm { + termType: NamedNodeTermType + value: string + equals(other: TFTerm): boolean +} + +/** + * RDF/JS taskforce Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface TFBlankNode extends TFTerm { + termType: BlankNodeTermType + value: string + equals(other: TFTerm): boolean +} + +/** + * RDF/JS taskforce Quad + * @link https://rdf.js.org/data-model-spec/#quad-interface + */ +export interface TFQuad< + S extends TFSubject = TFSubject, + P extends TFPredicate = TFPredicate, + O extends TFTerm = TFObject, + G extends TFGraph = TFGraph +> { + subject: S + predicate: P + object: O + graph: G + equals(other: TFQuad): boolean +} + +/** + * RDF/JS taskforce Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface TFLiteral extends TFTerm { + /** Contains the constant "Literal". */ + termType: LiteralTermType + /** The text value, unescaped, without language or type (example: "Brad Pitt") */ + value: string + /** + * The language as lowercase BCP-47 [BCP47] string (examples: "en", "en-gb") + * or an empty string if the literal has no language. + */ + language: string + /** A NamedNode whose IRI represents the datatype of the literal. */ + datatype: TFNamedNode + equals(other: TFTerm): boolean +} + +/** + * RDF/JS taskforce Variable + * @link https://rdf.js.org/data-model-spec/#variable-interface + */ +export interface TFVariable extends TFTerm { + /** Contains the constant "Variable". */ + termType: VariableTermType + /** The name of the variable without leading "?" (example: "a"). */ + value: string + /** + * Returns true if all general Term.equals conditions hold and term.value + * is the same string as other.value; otherwise, it returns false. + */ + equals(other: TFTerm): boolean +} + +/** + * RDF/JS taskforce DefaultGraph + * An instance of DefaultGraph represents the default graph. + * It's only allowed to assign a DefaultGraph to the graph property of a Quad. + * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface + */ +export interface TFDefaultGraph extends TFTerm { + termType: DefaultGraphTermType; + /** should return and empty string'' */ + value: string; + equals(other: TFTerm): boolean +} + +/** + * RDF/JS taskforce DataFactory + * @link https://rdf.js.org/data-model-spec/#datafactory-interface + */ +export interface TFDataFactory< + DFNamedNode extends TFNamedNode = TFNamedNode, + DFBlankNode extends TFBlankNode = TFBlankNode, + DFLiteral extends TFLiteral = TFLiteral, + DFSubject = TFSubject, + DFPredicate = TFPredicate, + DFObject = TFObject, + DFGraph = TFGraph, + DFDefaultGraph = TFDefaultGraph, + DFQuad = TFQuad, +> { + /** Returns a new instance of NamedNode. */ + namedNode: (value: string) => DFNamedNode, + /** + * Returns a new instance of BlankNode. + * If the value parameter is undefined a new identifier for the + * blank node is generated for each call. + */ + blankNode: (value?: string) => DFBlankNode, + /** + * Returns a new instance of Literal. + * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. + * Otherwise languageOrDatatype is used for the value of language. */ + literal: (value: string, languageOrDatatype: string | DFNamedNode) => DFLiteral, + /** Returns a new instance of Variable. This method is optional. */ + variable?: (value: string) => TFVariable, + /** + * Returns an instance of DefaultGraph. + */ + defaultGraph: () => DFDefaultGraph, + /** + * Returns a new instance of the specific Term subclass given by original.termType + * (e.g., NamedNode, BlankNode, Literal, etc.), + * such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. + */ + fromTerm?: (original: TFTerm) => TFTerm + /** + * Returns a new instance of Quad, such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. + */ + fromQuad?: (original: DFQuad) => DFQuad + /** + * Returns a new instance of Quad. + * If graph is undefined or null it MUST set graph to a DefaultGraph. + */ + quad: ( + subject: DFSubject, + predicate: DFPredicate, + object: DFObject, + graph?: DFGraph, + ) => DFQuad + /** + * This does not exist on the original RDF/JS spec + */ + supports: SupportTable +} + +export interface Bindings { + [id: string]: TFTerm; +} + +/** A RDF/JS taskforce Subject */ +export type TFSubject = TFNamedNode | TFBlankNode | TFVariable +/** A RDF/JS taskforce Predicate */ +export type TFPredicate = TFNamedNode | TFVariable +/** A RDF/JS taskforce Object */ +export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable +/** A RDF/JS taskforce Graph */ +export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable + +/** All the types that a .fromValue() method might return */ +export type FromValueReturns = TFTerm | undefined | null | Collection diff --git a/src/update-manager.js b/src/update-manager.ts similarity index 76% rename from src/update-manager.js rename to src/update-manager.ts index 3d39aedcc..f98a040e7 100644 --- a/src/update-manager.js +++ b/src/update-manager.ts @@ -7,34 +7,53 @@ import IndexedFormula from './store' import { docpart } from './uri' import Fetcher from './fetcher' -import DataFactory from './data-factory' import Namespace from './namespace' import Serializer from './serializer' import { join as uriJoin } from './uri' -import { isStore } from './utils/terms' +import { isStore, isTFBlankNode, termValue } from './utils/terms' import * as Util from './util' +import Statement from './statement' +import { NamedNode } from './index' +import { TFNamedNode, TFQuad, TFBlankNode, TFSubject, TFPredicate, TFObject, TFGraph, TFTerm } from './types' -/** Update Manager -* -* The update manager is a helper object for a store. +interface UpdateManagerFormula extends IndexedFormula { + fetcher: Fetcher +} + +type CallBackFunction = (uri: string, ok: boolean, message: string, response: Error | Response) => {} | void + +/** +* The UpdateManager is a helper object for a store. * Just as a Fetcher provides the store with the ability to read and write, * the Update Manager provides functionality for making small patches in real time, * and also looking out for concurrent updates from other agents */ - export default class UpdateManager { - /** @constructor - * @param {IndexedFormula} store - the quadstore to store data and metadata. Created if not passed.f + + store: UpdateManagerFormula + + ifps: {} + + fps: {} + + /** Index of objects for coordinating incoming and outgoing patches */ + patchControl: [] + + /** Object of namespaces */ + ns: any + + /** + * @param store - The quadstore to store data and metadata. Created if not passed. */ - constructor (store) { - store = store || new IndexedFormula() // If none provided make a store - this.store = store + constructor (store?: IndexedFormula) { + store = store || new IndexedFormula() if (store.updater) { throw new Error("You can't have two UpdateManagers for the same store") } - if (!store.fetcher) { // The store must also/already have a fetcher - store.fetcher = new Fetcher(store) + if (!(store as UpdateManagerFormula).fetcher) { + (store as UpdateManagerFormula).fetcher = new Fetcher(store) } + this.store = store as UpdateManagerFormula store.updater = this this.ifps = {} this.fps = {} @@ -48,14 +67,14 @@ export default class UpdateManager { this.ns.rdf = Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') this.ns.owl = Namespace('http://www.w3.org/2002/07/owl#') - this.patchControl = [] // index of objects fro coordinating incomng and outgoing patches + this.patchControl = [] } - patchControlFor (doc) { - if (!this.patchControl[doc.uri]) { - this.patchControl[doc.uri] = [] + patchControlFor (doc: TFNamedNode) { + if (!this.patchControl[doc.value]) { + this.patchControl[doc.value] = [] } - return this.patchControl[doc.uri] + return this.patchControl[doc.value] } /** @@ -64,26 +83,23 @@ export default class UpdateManager { * for safety. * We don't actually check for write access on files. * - * @param uri {string} - * @param kb {IndexedFormula} - * - * @returns {string|boolean|undefined} The method string SPARQL or DAV or + * @returns The method string SPARQL or DAV or * LOCALFILE or false if known, undefined if not known. */ - editable (uri, kb) { + editable (uri: string | TFNamedNode, kb: IndexedFormula): string | boolean | undefined { if (!uri) { return false // Eg subject is bnode, no known doc to write to } if (!kb) { kb = this.store } - uri = uri.uri || uri // Allow Named Node to be passed + uri = termValue(uri) - if (uri.slice(0, 8) === 'file:///') { + if ((uri as string).slice(0, 8) === 'file:///') { if (kb.holds( - kb.sym(uri), - DataFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - DataFactory.namedNode('http://www.w3.org/2007/ont/link#MachineEditableDocument'))) { + this.store.rdfFactory.namedNode(uri), + this.store.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + this.store.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#MachineEditableDocument'))) { return 'LOCALFILE' } @@ -91,7 +107,7 @@ export default class UpdateManager { console.log('UpdateManager.editable: Not MachineEditableDocument file ' + uri + '\n') - console.log(sts.map((x) => { return x.toNT() }).join('\n')) + console.log(sts.map((x) => { return (x as Statement).toNT() }).join('\n')) return false // @@ Would be nifty of course to see whether we actually have write access first. @@ -99,6 +115,7 @@ export default class UpdateManager { var request var definitive = false + // @ts-ignore passes a string to kb.each, which expects a term. Should this work? var requests = kb.each(undefined, this.ns.link('requestedURI'), docpart(uri)) // This if-statement does not follow the Solid spec, but temporarily reverts this change: @@ -110,14 +127,14 @@ export default class UpdateManager { // https://github.com/solid/node-solid-server/pull/1313 // Once that release has been published to the major Pod hosters, the commit that introduced // this statement should be reverted: - if (kb.holds(DataFactory.namedNode(uri), this.ns.rdf('type'), this.ns.ldp('Resource'))) { + if (kb.holds(this.store.rdfFactory.namedNode(uri), this.ns.rdf('type'), this.ns.ldp('Resource'))) { return 'SPARQL' } - var method + var method: string for (var r = 0; r < requests.length; r++) { request = requests[r] if (request !== undefined) { - var response = kb.any(request, this.ns.link('response')) + var response = kb.any(request, this.ns.link('response')) as TFSubject if (request !== undefined) { var wacAllow = kb.anyValue(response, this.ns.httph('wac-allow')) if (wacAllow) { @@ -151,6 +168,7 @@ export default class UpdateManager { var status = kb.each(response, this.ns.http('status')) if (status.length) { for (let i = 0; i < status.length; i++) { + // @ts-ignore since statuses should be TFTerms, this should always be false if (status[i] === 200 || status[i] === 404) { definitive = true // return false // A definitive answer @@ -179,7 +197,7 @@ export default class UpdateManager { : obj.toNT() } - anonymizeNT (stmt) { + anonymizeNT (stmt: TFQuad) { return this.anonymize(stmt.subject) + ' ' + this.anonymize(stmt.predicate) + ' ' + this.anonymize(stmt.object) + ' .' @@ -189,23 +207,23 @@ export default class UpdateManager { * Returns a list of all bnodes occurring in a statement * @private */ - statementBnodes (st) { + statementBnodes (st: TFQuad): TFBlankNode[] { return [st.subject, st.predicate, st.object].filter(function (x) { - return x.isBlank - }) + return isTFBlankNode(x) + }) as TFBlankNode[] } /** * Returns a list of all bnodes occurring in a list of statements * @private */ - statementArrayBnodes (sts) { - var bnodes = [] + statementArrayBnodes (sts: TFQuad[]) { + var bnodes: TFBlankNode[] = [] for (let i = 0; i < sts.length; i++) { bnodes = bnodes.concat(this.statementBnodes(sts[i])) } bnodes.sort() // in place sort - result may have duplicates - var bnodes2 = [] + var bnodes2: TFBlankNode[] = [] for (let j = 0; j < bnodes.length; j++) { if (j === 0 || !bnodes[j].equals(bnodes[j - 1])) { bnodes2.push(bnodes[j]) @@ -223,12 +241,12 @@ export default class UpdateManager { var a = this.store.each(undefined, this.ns.rdf('type'), this.ns.owl('InverseFunctionalProperty')) for (let i = 0; i < a.length; i++) { - this.ifps[a[i].uri] = true + this.ifps[a[i].value] = true } this.fps = {} a = this.store.each(undefined, this.ns.rdf('type'), this.ns.owl('FunctionalProperty')) for (let i = 0; i < a.length; i++) { - this.fps[a[i].uri] = true + this.fps[a[i].value] = true } } @@ -244,7 +262,7 @@ export default class UpdateManager { var y var res for (let i = 0; i < sts.length; i++) { - if (this.fps[sts[i].predicate.uri]) { + if (this.fps[sts[i].predicate.value]) { y = sts[i].subject if (!y.isBlank) { return [ sts[i] ] @@ -260,7 +278,7 @@ export default class UpdateManager { // outgoing links sts = this.store.statementsMatching(x, undefined, undefined, source) for (let i = 0; i < sts.length; i++) { - if (this.ifps[sts[i].predicate.uri]) { + if (this.ifps[sts[i].predicate.value]) { y = sts[i].object if (!y.isBlank) { return [ sts[i] ] @@ -296,9 +314,9 @@ export default class UpdateManager { * @private */ mentioned (x) { - return this.store.statementsMatching(x).length !== 0 || // Don't pin fresh bnodes - this.store.statementsMatching(undefined, x).length !== 0 || - this.store.statementsMatching(undefined, undefined, x).length !== 0 + return this.store.statementsMatching(x, null, null, null).length !== 0 || // Don't pin fresh bnodes + this.store.statementsMatching(null, x).length !== 0 || + this.store.statementsMatching(null, null, x).length !== 0 } /** @@ -321,9 +339,9 @@ export default class UpdateManager { * Returns the best context for a single statement * @private */ - statementContext (st) { + statementContext (st: TFQuad) { var bnodes = this.statementBnodes(st) - return this.bnodeContext(bnodes, st.why) + return this.bnodeContext(bnodes, st.graph) } /** @@ -342,7 +360,11 @@ export default class UpdateManager { /** * @private */ - fire (uri, query, callbackFunction) { + fire ( + uri: string, + query: string, + callbackFunction: CallBackFunction + ): Promise { return Promise.resolve() .then(() => { if (!uri) { @@ -369,7 +391,7 @@ export default class UpdateManager { console.log('UpdateManager: update Ok for <' + uri + '>') - callbackFunction(uri, response.ok, response.responseText, response) + callbackFunction(uri, response.ok, response.responseText as string, response) }) .catch(err => { callbackFunction(uri, false, err.message, err) @@ -384,15 +406,15 @@ export default class UpdateManager { * It returns an object which includes * function which can be used to change the object of the statement. */ - update_statement (statement) { - if (statement && !statement.why) { + update_statement (statement: TFQuad) { + if (statement && !statement.graph) { return } var updater = this var context = this.statementContext(statement) return { - statement: statement ? [statement.subject, statement.predicate, statement.object, statement.why] : undefined, + statement: statement ? [statement.subject, statement.predicate, statement.object, statement.graph] : undefined, statementNT: statement ? this.anonymizeNT(statement) : undefined, where: updater.contextWhere(context), @@ -400,16 +422,19 @@ export default class UpdateManager { var query = this.where query += 'DELETE DATA { ' + this.statementNT + ' } ;\n' query += 'INSERT DATA { ' + + // @ts-ignore `this` might refer to the wrong scope. Does this work? this.anonymize(this.statement[0]) + ' ' + + // @ts-ignore this.anonymize(this.statement[1]) + ' ' + + // @ts-ignore this.anonymize(obj) + ' ' + ' . }\n' - updater.fire(this.statement[3].uri, query, callbackFunction) + updater.fire((this.statement as [TFSubject, TFPredicate, TFObject, TFGraph])[3].value, query, callbackFunction) } } } - insert_statement (st, callbackFunction) { + insert_statement (st: TFQuad, callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -424,10 +449,10 @@ export default class UpdateManager { this.anonymize(st.object) + ' ' + ' . }\n' } - this.fire(st0.why.uri, query, callbackFunction) + this.fire(st0.graph.value, query, callbackFunction) } - delete_statement (st, callbackFunction) { + delete_statement (st: TFQuad | TFQuad[], callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -442,7 +467,7 @@ export default class UpdateManager { this.anonymize(st.object) + ' ' + ' . }\n' } - this.fire(st0.why.uri, query, callbackFunction) + this.fire(st0.graph.value, query, callbackFunction) } /// ////////////////////// @@ -456,7 +481,7 @@ export default class UpdateManager { * @param doc * @param action */ - requestDownstreamAction (doc, action) { + requestDownstreamAction (doc: TFNamedNode, action): void { var control = this.patchControlFor(doc) if (!control.pendingUpstream) { action(doc) @@ -475,27 +500,27 @@ export default class UpdateManager { * We want to start counting websocket notifications * to distinguish the ones from others from our own. */ - clearUpstreamCount (doc) { + clearUpstreamCount (doc: TFNamedNode): void { var control = this.patchControlFor(doc) control.upstreamCount = 0 } - getUpdatesVia (doc) { + getUpdatesVia (doc: TFNamedNode): string | null { var linkHeaders = this.store.fetcher.getHeader(doc, 'updates-via') if (!linkHeaders || !linkHeaders.length) return null return linkHeaders[0].trim() } - addDownstreamChangeListener (doc, listener) { + addDownstreamChangeListener (doc: TFNamedNode, listener): void { var control = this.patchControlFor(doc) if (!control.downstreamChangeListeners) { control.downstreamChangeListeners = [] } control.downstreamChangeListeners.push(listener) - this.setRefreshHandler(doc, (doc) => { + this.setRefreshHandler(doc, (doc: TFNamedNode) => { this.reloadAndSync(doc) }) } - reloadAndSync (doc) { + reloadAndSync (doc: TFNamedNode): void { var control = this.patchControlFor(doc) var updater = this @@ -524,14 +549,14 @@ export default class UpdateManager { } } else { control.reloading = false - if (response.status === 0) { + if ((response as Response).status === 0) { console.log('Network error refreshing the data. Retrying in ' + retryTimeout / 1000) control.reloading = true retryTimeout = retryTimeout * 2 setTimeout(tryReload, retryTimeout) } else { - console.log('Error ' + response.status + 'refreshing the data:' + + console.log('Error ' + (response as Response).status + 'refreshing the data:' + message + '. Stopped' + doc) } } @@ -557,8 +582,8 @@ export default class UpdateManager { * * @returns {boolean} */ - setRefreshHandler (doc, handler) { - var wssURI = this.getUpdatesVia(doc) // relative + setRefreshHandler (doc: TFNamedNode, handler): boolean { + let wssURI = this.getUpdatesVia(doc) // relative // var kb = this.store var theHandler = handler var self = this @@ -571,19 +596,21 @@ export default class UpdateManager { return false } - wssURI = uriJoin(wssURI, doc.uri) - wssURI = wssURI.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:') + wssURI = uriJoin(wssURI, doc.value) + const validWssURI = wssURI.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:') console.log('Web socket URI ' + wssURI) var openWebsocket = function () { // From https://github.com/solid/solid-spec#live-updates var socket if (typeof WebSocket !== 'undefined') { - socket = new WebSocket(wssURI) + socket = new WebSocket(validWssURI) + //@ts-ignore Firefox Addon } else if (typeof Services !== 'undefined') { // Firefox add on http://stackoverflow.com/questions/24244886/is-websocket-supported-in-firefox-for-android-addons + //@ts-ignore Firefox Addon socket = (Services.wm.getMostRecentWindow('navigator:browser').WebSocket)(wssURI) } else if (typeof window !== 'undefined' && window.WebSocket) { - socket = window.WebSocket(wssURI) + socket = (window as any).WebSocket(validWssURI) } else { console.log('Live update disabled, as WebSocket not supported by platform :-(') return @@ -591,7 +618,7 @@ export default class UpdateManager { socket.onopen = function () { console.log(' websocket open') retryTimeout = 1500 // reset timeout to fast on success - this.send('sub ' + doc.uri) + this.send('sub ' + doc.value) if (retries) { console.log('Web socket has been down, better check for any news.') updater.requestDownstreamAction(doc, theHandler) @@ -600,7 +627,7 @@ export default class UpdateManager { var control = self.patchControlFor(doc) control.upstreamCount = 0 - socket.onerror = function onerror (err) { + socket.onerror = function onerror (err: Error) { console.log('Error on Websocket:', err) } @@ -617,9 +644,9 @@ export default class UpdateManager { // 1006 CLOSE_ABNORMAL Reserved. Used to indicate that a connection was closed abnormally ( // // - socket.onclose = function (event) { + socket.onclose = function (event: CloseEvent) { console.log('*** Websocket closed with code ' + event.code + - ", reason '" + event.reason + "' clean = " + event.clean) + ", reason '" + event.reason + "' clean = " + event.wasClean) retryTimeout *= 2 retries += 1 console.log('Retrying in ' + retryTimeout + 'ms') // (ask user?) @@ -628,7 +655,7 @@ export default class UpdateManager { openWebsocket() }, retryTimeout) } - socket.onmessage = function (msg) { + socket.onmessage = function (msg: MessageEvent) { if (msg.data && msg.data.slice(0, 3) === 'pub') { if ('upstreamCount' in control) { control.upstreamCount -= 1 @@ -648,25 +675,27 @@ export default class UpdateManager { return true } - /** Update - * - * This high-level function updates the local store iff the web is changed - * successfully. - * - * Deletions, insertions may be undefined or single statements or lists or formulae - * (may contain bnodes which can be indirectly identified by a where clause). - * The `why` property of each statement must be the same and give the web document to be updated - * - * @param deletions - Statement or statments to be deleted. - * @param insertions - Statement or statements to be inserted - * - * @param callbackFunction {Function} called as callbackFunction(uri, success, errorbody) - * OR returns a promise - * - * @returns {*} + /** + * This high-level function updates the local store iff the web is changed successfully. + * Deletions, insertions may be undefined or single statements or lists or formulae (may contain bnodes which can be indirectly identified by a where clause). + * The `why` property of each statement must be the same and give the web document to be updated. + * @param deletions - Statement or statements to be deleted. + * @param insertions - Statement or statements to be inserted. + * @param callback - called as callbackFunction(uri, success, errorbody) + * OR returns a promise */ - update (deletions, insertions, callbackFunction, secondTry) { - if (!callbackFunction) { + update( + deletions: ReadonlyArray, + insertions: ReadonlyArray, + callback?: ( + uri: string | undefined | null, + success: boolean, + errorBody?: string, + response?: Response | Error + ) => void, + secondTry?: boolean + ): void | Promise { + if (!callback) { var thisUpdater = this return new Promise(function (resolve, reject) { // Promise version thisUpdater.update(deletions, insertions, function (uri, ok, errorBody) { @@ -694,9 +723,9 @@ export default class UpdateManager { throw new Error('Type Error ' + (typeof is) + ': ' + is) } if (ds.length === 0 && is.length === 0) { - return callbackFunction(null, true) // success -- nothing needed to be done. + return callback(null, true) // success -- nothing needed to be done. } - var doc = ds.length ? ds[0].why : is[0].why + var doc = ds.length ? ds[0].graph : is[0].graph if (!doc) { let message = 'Error patching: statement does not specify which document to patch:' + ds[0] + ', ' + is[0] console.log(message) @@ -709,10 +738,10 @@ export default class UpdateManager { var verbs = ['insert', 'delete'] var clauses = { 'delete': ds, 'insert': is } verbs.map(function (verb) { - clauses[verb].map(function (st) { - if (!doc.equals(st.why)) { + clauses[verb].map(function (st: TFQuad) { + if (!doc.equals(st.graph)) { throw new Error('update: destination ' + doc + - ' inconsistent with delete quad ' + st.why) + ' inconsistent with delete quad ' + st.graph) } props.map(function (prop) { if (typeof st[prop] === 'undefined') { @@ -722,7 +751,7 @@ export default class UpdateManager { }) }) - var protocol = this.editable(doc.uri, kb) + var protocol = this.editable(doc.value, kb) if (protocol === false) { throw new Error('Update: Can\'t make changes in uneditable ' + doc) } @@ -730,15 +759,15 @@ export default class UpdateManager { if (secondTry) { throw new Error('Update: Loaded ' + doc + "but stil can't figure out what editing protcol it supports.") } - console.log(`Update: have not loaded ${doc} before: loading now...`) - this.store.fetcher.load(doc).then(response => { - this.update(deletions, insertions, callbackFunction, true) // secondTry + console.log(`Update: have not loaded ${doc} before: loading now...`); + (this.store.fetcher.load(doc) as Promise).then(response => { + this.update(deletions, insertions, callback, true) }, err => { throw new Error(`Update: Can't read ${doc} before patching: ${err}`) }) return - } else if (protocol.indexOf('SPARQL') >= 0) { - var bnodes = [] + } else if ((protocol as string).indexOf('SPARQL') >= 0) { + var bnodes: TFBlankNode[] = [] if (ds.length) bnodes = this.statementArrayBnodes(ds) if (is.length) bnodes = bnodes.concat(this.statementArrayBnodes(is)) var context = this.bnodeContext(bnodes, doc) @@ -784,11 +813,11 @@ export default class UpdateManager { console.log('upstream count up to : ' + control.upstreamCount) } - this.fire(doc.uri, query, (uri, success, body, response) => { - response.elapsedTimeMs = Date.now() - startTime + this.fire(doc.value, query, (uri, success, body, response) => { + (response as any).elapsedTimeMs = Date.now() - startTime console.log(' UpdateManager: Return ' + - (success ? 'success ' : 'FAILURE ') + response.status + - ' elapsed ' + response.elapsedTimeMs + 'ms') + (success ? 'success ' : 'FAILURE ') + (response as Response).status + + ' elapsed ' + (response as any).elapsedTimeMs + 'ms') if (success) { try { kb.remove(ds) @@ -801,7 +830,7 @@ export default class UpdateManager { } } - callbackFunction(uri, success, body, response) + callback(uri, success, body, response) control.pendingUpstream -= 1 // When upstream patches have been sent, reload state if downstream waiting if (control.pendingUpstream === 0 && control.downstreamAction) { @@ -811,15 +840,15 @@ export default class UpdateManager { downstreamAction(doc) } }) - } else if (protocol.indexOf('DAV') >= 0) { - this.updateDav(doc, ds, is, callbackFunction) + } else if ((protocol as string).indexOf('DAV') >= 0) { + this.updateDav(doc, ds, is, callback) } else { - if (protocol.indexOf('LOCALFILE') >= 0) { + if ((protocol as string).indexOf('LOCALFILE') >= 0) { try { - this.updateLocalFile(doc, ds, is, callbackFunction) + this.updateLocalFile(doc, ds, is, callback) } catch (e) { - callbackFunction(doc.uri, false, - 'Exception trying to write back file <' + doc.uri + '>\n' + callback(doc.value, false, + 'Exception trying to write back file <' + doc.value + '>\n' // + tabulator.Util.stackString(e)) ) } @@ -828,12 +857,17 @@ export default class UpdateManager { } } } catch (e) { - callbackFunction(undefined, false, 'Exception in update: ' + e + '\n' + + callback(undefined, false, 'Exception in update: ' + e + '\n' + Util.stackString(e)) } } - updateDav (doc, ds, is, callbackFunction) { + updateDav ( + doc: TFSubject, + ds, + is, + callbackFunction + ): null | Promise { let kb = this.store // The code below is derived from Kenny's UpdateCenter.js var request = kb.any(doc, this.ns.link('request')) @@ -841,11 +875,11 @@ export default class UpdateManager { throw new Error('No record of our HTTP GET request for document: ' + doc) } // should not happen - var response = kb.any(request, this.ns.link('response')) + var response = kb.any(request as TFNamedNode, this.ns.link('response')) as TFSubject if (!response) { return null // throw "No record HTTP GET response for document: "+doc } - var contentType = kb.the(response, this.ns.httph('content-type')).value + var contentType = (kb.the(response, this.ns.httph('content-type'))as TFTerm).value // prepare contents of revised document let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy! @@ -856,7 +890,7 @@ export default class UpdateManager { newSts.push(is[i]) } - const documentString = this.serialize(doc.uri, newSts, contentType) + const documentString = this.serialize(doc.value, newSts, contentType) // Write the new version back var candidateTarget = kb.the(response, this.ns.httph('content-location')) @@ -884,10 +918,10 @@ export default class UpdateManager { kb.add(is[i].subject, is[i].predicate, is[i].object, doc) } - callbackFunction(doc.uri, response.ok, response.responseText, response) + callbackFunction(doc.value, response.ok, response.responseText, response) }) .catch(err => { - callbackFunction(doc.uri, false, err.message, err) + callbackFunction(doc.value, false, err.message, err) }) } @@ -899,7 +933,7 @@ export default class UpdateManager { * @param is * @param callbackFunction */ - updateLocalFile (doc, ds, is, callbackFunction) { + updateLocalFile (doc: TFNamedNode, ds, is, callbackFunction): void { const kb = this.store console.log('Writing back to local file\n') // See http://simon-jung.blogspot.com/2007/10/firefox-extension-file-io.html @@ -913,29 +947,31 @@ export default class UpdateManager { newSts.push(is[ i ]) } // serialize to the appropriate format - var dot = doc.uri.lastIndexOf('.') + var dot = doc.value.lastIndexOf('.') if (dot < 1) { - throw new Error('Rewriting file: No filename extension: ' + doc.uri) + throw new Error('Rewriting file: No filename extension: ' + doc.value) } - var ext = doc.uri.slice(dot + 1) + var ext = doc.value.slice(dot + 1) let contentType = Fetcher.CONTENT_TYPE_BY_EXT[ ext ] if (!contentType) { throw new Error('File extension .' + ext + ' not supported for data write') } - const documentString = this.serialize(doc.uri, newSts, contentType) + const documentString = this.serialize(doc.value, newSts, contentType) // Write the new version back // create component for file writing console.log('Writing back: <<<' + documentString + '>>>') - var filename = doc.uri.slice(7) // chop off file:// leaving /path + var filename = doc.value.slice(7) // chop off file:// leaving /path // console.log("Writeback: Filename: "+filename+"\n") + // @ts-ignore Where does Component come from? Perhaps deprecated? var file = Components.classes[ '@mozilla.org/file/local;1' ] + // @ts-ignore Where does Component come from? Perhaps deprecated? .createInstance(Components.interfaces.nsILocalFile) file.initWithPath(filename) if (!file.exists()) { - throw new Error('Rewriting file <' + doc.uri + + throw new Error('Rewriting file <' + doc.value + '> but it does not exist!') } // { @@ -943,7 +979,9 @@ export default class UpdateManager { // } // create file output stream and use write/create/truncate mode // 0x02 writing, 0x08 create file, 0x20 truncate length if exist + // @ts-ignore Where does Component come from? Perhaps deprecated? var stream = Components.classes[ '@mozilla.org/network/file-output-stream;1' ] + // @ts-ignore Where does Component come from? Perhaps deprecated? .createInstance(Components.interfaces.nsIFileOutputStream) // Various JS systems object to 0666 in struct mode as dangerous @@ -959,19 +997,15 @@ export default class UpdateManager { for (let i = 0; i < is.length; i++) { kb.add(is[ i ].subject, is[ i ].predicate, is[ i ].object, doc) } - callbackFunction(doc.uri, true, '') // success! + callbackFunction(doc.value, true, '') // success! } /** - * @param uri {string} - * @param data {string|Array} - * @param contentType {string} - * * @throws {Error} On unsupported content type * * @returns {string} */ - serialize (uri, data, contentType) { + serialize (uri: string, data: string | TFQuad[], contentType: string): string { const kb = this.store let documentString @@ -1003,35 +1037,31 @@ export default class UpdateManager { } /** - * This is suitable for an initial creation of a document - * - * @param doc {Node} - * @param data {string|Array} - * @param contentType {string} - * @param callbackFunction {Function} callbackFunction(uri, ok, message, response) - * - * @throws {Error} On unsupported content type (via serialize()) - * - * @returns {Promise} + * This is suitable for an initial creation of a document. */ - put (doc, data, contentType, callbackFunction) { + put( + doc: NamedNode, + data: string | TFQuad[], + contentType: string, + callback: (uri: string, ok: boolean, errorMessage?: string, response?: unknown) => void, + ): Promise { const kb = this.store - let documentString + let documentString: string return Promise.resolve() .then(() => { - documentString = this.serialize(doc.uri, data, contentType) + documentString = this.serialize(doc.value, data, contentType) return kb.fetcher - .webOperation('PUT', doc.uri, { contentType, body: documentString }) + .webOperation('PUT', doc.value, { contentType, body: documentString }) }) .then(response => { if (!response.ok) { - return callbackFunction(doc.uri, response.ok, response.error, response) + return callback(doc.value, response.ok, response.error, response) } - delete kb.fetcher.nonexistent[doc.uri] - delete kb.fetcher.requested[doc.uri] // @@ could this mess with the requested state machine? if a fetch is in progress + delete kb.fetcher.nonexistent[doc.value] + delete kb.fetcher.requested[doc.value] // @@ could this mess with the requested state machine? if a fetch is in progress if (typeof data !== 'string') { data.map((st) => { @@ -1039,10 +1069,10 @@ export default class UpdateManager { }) } - callbackFunction(doc.uri, response.ok, '', response) + callback(doc.value, response.ok, '', response) }) .catch(err => { - callbackFunction(doc.uri, false, err.message) + callback(doc.value, false, err.message) }) } @@ -1058,17 +1088,27 @@ export default class UpdateManager { * @param doc {NamedNode} * @param callbackFunction */ - reload (kb, doc, callbackFunction) { + reload ( + kb: IndexedFormula, + doc: docReloadType, + callbackFunction: (ok: boolean, message?: string, response?: Error | Response) => {} | void + ): void { var startTime = Date.now() // force sets no-cache and - const options = { force: true, noMeta: true, clearPreviousData: true } + const options = { + force: true, + noMeta: true, + clearPreviousData: true, + }; - kb.fetcher.nowOrWhenFetched(doc.uri, options, function (ok, body, response) { + (kb as any).fetcher.nowOrWhenFetched(doc.value, options, function (ok: boolean, body: Body, response: Response) { if (!ok) { console.log(' ERROR reloading data: ' + body) callbackFunction(false, 'Error reloading data: ' + body, response) + //@ts-ignore Where does onErrorWasCalled come from? } else if (response.onErrorWasCalled || response.status !== 200) { console.log(' Non-HTTP error reloading data! onErrorWasCalled=' + + //@ts-ignore Where does onErrorWasCalled come from? response.onErrorWasCalled + ' status: ' + response.status) callbackFunction(false, 'Non-HTTP error reloading data: ' + body, response) } else { @@ -1089,3 +1129,8 @@ export default class UpdateManager { }) } } + +interface docReloadType extends TFNamedNode { + reloadTimeCount?: number + reloadTimeTotal?: number +} diff --git a/src/uri.js b/src/uri.ts similarity index 75% rename from src/uri.js rename to src/uri.ts index 40fe7923b..18945e3d5 100644 --- a/src/uri.js +++ b/src/uri.ts @@ -12,8 +12,12 @@ var alert = alert || console.log import NamedNode from './named-node' -export function docpart (uri) { - var i +/** + * Gets the document part of an URI + * @param uri The URI + */ +export function docpart(uri: string): string { + var i: number i = uri.indexOf('#') if (i < 0) { return uri @@ -22,11 +26,19 @@ export function docpart (uri) { } } -export function document (x) { - return new NamedNode(docpart(x.uri)) +/** + * Gets the document part of an URI as a named node + * @param x - The URI + */ +export function document(x: string): NamedNode { + return new NamedNode(docpart(x)) } -export function hostpart (u) { +/** + * Gets the hostname in an URI + * @param u The URI + */ +export function hostpart(u: string): string { var m = /[^\/]*\/\/([^\/]*)\//.exec(u) if (m) { return m[1] @@ -35,7 +47,12 @@ export function hostpart (u) { } } -export function join (given, base) { +/** + * Joins an URI with a base + * @param given - The relative part + * @param base - The base URI + */ +export function join(given: string, base: string): string { var baseColon, baseScheme, baseSingle var colon, lastSlash, path var baseHash = base.indexOf('#') @@ -103,9 +120,12 @@ export function join (given, base) { return base.slice(0, baseSingle) + path } -export function protocol (uri) { - var i - i = uri.indexOf(':') +/** + * Gets the protocol part of an URI + * @param uri The URI + */ +export function protocol(uri: string): string | null { + const i = uri.indexOf(':') if (i < 0) { return null } else { @@ -113,8 +133,25 @@ export function protocol (uri) { } } -export function refTo (base, uri) { - var c, i, k, l, len, len1, n, o, p, q, ref, ref1, s +/** + * Gets a relative uri + * @param base The base URI + * @param uri The absolute URI + */ +export function refTo(base: string, uri: string): string { + var c: string, + i: number, + k: number, + l: number, + len: number, + len1: number, + n: number, + o: number, + p: number, + q: number, + ref: string, + ref1: number, + s: string var commonHost = new RegExp('^[-_a-zA-Z0-9.]+:(//[^/]*)?/[^/]*$') if (!base) { return uri @@ -123,7 +160,7 @@ export function refTo (base, uri) { return '' } for (i = o = 0, len = uri.length; o < len; i = ++o) { - c = uri[i] + const c = uri[i] if (c !== base[i]) { break } diff --git a/src/util.js b/src/util.js index 08de267bd..eb9b543cb 100644 --- a/src/util.js +++ b/src/util.js @@ -20,117 +20,13 @@ export function linkRelationProperty(relation){ return new NamedNode('http://www.w3.org/ns/iana/link-relations/relation#' + relation.trim()) } -export const appliedFactoryMethods = [ - 'blankNode', - 'defaultGraph', - 'literal', - 'namedNode', - 'quad', - 'variable', - 'supports', -] - -/** - * Loads ontologies of the data we load (this is the callback from the kb to - * the fetcher). - */ -export function AJAR_handleNewTerm (kb, p, requestedBy) { - var sf = null - if (typeof kb.fetcher !== 'undefined') { - sf = kb.fetcher - } else { - return - } - if (p.termType !== 'NamedNode') return - var docuri = docpart(p.uri) - var fixuri - if (p.uri.indexOf('#') < 0) { // No hash - // @@ major hack for dbpedia Categories, which spread indefinitely - if (string_startswith(p.uri, 'http://dbpedia.org/resource/Category:')) return - - /* - if (string_startswith(p.uri, 'http://xmlns.com/foaf/0.1/')) { - fixuri = "http://dig.csail.mit.edu/2005/ajar/ajaw/test/foaf" - // should give HTTP 303 to ontology -- now is :-) - } else - */ - if (string_startswith(p.uri, - 'http://purl.org/dc/elements/1.1/') || - string_startswith(p.uri, 'http://purl.org/dc/terms/')) { - fixuri = 'http://dublincore.org/2005/06/13/dcq' - // dc fetched multiple times - } else if (string_startswith(p.uri, 'http://xmlns.com/wot/0.1/')) { - fixuri = 'http://xmlns.com/wot/0.1/index.rdf' - } else if (string_startswith(p.uri, 'http://web.resource.org/cc/')) { - // log.warn("creative commons links to html instead of rdf. doesn't seem to content-negotiate.") - fixuri = 'http://web.resource.org/cc/schema.rdf' - } - } - if (fixuri) { - docuri = fixuri - } - if (sf && sf.getState(docuri) !== 'unrequested') return - - if (fixuri) { // only give warning once: else happens too often - log.warn('Assuming server still broken, faking redirect of <' + p.uri + - '> to <' + docuri + '>') - } - - return sf.fetch(docuri, { referringTerm: requestedBy }) -} - -const rdf = { - first: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', - rest: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', - nil: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil' -} - -/** - * Expands an array of Terms to a set of statements representing the rdf:list. - * @param rdfFactory - The factory to use - * @param {NamedNode|BlankNode} subject - The iri of the first list item. - * @param {Term[]} data - The terms to expand into the list. - * @return {Statement[]} - The {data} as a set of statements. - */ -export function arrayToStatements(rdfFactory, subject, data) { - const statements = [] - - data.reduce((id, listObj, i, listData) => { - statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i])) - - let nextNode - if (i < listData.length - 1) { - nextNode = rdfFactory.blankNode() - statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.rest), nextNode)) - } else { - statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.rest), rdfFactory.namedNode(rdf.nil))) - } - - return nextNode - }, subject) - - return statements -} - -export function ArrayIndexOf (arr, item, i) { - i || (i = 0) - var length = arr.length - if (i < 0) i = length + i - for (; i < length; i++) { - if (arr[i] === item) { - return i - } - } - return -1 -} - /** * Adds callback functionality to an object. * Callback functions are indexed by a 'hook' string. * They return true if they want to be called again. * @method callbackify * @param obj {Object} - * @param callbacks {Array} + * @param callbacks {Array} */ export function callbackify (obj, callbacks) { obj.callbacks = {} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..ca82425cc --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,114 @@ +import Fetcher from './fetcher' +import log from './log' +import { TFDataFactory, TFLiteral, TFQuad, TFSubject, TFTerm } from './types' +import { docpart } from './uri' +import { string_startswith } from './util' + +/** RDF/JS Taskforce Typeguards */ + +/** + * Loads ontologies of the data we load (this is the callback from the kb to + * the fetcher). + */ +export function AJAR_handleNewTerm (kb: { fetcher: Fetcher }, p, requestedBy) { + var sf: Fetcher | null = null + if (typeof kb.fetcher !== 'undefined') { + sf = kb.fetcher + } else { + return + } + if (p.termType !== 'NamedNode') return + var docuri = docpart(p.uri) + var fixuri + if (p.uri.indexOf('#') < 0) { // No hash + // @@ major hack for dbpedia Categories, which spread indefinitely + if (string_startswith(p.uri, 'http://dbpedia.org/resource/Category:')) return + + /* + if (string_startswith(p.uri, 'http://xmlns.com/foaf/0.1/')) { + fixuri = "http://dig.csail.mit.edu/2005/ajar/ajaw/test/foaf" + // should give HTTP 303 to ontology -- now is :-) + } else + */ + if (string_startswith(p.uri, + 'http://purl.org/dc/elements/1.1/') || + string_startswith(p.uri, 'http://purl.org/dc/terms/')) { + fixuri = 'http://dublincore.org/2005/06/13/dcq' + // dc fetched multiple times + } else if (string_startswith(p.uri, 'http://xmlns.com/wot/0.1/')) { + fixuri = 'http://xmlns.com/wot/0.1/index.rdf' + } else if (string_startswith(p.uri, 'http://web.resource.org/cc/')) { + // log.warn("creative commons links to html instead of rdf. doesn't seem to content-negotiate.") + fixuri = 'http://web.resource.org/cc/schema.rdf' + } + } + if (fixuri) { + docuri = fixuri + } + if (sf && (sf as Fetcher).getState(docuri) !== 'unrequested') return + + if (fixuri) { // only give warning once: else happens too often + log.warn('Assuming server still broken, faking redirect of <' + p.uri + + '> to <' + docuri + '>') + } + + return (sf as any).fetch(docuri, { referringTerm: requestedBy }) +} + +export const appliedFactoryMethods = [ + 'blankNode', + 'defaultGraph', + 'literal', + 'namedNode', + 'quad', + 'variable', + 'supports', +] + +const rdf = { + first: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', + rest: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', + nil: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil' +} + +/** + * Expands an array of Terms to a set of statements representing the rdf:list. + * @param rdfFactory - The factory to use + * @param subject - The iri of the first list item. + * @param data - The terms to expand into the list. + * @return The {data} as a set of statements. + */ +export function arrayToStatements( + rdfFactory: TFDataFactory, + subject: TFSubject, + data: TFTerm[] +): TFQuad[] { + const statements: TFQuad[] = [] + + data.reduce((id, _listObj, i, listData) => { + statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i] as TFLiteral)) + + let nextNode + if (i < listData.length - 1) { + nextNode = rdfFactory.blankNode() + statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.rest), nextNode)) + } else { + statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.rest), rdfFactory.namedNode(rdf.nil))) + } + + return nextNode + }, subject) + + return statements +} + +export function ArrayIndexOf (arr, item, i: number = 0) { + var length = arr.length + if (i < 0) i = length + i + for (; i < length; i++) { + if (arr[i] === item) { + return i + } + } + return -1 +} diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 448f70a8d..80aee4cd2 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -1,24 +1,35 @@ -import { TFTerm } from "../types"; +import { + ObjectType, + TermType, + TFBlankNode, + TFGraph, + TFLiteral, + TFNamedNode, + TFObject, + TFPredicate, + TFQuad, + TFSubject, + TFTerm +} from '../types' +import Collection from '../collection' +import IndexedFormula from '../store' +import Statement from '../statement' +import NamedNode from '../named-node' -export function isTerm(obj) { - return typeof obj === 'object' - && obj !== null - && 'termType' in obj - && 'value' in obj -} - -export function isStatement(obj) { +export function isStatement(obj): obj is Statement { return typeof obj === 'object' && obj !== null && 'subject' in obj } -export function isStore(obj) { +/** TypeGuard for RDF/JS TaskForce Stores */ +export function isStore(obj): obj is IndexedFormula { return typeof obj === 'object' && obj !== null && 'statements' in obj } -export function isNamedNode(obj) { - return isTerm(obj) && obj.termType === 'NamedNode' +export function isNamedNode(obj): obj is NamedNode { + return isTFTerm(obj) && obj.termType === 'NamedNode' } +/** Retrieve the value of a term, or self if already a string. */ export function termValue(node: TFTerm | string): string { if (typeof node === 'string') { return node @@ -26,3 +37,85 @@ export function termValue(node: TFTerm | string): string { return node.value } + +/** TypeGuard for RDFLib Collections */ +export function isCollection(obj: any): obj is Collection { + return isTFTerm(obj) + && (obj as TFTerm).termType === TermType.Collection +} + +/** TypeGuard for valid RDFlib Object types, also allows Collections */ +export function isRDFObject (obj: any): obj is ObjectType { + return obj && Object.prototype.hasOwnProperty.call(obj, 'termType') && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.Variable || + obj.termType === TermType.BlankNode || + obj.termType === TermType.Collection || + obj.termType === TermType.Literal + ) +} + +/** TypeGuard for RDF/JS TaskForce Terms */ +export function isTFTerm (obj: any): obj is TFTerm { + return typeof obj === 'object' + && obj !== null + && 'termType' in obj + && 'value' in obj +} + +/** TypeGuard for RDF/JS TaskForce Literals */ +export function isTFLiteral (value: any): value is TFLiteral { + return (value as TFTerm).termType === TermType.Literal +} + +/** TypeGuard for RDF/JS TaskForce Quads */ +export function isTFStatement (obj: any): obj is TFQuad { + return typeof obj === "object" && obj !== null && 'subject' in obj +} + +/** TypeGuard for RDF/JS TaskForce NamedNodes */ +export function isTFNamedNode (obj: any): obj is TFNamedNode { + return isTFTerm(obj) && 'termType' in obj && obj.termType === 'NamedNode' +} + +/** TypeGuard for RDF/JS TaskForce BlankNodes */ +export function isTFBlankNode (obj: any): obj is TFBlankNode { + return isTFTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' +} + +/** TypeGuard for valid RDFJS Taskforce Subject types */ +export function isTFSubject (obj: any): obj is TFSubject { + return isTFTerm(obj) && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.Variable || + obj.termType === TermType.BlankNode + ) +} + +/** TypeGuard for valid RDFJS Taskforce Predicate types */ +export function isTFPredicate (obj: any): obj is TFPredicate { + return isTFTerm(obj) && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.Variable + ) +} + +/** TypeGuard for valid RDFJS Taskforce Object types */ +export function isTFObject (obj: any): obj is TFObject { + return isTFTerm(obj) && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.Variable || + obj.termType === TermType.BlankNode || + obj.termType === TermType.Literal + ) +} + +/** TypeGuard for valid RDFJS Graph types */ +export function isTFGraph (obj: any): obj is TFGraph { + return isTFTerm(obj) && ( + obj.termType === TermType.NamedNode || + obj.termType === TermType.Variable || + obj.termType === TermType.BlankNode || + obj.termType === TermType.DefaultGraph + ) +} diff --git a/src/variable.ts b/src/variable.ts index d38d854cc..7f7704325 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -1,25 +1,23 @@ -'use strict' import ClassOrder from './class-order' import Node from './node-internal' -import { TermType } from "./types"; +import { TermType, TFVariable, VariableTermType } from './types' import * as Uri from './uri' /** - * Variables are placeholders used in patterns to be matched. - * In cwm they are symbols which are the formula's list of quantified variables. - * In sparql they are not visibly URIs. Here we compromise, by having - * a common special base URI for variables. Their names are uris, - * but the ? notation has an implicit base uri of 'varid:' - * @class Variable - */ -export default class Variable extends Node { +* Variables are placeholders used in patterns to be matched. +* In cwm they are symbols which are the formula's list of quantified variables. +* In sparql they are not visibly URIs. Here we compromise, by having +* a common special base URI for variables. Their names are uris, +* but the ? notation has an implicit base uri of 'varid:' +*/ +export default class Variable extends Node implements TFVariable { static termType = TermType.Variable /** The base string for a variable's name */ base = 'varid:' classOrder = ClassOrder.Variable isVar = 1 - termType = TermType.Variable + termType: VariableTermType = TermType.Variable /** The unique identifier of this variable */ uri: string @@ -29,6 +27,7 @@ export default class Variable extends Node { */ constructor (name = '') { super(name) + this.base = 'varid:' this.uri = Uri.join(name, this.base) } diff --git a/src/wip.md b/src/wip.md new file mode 100644 index 000000000..209422fb9 --- /dev/null +++ b/src/wip.md @@ -0,0 +1,111 @@ +Builds upon the approved #363 PR for [typescript migration](https://github.com/linkeddata/rdflib.js/issues/355): + +## Changes included in PR #363 + +- Converted some of the most fundamental classes to typescript, including `Node`, `Literal`, `BlankNode`, `NamedNode`, `Collection`, `Statement`. +- Introduced a `.types` file for shared types. +- Included a temporary `types-temp.ts` file in project root as a reference file for documentation and keeping track of the ts migration process. +- The `.isVar` method is set to boolean values, instead of `0` or `1`. This seemed reasonable, as it's only used for boolean type checks, and the existing types already define it as a boolean value. Timbl confirmed that `isVar` is only used for boolean operations. +- JSDoc is replaced with Typedoc. Combined with types and comments from `@types/rdflib`, this makes the documentation far more complete. +- I used many of the types and comments from `@types/rdflib` by [Cénotélie](https://github.com/cenotelie/). Added credits in `package.json`, discussed this with Cénotélie. + +## New changes + +- Migrated `formula`, `variable`, `store`, `update-manager`, `data-factory`, `default-graph`, `namespace`, `parse`,`serialize`, `parse`, `uri` and `utils` tot ts. +- Added `fork-ts-checker-webpack-plugin`, which enables errors in the log when ts errors occur. +- Added and implemented RDF/JS Taskforce (TF) types, included these in the `types.ts` file. I tried implementing the TF types in the major classes, but some of the incompatibilities make it difficult. Many available methods on rdfjs instances (e.g. `.toNt()` in NamedNode), are missing in TF classes. To improve TF comatibility, we should minimize using rdflib specific functions. This would for example enable using Forumla methods on RDFExt nodes. We should use the Taskforce types (TFTerm, TFQuad) as much as possible, instead of rdflib types (Node, Statement). +- Added typeguards, e.g. `isTFNamedNode` and `isTFPredicate` in `Utils`, and used these at various locations. +- Use enums for `termType` and `contentType`, without breaking compatibility with regular strings. +- `Formula` Constructor arguments are optional - since some functions initialize empty Formulas. +- In `Formula.fromNT()` `return this.literal(str, lang || dt)` seemed wrong, converted it to +- The various `fromValue` methods conflict with the base Node class, type wise. Since they don't use `this`, I feel like they should be converted to functions. + +## Compatibility with RDFJS taskforce and external datafactories + +- Variables (from rdfjs taskforce) make typings a lot more complex (many methods would require explicit type checks, e.g. you can't serialize a variable to N-Triples), so I disabled them. +- Switched internal calls from `sameTerm` to `equals` in order to comply to TF spec, so that these functions also work with external datafactories. Alias still exists, so nothing changes externally. +- Switched internal calls from `.why` to `graph`. Alias still exists, so nothing changes externally. +- Calls to `kb.sym` have been replaced with `kb.rdfFactory.namedNode`, which makes all these functions more compatible with external datafactories. Added a deprecation warning to `.sym`. + +## Minor fixes + +- Removed the last conditional of `Formula.holds()`, since it did not make sense +- Removed some unreachable code, unused variables and functions that didn't do anything such as `Node.substitute()`. +- Removed the `justOne` argument from `formula.statementsMatching`, since it was unused. +- The `uri.document` function called `.uri` on a string, I removed that. +- Transformed inline comments to JSDoc, moved them to type declarations instead of constructor. +- Some types are set to any, because I didn't fully understand them. I've added TODO comments for these. +- Removed the fourth argument from the `parser.parse` function in `fetcher.parse`, since the function only takes three. +- Removed the `response` argument from `fetcher.parse`, `XHTMLHandler.parse`, `RDFXMLHander.parse`, `XMLHandler`, since it was not used. +- `Fetcher.failfetch` added strings as objects to the store. Changed that to literals. +- Internal calls to `NamedNode.uri` are changed to `.value` to comply with TF spec. This enables these functions to work with external datafactories. +- Removed unused second argument from `Fetcher.cleanupFetchRequest` +- Created one huge `Options` type for Fetcher. Not sure if this is the way to go. +- In `Node.toJS`, the boolean only returned true if the `xsd:boolean` value is `'1'`, now it it should also work for `'true'`. +- Converted `kb.add(someString)` to `kb.add(new Namednode(somestring))` to enhance compatibility with other datafactories. This happens in `Fetcher` and +- `Fetcher.refreshIfExpired` passed an array of headers, but it needs only one string. +- `Fethcer` uses `Headers` a lot. I've changed empty objects to empty `new Headers` instances, which enhances compatibility with default `Fetch` behavior. +- `Serializer.tripleCallback` had an unused third argument. +- `UpdateManager.update` checked an undefined `secondTry` variable. Since later in the same function, `.update` is called with a 4th argument, I assume this is `secondTry`. I've added it as an optional argument. Perhaps this is +- `Formula.add()` now uses `this.rdfFactory.defaultGraph` instead of the non-existent `this.defaultGraph` +- `IndexedFormula.replaceWith` now passes a Node instead of a string to `.add` in the `if (big.value)` block + +## Possible bugs discovered, which are not fixed by this PR + +- `Formula.substitute` uses `this.add(Statments[])`, which will crash. I think it should be removed, since `IndexedFormula.substitute` is used all the time anyway. +- The `Formula.serialize` function calls `serialize.ts` with only one argument, so without a store. I think this will crash every time. Also`Formula.serialize` uses `this.namespaces`, but this is only defined in `IndexedFormula`. Is it rotten code and should it be removed? +- `IndexedFormula.add()` accepts many types of inputs, but this will lead to invalid statements (e.g. a Literal as a Subject). I suggest we make this more strict and throw more errors on wrong inputs. Relates to #362. We could still make the allowed inputs bigger by allowing other types with some explicit behavior, e.g. in subject arguments, create `NamedNodes` from `URL` objects and `strings` that look like URLs . In any case, I thinkg the `Node.fromValue` behavior is too unpredictable for `store.add`. For now, I've updated the docs to match its behavior. +- The types for `Node.fromValue` and `Literal.fromValue` show how unpredictable these methods are. I suggest we make them more strict (also relates to #362), so they either return a `TFTerm` (`node`) or throw an error - they should not return `undefined` or `null`. Also, I think they should be converted to functions in `Utils`: this would fix the circular dependency issue (why we need `node_internal`) and it would fix the type issues in `Literal.fromValue` (which tends to give issues since it's signature does not correctly extend from `Node.fromValue`) +- In `Fetcher.addtype`, the final logic will allways return `true`, since `redirection` is a `NamedNode`. Should it call `.value`? +- Various `Hanlder.parse()` functions in `Fetcher` return either a `Response` or a `Promise`. This seems like weird behavior - should it not always return an array? +- The `defaultGraph` iri is set to `chrome:theSession`, but this errors in Firefox. I suggest we change it to something else. See #370. +- The `Parse.executeErrorCallback` conditional logic is always `true`. +- I've added many `// @ts-ignore` comments. Ideally, these should be resolved by fixing the underlying type issues. +- `UpdateManager.update_statement` seems to refer to the wrong `this`. It calls `this.anonimize`, but it is not available in that scope. +- `UpdateManager.updateLocalFile` uses `Component`, but this is not defined anywhere. Is this deprecated? +- `Data-factory-internal.id()` returns `string | undefined`, I feel like undefined should not be possible - it should throw an error. This would resolve the type incompatibility on line 146. +- `IndexedFormula.copy` runs `.copy` on a Collection, but that method is not available there. +- `IndexedFormula.predicateCallback` is checked, but never used in this codebase. + + +## Other things I noticed + +- Literals can apparently be `null` or `undefined`, when nodes are created using the `.fromValue` method. This causes faulty behavior. This happens in the `new Statement()` constructor as well. See #362. +- The `IndexedFormula.add()` method has logic for Statement array inputs and store inputs, but this behavior is not documented. It also refers to `this.fetcher` and `this.defaultGraph`, which both should not be available. I've added types that accept these arrays. +- The filenames of major classes differ from their default exports, e.g. `store.ts` is called `IndexedFormula`. +- Aliases (e.g. `IndexedFormula.match` for `IndexefFormula.statementsMatching`) introduce complexity, documentation and type duplication. I suggest adding deprecation warnings. +- The various calling methods of `Fetcher.nowOrWhenFetched` are quite dynamic. A simpler, stricter input type might be preferable. +- The Variable type (or `TFVariable`) really messes with some assumptions. I feel like they should not be valid in regular quads, since it's impossible to serialize them decently. If I'd add it to the accepted types, we'd require a lot of typeguards in functions. +- `Fetcher` `StatusValues` can be many types in RDFlib: string, number, true, undefined... This breaks compatibility with extending `Response` types, since these only use numbers. I feel we should only use the `499` browser error and add the text message to the `requestbody`. I've created a type for the internal `InternalResponse`; it shows how it differs from a regular `Response`. The `.responseText` property, for example, is used frequently in Fetcher, but +- The `IndexedFormula` and `Formula` methods have incompatible types, such as in `compareTerm`, `variable` and `add`. I've added `//@ts-ignore` lines with comments. +- The fourth `reponse` argument in `.parse()` methods in `Handler` classes was unused (except in N3Handler), so I removed it everywhere it was unused. +- `Serializer`'s fourth `options` argument is undocumented, and I couldn't find out how it worked. +- `Fetcher` `saveResponseMetadata` creates literals +- Many functions in `Fetcher` assume that specific `Opts` are defined. I've included all these in a single `Options` type and added documentation for the props I understood. I've also created an `AutoInitOptions` type, which sets auto-initialized. I extended Options in each function where specific opts seemed to be required. I'm not entirely confident about the types I've set. I feel like the truly required items should never be `Opts`, since they aren't optional. Refactoring this requires a seperate PR. +- `Fetcher.load` allows arrays as inputs. This causes the output types to be more unpredictable. `Promise | Result[]`. I suggest splitting this in two functions, e.g. add `loadMany` +- `Utils.callbackify` seems to be used only in `Fetcher`. +- `UpdateManager.editable` has a confusing return type (`string | boolean | undefined`). I suggest we refactor it to always return one of multiple pre-defined strings,. +- The `optional` argument in `formula.js` does not seem to be documented, used or tested - should it be removed? + +## Need review + +- Some of the `Formula` and `IndexedFormula` functions (e.g. `anyStatementMatching`) might have too strict types - perhaps Collections are allowed in some of them. +- `IndexedFormula.declareExistential` & `newExistential` have different assumptions on types - should they be blanknodes or namednodes? +- `IndexedFormula.check` passes a single statement to `checkStatementList`, which expects an array +- I've added many type assertions (e.g. `as TFObject`), but Ideally, these do not exist. Ultimately, these should be replaced by TypeGuards that work on runtime/ +- The `data-factory-types` are quite complex. This is a result of the differences between the RDF//JS Taskforce spec and rdflib itself. Perhaps there is an easier / cleaner way to setup the types (without heavy use of generics), but I'm afraid I can't think of one. + +## Some thoughts on simplifying language + +Getting started with Linked Data or RDF can be difficult, since it introduces quite a few new concepts. +Since this library is powerful and generic, it might be one of the first and most important RDF tools that a developer will use. +Therefore, we should try to use consistent langauge and keep synonyms to a minimum. + +- The name `Node` and `Term` seem to refer to the same concept. Both are used in this repo. I think Term is slightly more suited, partially because it complies to the TF spec, but also because it seems more sementically correct. A `Literal`, for example, is not really a node in the mathematical sense, it's more of an edge, since it cannot connect to other nodes. +- `Statement`, `Triple` and `Quad` refer to the same concept, at least technically. Maybe we could pick one. I suggest `Statement`, because it covers both triples and quads. +- The concept `graph` is referred to as `why`, `doc` and `graph` in the code and API. I think this might be confusing - should we just call it `graph` everywhere? +- the `IndexedFormula` default export name is different from the `store` filename. It might be easier to just call it `store` everywhere, including where it's called `kb`. + +## Probably OK, but I don't get it: + +- I'm a bit embarassed about this, but even after rewriting so much of the code I still don't understand all methods. E.g. `Forumla.transitiveClosure()` diff --git a/tests/unit/fetcher-test.js b/tests/unit/fetcher-test.js index 182c0942b..677d46fa8 100644 --- a/tests/unit/fetcher-test.js +++ b/tests/unit/fetcher-test.js @@ -70,6 +70,7 @@ describe('Fetcher', () => { status: 200, }) const options = { + req: store.rdfFactory.blankNode(), resource: store.rdfFactory.namedNode('https://example.com/resource/1') } diff --git a/tests/unit/parse-test.js b/tests/unit/parse-test.js index 9ffb399bd..5b0d73b7c 100644 --- a/tests/unit/parse-test.js +++ b/tests/unit/parse-test.js @@ -4,7 +4,6 @@ import { expect } from 'chai' import parse from '../../src/parse' import CanonicalDataFactory from '../../src/data-factory-internal' import DataFactory from '../../src/data-factory' -import Node from '../../src/node' import defaultXSD from '../../src/xsd' describe('Parse', () => { diff --git a/tests/unit/typings-test.ts b/tests/unit/typings-test.ts new file mode 100644 index 000000000..38244c15b --- /dev/null +++ b/tests/unit/typings-test.ts @@ -0,0 +1,18 @@ + +import Statement from '../../src/statement' +import Literal from '../../src/literal' +import { expect } from 'chai' + +/** + * These test do some assertions, but their main test is that they don't create compiler errors. + */ +describe('typings', () => { + it('allows calling Literal.equal with non-literal', () => { + const test = (): Statement => undefined as unknown as Statement + + const b = test() + const c = new Literal("") + + expect(c.equals(b.object)).to.be.false() + }) +}) diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index 4c54f5ad4..1f1c23ffb 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -5,11 +5,11 @@ import CanonicalDataFactory from '../../src/data-factory-internal' import Literal from '../../src/literal' import NamedNode from '../../src/named-node' import Statement from '../../src/statement' -import { arrayToStatements } from '../../src/util' -import { isNamedNode, isStatement, isTerm } from '../../src/utils/terms' +import { arrayToStatements } from '../../src/utils' +import { isNamedNode, isStatement, isTFTerm } from '../../src/utils/terms' describe('util', () => { - describe('isTerm', () => { + describe('isTFTerm', () => { it('handles undefined', () => { expect(isNamedNode(undefined)).to.be.false() }) @@ -26,7 +26,7 @@ describe('util', () => { }) it ('handles literals', () => { - expect(isTerm(new Literal('test'))).to.be.true() + expect(isTFTerm(new Literal('test'))).to.be.true() }); }) diff --git a/tsconfig.json b/tsconfig.json index 4b78c20c4..0ce1aedf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,13 +3,15 @@ "allowSyntheticDefaultImports": true, "checkJs": false, "allowJs": true, - "target": "es6", + "target": "es5", "module": "es6", "strict": true, "noImplicitAny": false, "lib": [ - "es2019" - ] + "es2019", + "dom" + ], + "outDir": "./temp_ts_output" }, "include": [ "src/**/*" diff --git a/typedoc.js b/typedoc.js new file mode 100644 index 000000000..c3592bae5 --- /dev/null +++ b/typedoc.js @@ -0,0 +1,18 @@ +module.exports = { + src: ['./src'], + mode: "file", + out: "doc", + tsconfig: "tsconfig.json", + theme: "default", + hideGenerator: true, + ignoreCompilerErrors: true, + excludePrivate: true, + excludeNotExported: "true", + target: "ES6", + moduleResolution: "node", + preserveConstEnums: "true", + stripInternal: "true", + suppressExcessPropertyErrors: "true", + suppressImplicitAnyIndexErrors: "true", + module: "commonjs" +} diff --git a/types-temp.ts b/types-temp.ts new file mode 100644 index 000000000..bee81ed0e --- /dev/null +++ b/types-temp.ts @@ -0,0 +1,293 @@ +// This is a Temporary file to help with the migration to typescript. +// See issue: https://github.com/linkeddata/rdflib.js/issues/355 +// Migrate these types and comments to the according files, then remove them from this list. +// Don't import types from this file. +// When you do want to use a type from this file, move it to `./types.ts` +// And import it here. + +import { + Bindings, + ValueType, +} from './src/types' +import { NamedNode } from './src' + +// Type definitions for rdflib 0.20 +// Project: http://github.com/linkeddata/rdflib.js +// Definitions by: Cénotélie +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 3.0 +// Acknowledgements: This work has been financed by Logilab SA, FRANCE, logilab.fr + +export namespace log { + /** + * Logs a debug event + * @param x The event + */ + function debug(x: any): void; + /** + * Logs a warning event + * @param x The event + */ + function warn(x: any): void; + /** + * Logs an information event + * @param x The event + */ + function info(x: any): void; + /** + * Logs an error event + * @param x The event + */ + function error(x: any): void; + /** + * Logs a success event + * @param x The event + */ + function success(x: any): void; + /** + * Logs a message event + * @param x The event + */ + function msg(x: any): void; +} + +export namespace convert { + /** + * Converts an n3 string to JSON + * @param n3String The n3 string + * @param jsonCallback Callback when the operation terminated + */ + function convertToJson( + n3String: string, + jsonCallback: (err: string, jsonString: string) => void + ): void; + /** + * Converts an n3 string to n-quads + * @param n3String The n3 string + * @param nquadCallback Callback when the operation terminated + */ + function convertToNQuads( + n3String: string, + nquadCallback: (err: string, nquadString: string) => void + ): void; +} + +export class Query { + pat: IndexedFormula; + name: string; + id?: string; + constructor(name: string, id?: any); + } + +export namespace Util { + /** + * Gets a named node for a media type + * @param mediaType A media type + */ + function mediaTypeClass(mediaType: string): NamedNode; + /** + * Gets a named node from the name of a relation + * @param relation The name of a relation + */ + function linkRelationProperty(relation: string): NamedNode; + /** + * Loads ontologies of the data we load (this is the callback from the kb to + * the fetcher). Exports as `AJAR_handleNewTerm` + * @param kb The store + * @param p A property + * @param requestedBy + */ + function AJAR_handleNewTerm( + kb: Formula, + p: NamedNode, + requestedBy: string + ): Promise; +} +/** +* A datatype-specific handler for fetching data +*/ +export interface Handler { + response: any; + dom: any; +} +export interface FetchOptions { + fetch?: typeof fetch; + /** + * The resource which referred to this (for tracking bad links). + */ + referringTerm?: NamedNode; + /** + * Provided content type (for writes). + */ + contentType?: string; + /** + * Override the incoming header to force the data to be treated as this content-type (for reads). + */ + forceContentType?: string; + /** + * Load the data even if loaded before. Also sets the `Cache-Control:` header to `no-cache`. + */ + force?: boolean; + /** + * Original uri to preserve through proxying etc (`xhr.original`). + */ + baseUri?: Node | string; + /** + * Whether this request is a retry via a proxy (generally done from an error handler). + */ + proxyUsed?: boolean; + /** + * Flag for XHR/CORS etc + */ + withCredentials?: boolean; + /** + * Before we parse new data, clear old, but only on status 200 responses. + */ + clearPreviousData?: boolean; + /** + * Prevents the addition of various metadata triples (about the fetch request) to the store. + */ + noMeta?: boolean; + noRDFa?: boolean; +} +/** +* Responsible for fetching RDF data +*/ +export class Fetcher { + store: any; + timeout: number; + appNode: BlankNode; + requested: { + [uri: string]: any; + }; + timeouts: any; + redirectedTo: any; + constructor(store: any, options: any); + static HANDLERS: { + RDFXMLHandler: Handler; + XHTMLHandler: Handler; + XMLHandler: Handler; + HTMLHandler: Handler; + TextHandler: Handler; + N3Handler: Handler; + }; + static CONTENT_TYPE_BY_EXT: { + [ext: string]: string; + }; + /** + * Loads a web resource or resources into the store. + * @param uri Resource to load, provided either as a NamedNode object or a plain URL. If multiple resources are passed as an array, they will be fetched in parallel. + */ + load: (uri: ReadonlyArray | ReadonlyArray | NamedNode | string, options?: FetchOptions) => Promise; +} +/** +* Gets a node for the specified input +* @param value An input value +*/ +export function term(value: ValueType): Node | Collection | ValueType; +/** +* Gets a namespace +* @param nsuri The URI for the namespace +*/ +export function Namespace(nsuri: string): (ln: string) => NamedNode; +/** +* Transforms an NTriples string format into a Node. +* The bnode bit should not be used on program-external values; designed +* for internal work such as storing a bnode id in an HTML attribute. +* This will only parse the strings generated by the vaious toNT() methods. +* @param str A string representation +*/ +export function fromNT(str: string): Node; +/** +* Creates a new fetcher +* @param store The store to use +* @param options The options +*/ +export function fetcher(store: Formula, options: any): Fetcher; +/** +* Creates a new graph (store) +*/ +export function graph(): IndexedFormula; +/** +* Creates a new literal node +* @param val The lexical value +* @param lang The language +* @param dt The datatype +*/ +export function lit(val: string, lang: string, dt: NamedNode): Literal; +/** +* Creates a new statement +* @param subject The subject +* @param predicate The predicate +* @param object The object +* @param graph The containing graph +*/ +export function st( + subject: Node | Date | string, + predicate: Node, + object: Node | Date | string, + graph: Node +): Statement; +/** +* Creates a new named node +* @param value The new named node +*/ +export function sym(value: string): NamedNode; +/** +* Creates a new variable +* @param name The name for the variable +*/ +export function variable(name: string): Variable; +/** +* Creates a new blank node +* @param value The blank node's identifier +*/ +export function blankNode(value: string): BlankNode; +/** +* Gets the default graph +*/ +export function defaultGraph(): DefaultGraph; +/** +* Creates a new literal node +* @param value The lexical value +* @param languageOrDatatype Either the language or the datatype +*/ +export function literal( + value: string, + languageOrDatatype: string | NamedNode +): Literal; +/** +* Creates a new named node +* @param value The new named node +*/ +export function namedNode(value: string): NamedNode; + +/** +* Creates a new statement +* @param subject The subject +* @param predicate The predicate +* @param object The object +*/ +export function triple(subject: Node, predicate: Node, object: Node): Statement; +/** +* Parse a string and put the result into the graph kb. +* Normal method is sync. +* Unfortunately jsdonld is currently written to need to be called async. +* Hence the mess below with executeCallback. +* @param str The input string to parse +* @param kb The store to use +* @param base The base URI to use +* @param contentType The content type for the input +* @param callback The callback to call when the data has been loaded +*/ +export function parse( + str: string, + kb: Formula, + base: string, + contentType: string, + callback: (error: any, kb: Formula) => void +): void; +/** +* Get the next available unique identifier +*/ +export let NextId: number; diff --git a/webpack.config.js b/webpack.config.js index 5198dab44..35324cd7a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path') const WrapperPlugin = require('wrapper-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = (env, args) => { return { @@ -26,6 +27,7 @@ module.exports = (env, args) => { } ] }, + resolve: { extensions: ['.js', '.ts'] }, plugins: [ new WrapperPlugin({ // Fall back to window.fetch when solid-auth-client is not present, @@ -37,6 +39,8 @@ module.exports = (env, args) => { window.solid.auth = { fetch: (a, b) => window.fetch(a, b) } }` }), + // Comment out the next line if you want to disable type checking. + new ForkTsCheckerWebpackPlugin() ], externals: { '@trust/webcrypto': 'crypto', From bbd382006f5cd2dd3f5915b3f518ce292fb76833 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Mon, 2 Dec 2019 14:20:07 +0100 Subject: [PATCH 06/29] Minimize circular dependencies --- src/data-factory-internal.ts | 6 +- src/data-factory.ts | 110 ++++------------------------- src/fetcher.ts | 3 +- src/index.ts | 3 +- src/named-node.ts | 2 +- src/query.js | 6 +- src/rdflib-data-factory.ts | 84 ++++++++++++++++++++++ src/serialize.ts | 2 +- src/statement.ts | 4 +- src/store.ts | 14 +++- src/types.ts | 22 +++++- src/update-manager.ts | 5 +- src/updates-via.js | 2 +- src/utils/default-graph-uri.ts | 6 ++ src/utils/termValue.ts | 10 +++ src/utils/terms.ts | 9 --- tests/unit/indexed-formula-test.js | 2 +- tests/unit/parse-test.js | 2 +- 18 files changed, 165 insertions(+), 127 deletions(-) create mode 100644 src/rdflib-data-factory.ts create mode 100644 src/utils/default-graph-uri.ts create mode 100644 src/utils/termValue.ts diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts index d0bd7f463..bff5d8e26 100644 --- a/src/data-factory-internal.ts +++ b/src/data-factory-internal.ts @@ -13,11 +13,11 @@ import { TFDataFactory, TFTerm, } from './types' -import { Feature, IdentityFactory, Indexable } from './data-factory-type' +import { defaultGraphNode } from './utils/default-graph-uri' +import { Feature, IdentityFactory } from './data-factory-type' import Node from './node-internal' -export const defaultGraphURI = 'chrome:theSession' -const defaultGraphNode = new NamedNode(defaultGraphURI) +export { defaultGraphURI } from './utils/default-graph-uri' /** * Creates a new blank node diff --git a/src/data-factory.ts b/src/data-factory.ts index 6f3bb22b8..eddafde04 100644 --- a/src/data-factory.ts +++ b/src/data-factory.ts @@ -1,11 +1,7 @@ import Collection from './collection' import CanonicalDataFactory from './data-factory-internal' -import Fetcher from './fetcher' -import Literal from './literal' -import Statement from './statement' -import { ValueType, TFNamedNode, SubjectType, PredicateType, ObjectType, GraphType, TFBlankNode, TFLiteral } from './types' -import IndexedFormula from './store' -import { DataFactory, Indexable, SupportTable } from './data-factory-type' +import { TFBlankNode, TFLiteral, TFNamedNode, ValueType } from './types' +import { Indexable, SupportTable } from './data-factory-type' import NamedNode from './named-node' const RDFlibjsSupports: SupportTable = { @@ -18,49 +14,6 @@ const RDFlibjsSupports: SupportTable = { ID: false, } -/** - * Data factory which also supports Collections - * - * Necessary for preventing circular dependencies. - */ -const ExtendedTermFactory = { - ...CanonicalDataFactory, - collection, - id, - supports: RDFlibjsSupports -} - -interface IRDFlibDataFactory extends DataFactory { - fetcher: (store: IndexedFormula, options: any) => Fetcher - graph: (features, opts) => IndexedFormula - lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal - st: ( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType, - graph?: GraphType - ) => Statement - triple: ( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType - ) => Statement -} - -/** Full RDFLib.js Data Factory - * @todo Add missing functions (isQuad, equals, toNQ), so Partial can be removed - */ -const RDFlibDataFactory: Partial = { - ...ExtendedTermFactory, - fetcher, - graph, - lit, - st, - triple, -} - -export default RDFlibDataFactory - function id (term: TFNamedNode | TFBlankNode | TFLiteral | Collection ): Indexable { if (!term) { return term @@ -85,54 +38,17 @@ function id (term: TFNamedNode | TFBlankNode | TFLiteral | Collection ): Indexab function collection(elements: ReadonlyArray): Collection { return new Collection(elements) } + /** - * Creates a new fetcher - * @param store - The store to use - * @param options - The options - */ -function fetcher(store: IndexedFormula, options: any): Fetcher { - return new Fetcher(store, options) -} -/** - * Creates a new graph (store) - */ -function graph (features = undefined, opts = undefined): IndexedFormula { - return new IndexedFormula(features, opts || { rdfFactory: ExtendedTermFactory }) -} -/** - * Creates a new literal node - * @param val The lexical value - * @param lang The language - * @param dt The datatype - */ -function lit(val: string, lang?: string, dt?: TFNamedNode): Literal { - return new Literal('' + val, lang, dt) -} -/** - * Creates a new statement - * @param subject The subject - * @param predicate The predicate - * @param object The object - * @param graph The containing graph - */ -function st( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType, - graph?: GraphType -): Statement { - return new Statement(subject, predicate, object, graph) -} -/** - * Creates a new statement - * @param subject The subject - * @param predicate The predicate - * @param object The object + * Data factory which also supports Collections + * + * Necessary for preventing circular dependencies. */ -function triple( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType -): Statement { - return CanonicalDataFactory.quad(subject, predicate, object) +const ExtendedTermFactory = { + ...CanonicalDataFactory, + collection, + id, + supports: RDFlibjsSupports } + +export default ExtendedTermFactory diff --git a/src/fetcher.ts b/src/fetcher.ts index 1d3d8539a..fc9030fab 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -34,8 +34,8 @@ import rdfParse from './parse' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import * as Uri from './uri' +import { isCollection, isTFNamedNode} from './utils/terms' import * as Util from './util' -import { isCollection, isTFNamedNode, termValue } from './utils/terms' import serialize from './serialize' // @ts-ignore This is injected @@ -51,6 +51,7 @@ import { TFDataFactory, TFPredicate } from './types' +import { termValue } from './utils/termValue' // This is a special fetch which does OIDC auth, catching 401 errors const fetch = typeof window === 'undefined' ? solidAuthCli : solidAuthClient diff --git a/src/index.ts b/src/index.ts index 1a921d305..c34404f78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,6 @@ import BlankNode from './blank-node' import Collection from './collection' import * as convert from './convert' -import DataFactory from './data-factory' import Empty from './empty' import Fetcher from './fetcher' import Formula from './formula' @@ -29,6 +28,7 @@ import { UpdatesVia } from './updates-via' import * as uri from './uri' import * as Util from './util' import Variable from './variable' +import DataFactory from './rdflib-data-factory' export * from './utils/terms' @@ -106,3 +106,4 @@ export { triple, variable, } +export { termValue } from './utils/termValue' diff --git a/src/named-node.ts b/src/named-node.ts index 2b333c0b1..c243b7e2e 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -2,7 +2,7 @@ import ClassOrder from './class-order' import Node from './node-internal' import { NamedNodeTermType, TermType, TFNamedNode } from './types' -import { termValue } from './utils/terms' +import { termValue } from './utils/termValue' /** * A named (IRI) RDF node diff --git a/src/query.js b/src/query.js index f45d60bcf..d5453374c 100644 --- a/src/query.js +++ b/src/query.js @@ -17,10 +17,8 @@ // Also, users name variables and want the same name back when stuff is printed /* jsl:option explicit */ // Turn on JavaScriptLint variable declaration checking -import { - default as IndexedFormula, - defaultGraphURI as defaultDocumentURI -} from './store' +import IndexedFormula from './store' +import { defaultGraphURI as defaultDocumentURI } from './utils/default-graph-uri' import log from './log' import { docpart } from './uri' diff --git a/src/rdflib-data-factory.ts b/src/rdflib-data-factory.ts new file mode 100644 index 000000000..154283575 --- /dev/null +++ b/src/rdflib-data-factory.ts @@ -0,0 +1,84 @@ +import { + GraphType, + IRDFlibDataFactory, + ObjectType, + PredicateType, + SubjectType, + TFNamedNode +} from './types' +import Literal from './literal' +import Statement from './statement' +import CanonicalDataFactory from './data-factory-internal' +import IndexedFormula from './store' +import Fetcher from './fetcher' +import ExtendedTermFactory from './data-factory' + +/** Full RDFLib.js Data Factory + * @todo Add missing functions (isQuad, equals, toNQ), so Partial can be removed + */ +const RDFlibDataFactory: Partial = { + ...ExtendedTermFactory, + fetcher, + graph, + lit, + st, + triple, +} + +/** + * Creates a new fetcher + * @param store - The store to use + * @param options - The options + */ +function fetcher (store: IndexedFormula, options: any): Fetcher { + return new Fetcher(store, options) +} + +/** + * Creates a new graph (store) + */ +function graph (features = undefined, opts = undefined): IndexedFormula { + return new IndexedFormula(features, opts || {rdfFactory: ExtendedTermFactory}) +} + +/** + * Creates a new literal node + * @param val The lexical value + * @param lang The language + * @param dt The datatype + */ +function lit (val: string, lang?: string, dt?: TFNamedNode): Literal { + return new Literal('' + val, lang, dt) +} + +/** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The containing graph + */ +function st ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType +): Statement { + return new Statement(subject, predicate, object, graph) +} + +/** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + */ +function triple ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType +): Statement { + return CanonicalDataFactory.quad(subject, predicate, object) +} + +export default RDFlibDataFactory diff --git a/src/serialize.ts b/src/serialize.ts index b380b8c7b..80d512048 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,8 +1,8 @@ import * as convert from './convert' +import Formula from './formula' import Serializer from './serializer' import { ContentType, TFNamedNode, TFBlankNode } from './types' import IndexedFormula from './store' -import { Formula } from './index' /** * Serialize to the appropriate format diff --git a/src/statement.ts b/src/statement.ts index 67c7c33d5..4e8f4a696 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -1,4 +1,3 @@ -import { defaultGraph } from './data-factory-internal' import NamedNode from './named-node' import Node from './node-internal' import { @@ -11,6 +10,7 @@ import { TFQuad, } from './types' import Literal from './literal' +import { defaultGraphNode } from './utils/default-graph-uri' /** A Statement represents an RDF Triple or Quad. */ export default class Statement implements TFQuad { @@ -55,7 +55,7 @@ export default class Statement implements TFQuad = TFTerm | undefined | null | Collection + +export interface IRDFlibDataFactory extends DataFactory { + fetcher: (store: IndexedFormula, options: any) => Fetcher + graph: (features, opts) => IndexedFormula + lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal + st: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType + ) => Statement + triple: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType + ) => Statement +} diff --git a/src/update-manager.ts b/src/update-manager.ts index f98a040e7..08df881ff 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -10,11 +10,12 @@ import Fetcher from './fetcher' import Namespace from './namespace' import Serializer from './serializer' import { join as uriJoin } from './uri' -import { isStore, isTFBlankNode, termValue } from './utils/terms' +import { isStore, isTFBlankNode } from './utils/terms' import * as Util from './util' import Statement from './statement' -import { NamedNode } from './index' +import NamedNode from './named-node' import { TFNamedNode, TFQuad, TFBlankNode, TFSubject, TFPredicate, TFObject, TFGraph, TFTerm } from './types' +import { termValue } from './utils/termValue' interface UpdateManagerFormula extends IndexedFormula { fetcher: Fetcher diff --git a/src/updates-via.js b/src/updates-via.js index 981f54f1f..0ea1aab37 100644 --- a/src/updates-via.js +++ b/src/updates-via.js @@ -1,7 +1,7 @@ /* * Updates-Via */ -import DataFactory from './data-factory' +import DataFactory from './rdflib-data-factory' export class UpdatesSocket { constructor (parent, via) { diff --git a/src/utils/default-graph-uri.ts b/src/utils/default-graph-uri.ts new file mode 100644 index 000000000..6186956c8 --- /dev/null +++ b/src/utils/default-graph-uri.ts @@ -0,0 +1,6 @@ +// Prevents circular dependencies between data-factory-internal and statement + +import NamedNode from '../named-node' + +export const defaultGraphURI = 'chrome:theSession' +export const defaultGraphNode = new NamedNode(defaultGraphURI) diff --git a/src/utils/termValue.ts b/src/utils/termValue.ts new file mode 100644 index 000000000..8c34f1504 --- /dev/null +++ b/src/utils/termValue.ts @@ -0,0 +1,10 @@ +import { TFTerm } from '../types' + +/** Retrieve the value of a term, or self if already a string. */ +export function termValue (node: TFTerm | string): string { + if (typeof node === 'string') { + return node + } + + return node.value +} diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 80aee4cd2..4d8deeb2f 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -29,15 +29,6 @@ export function isNamedNode(obj): obj is NamedNode { return isTFTerm(obj) && obj.termType === 'NamedNode' } -/** Retrieve the value of a term, or self if already a string. */ -export function termValue(node: TFTerm | string): string { - if (typeof node === 'string') { - return node - } - - return node.value -} - /** TypeGuard for RDFLib Collections */ export function isCollection(obj: any): obj is Collection { return isTFTerm(obj) diff --git a/tests/unit/indexed-formula-test.js b/tests/unit/indexed-formula-test.js index 0bc02ca39..eef7b1204 100644 --- a/tests/unit/indexed-formula-test.js +++ b/tests/unit/indexed-formula-test.js @@ -5,8 +5,8 @@ import Formula from '../../src/formula' import IndexedFormula from '../../src/store' import NamedNode from '../../src/named-node' -import DataFactory from '../../src/data-factory' import { RDFArrayRemove } from '../../src/util' +import DataFactory from '../../src/rdflib-data-factory' describe('IndexedFormula', () => { const g0 = NamedNode.fromValue('https://example.com/graph0') diff --git a/tests/unit/parse-test.js b/tests/unit/parse-test.js index 5b0d73b7c..2bc3a3c0a 100644 --- a/tests/unit/parse-test.js +++ b/tests/unit/parse-test.js @@ -3,8 +3,8 @@ import { expect } from 'chai' import parse from '../../src/parse' import CanonicalDataFactory from '../../src/data-factory-internal' -import DataFactory from '../../src/data-factory' import defaultXSD from '../../src/xsd' +import DataFactory from '../../src/rdflib-data-factory' describe('Parse', () => { describe('ttl', () => { From 595e02b7af615e8132460fbe5e1a8ec903b02490 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Mon, 2 Dec 2019 18:02:21 +0100 Subject: [PATCH 07/29] Remove and simplify some generics, improve `identity` documentation --- src/collection.ts | 15 ++- src/data-factory-internal.ts | 235 +++++++++++++++++------------------ src/data-factory-type.ts | 75 ++++------- src/data-factory.ts | 63 ++++------ src/formula.ts | 5 +- src/rdflib-data-factory.ts | 111 ++++++++--------- src/statement.ts | 15 ++- src/store.ts | 8 +- src/types.ts | 75 ++++++----- tsconfig.json | 4 +- 10 files changed, 283 insertions(+), 323 deletions(-) diff --git a/src/collection.ts b/src/collection.ts index 16125ef21..63c4e3a53 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -2,7 +2,14 @@ import BlankNode from './blank-node' import ClassOrder from './class-order' import Literal from './literal' import Node from './node-internal' -import { Bindings, FromValueReturns, TermType, ValueType } from './types' +import { + Bindings, + CollectionTermType, + FromValueReturns, + TermType, + TFTerm, + ValueType +} from './types' import Variable from './variable' import { isTFTerm } from './utils/terms' @@ -35,7 +42,9 @@ export function fromValue = any, C extends Node = * * Use generic T to control the contents of the array. */ -export default class Collection | Literal | Variable> extends Node { +export default class Collection< + T extends Node = Node | BlankNode | Collection | Literal | Variable +> extends Node implements TFTerm { static termType = TermType.Collection classOrder = ClassOrder.Collection @@ -46,7 +55,7 @@ export default class Collection) { super((BlankNode.nextId++).toString()) diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts index bff5d8e26..06077d745 100644 --- a/src/data-factory-internal.ts +++ b/src/data-factory-internal.ts @@ -10,23 +10,15 @@ import { ObjectType, GraphType, TermType, - TFDataFactory, TFTerm, } from './types' import { defaultGraphNode } from './utils/default-graph-uri' -import { Feature, IdentityFactory } from './data-factory-type' +import { DataFactory, DefaultFactoryTypes, Feature } from './data-factory-type' import Node from './node-internal' +import Collection from './collection' export { defaultGraphURI } from './utils/default-graph-uri' -/** - * Creates a new blank node - * @param value The blank node's identifier - */ -function blankNode(value?: string): BlankNode { - return new BlankNode(value) -} - /** * Gets the default graph */ @@ -34,118 +26,11 @@ export function defaultGraph(): NamedNode { return defaultGraphNode } -/** - * Generates a unique identifier for the object. - * - * Equivalent to {Term.hashString} - */ -function id (term: TFTerm): string | undefined { - if (!term) { - return term - } - if (Object.prototype.hasOwnProperty.call(term, "id") && typeof (term as NamedNode).id === "function") { - return (term as NamedNode).id() - } - if (Object.prototype.hasOwnProperty.call(term, "hashString")) { - return (term as Node).hashString() - } - - switch (term.termType) { - case TermType.NamedNode: - return '<' + term.value + '>' - case TermType.BlankNode: - return '_:' + term.value - case TermType.Literal: - return Literal.toNT(term as Literal) - case TermType.Variable: - return Variable.toString(term) - default: - return undefined - } -} - -/** - * Creates a new literal node - * @param value The lexical value - * @param languageOrDatatype Either the language or the datatype - */ -function literal( - value: string, - languageOrDatatype?: string | TFNamedNode -): Literal { - if (typeof value !== "string" && !languageOrDatatype) { - return Literal.fromValue(value) as Literal - } - - const strValue = typeof value === 'string' ? value : '' + value - if (typeof languageOrDatatype === 'string') { - if (languageOrDatatype.indexOf(':') === -1) { - return new Literal(strValue, languageOrDatatype) - } else { - return new Literal(strValue, null, namedNode(languageOrDatatype)) - } - } else { - return new Literal(strValue, null, languageOrDatatype) - } -} - -/** - * Creates a new named node - * @param value The new named node - */ -function namedNode(value: string): NamedNode { - return new NamedNode(value) -} - -/** -* Creates a new statement -* @param subject The subject -* @param predicate The predicate -* @param object The object -* @param graph The containing graph -*/ -function quad( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType, - graph?: GraphType -): Statement { - graph = graph || defaultGraph() - return new Statement(subject, predicate, object, graph) -} - -/** - * Creates a new variable - * @param name The name for the variable - */ -function variable(name?: string): Variable { - return new Variable(name) -} - /** The internal RDFlib datafactory, which uses Collections */ -const CanonicalDataFactory: TFDataFactory< - NamedNode, - BlankNode, - Literal, - SubjectType, - PredicateType, - ObjectType, - GraphType, - // DefaultGraph is: - NamedNode | BlankNode, - Statement -> & IdentityFactory < - Statement, - NamedNode | BlankNode | Literal | Variable +const CanonicalDataFactory: DataFactory< + DefaultFactoryTypes & Collection & Variable > = { - blankNode, - defaultGraph, - literal, - namedNode, - quad, - variable, - // @ts-ignore id should return an Indexable value, but in RDFlib can return `undefined` - id, + supports: { [Feature.collections]: false, [Feature.defaultGraphType]: true, @@ -154,7 +39,115 @@ const CanonicalDataFactory: TFDataFactory< [Feature.id]: true, [Feature.reversibleId]: false, [Feature.variableType]: true, - } + }, + + + /** + * Creates a new blank node + * @param value The blank node's identifier + */ + blankNode(value?: string): BlankNode { + return new BlankNode(value) + }, + + defaultGraph, + + /** + * Generates a uniquely identifiably idempotent string for the given {term}. + * + * Equivalent to {Term.hashString} + * + * @example Use this to associate data with a term in an object + * { obj[id(term)] = "myData" } + */ + id (term: TFTerm): string { + if (!term) { + return term + } + if (typeof term === "object" && term !== null && "id" in term && typeof term['id'] === "function") { + return (term as NamedNode).id() + } + if (Object.prototype.hasOwnProperty.call(term, "hashString")) { + return (term as Node).hashString() + } + + switch (term.termType) { + case TermType.BlankNode: + return '_:' + term.value + case TermType.Collection: + return Collection.toNT(term) + case TermType.DefaultGraph: + return 'defaultGraph' + case TermType.Empty: + return '()' + case TermType.Literal: + return Literal.toNT(term as Literal) + case TermType.NamedNode: + return '<' + term.value + '>' + case TermType.Variable: + return Variable.toString(term) + default: + throw new Error(`Can't id term with type '${term.termType}', add 'id' on your instance to override`) + } + }, + + /** + * Creates a new literal node + * @param value The lexical value + * @param languageOrDatatype Either the language or the datatype + */ + literal( + value: string, + languageOrDatatype?: string | TFNamedNode + ): Literal { + if (typeof value !== "string" && !languageOrDatatype) { + return Literal.fromValue(value) as Literal + } + + const strValue = typeof value === 'string' ? value : '' + value + if (typeof languageOrDatatype === 'string') { + if (languageOrDatatype.indexOf(':') === -1) { + return new Literal(strValue, languageOrDatatype) + } else { + return new Literal(strValue, null, this.namedNode(languageOrDatatype)) + } + } else { + return new Literal(strValue, null, languageOrDatatype) + } + }, + + /** + * Creates a new named node + * @param value The new named node + */ + namedNode(value: string): NamedNode { + return new NamedNode(value) + }, + + /** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The containing graph + */ + quad( + subject: TFTerm | SubjectType, + predicate: TFTerm | PredicateType, + object: TFTerm | ObjectType, + graph?: TFTerm | GraphType + ): Statement { + graph = graph || defaultGraph() + return new Statement(subject, predicate, object, graph) + }, + + /** + * Creates a new variable + * @param name The name for the variable + */ + variable(name?: string): Variable { + return new Variable(name) + }, } /** Contains the factory methods as defined in the spec, plus id */ diff --git a/src/data-factory-type.ts b/src/data-factory-type.ts index 7e0e5a185..a5256926a 100644 --- a/src/data-factory-type.ts +++ b/src/data-factory-type.ts @@ -1,32 +1,28 @@ -import { TFNamedNode, TFBlankNode, TFLiteral, TFQuad, TFTerm, TFDataFactory, TFSubject, TFPredicate, TFObject, TFGraph, TFVariable } from "./types" +import { + TFNamedNode, + TFBlankNode, + TFLiteral, + TFQuad, + TFTerm, + TFVariable, + TFDataFactory, +} from './types' +import Literal from './literal' +import Statement from './statement' +import NamedNode from './named-node' +import BlankNode from './blank-node' + +export type DefaultFactoryTypes = NamedNode | BlankNode | Literal | Statement /** - * Defines a strict subset of the DataFactory as defined in the RDF/JS: Data model specification - * Non RDF-native features have been removed (e.g. no Variable, no Literal as predicate, etc.). + * Defines a DataFactory as used in rdflib, based on the RDF/JS: Data model specification, + * but with additional extensions * bnIndex is optional but useful. */ export interface DataFactory< - NamedNode extends TFNamedNode = TFNamedNode, - BlankNode extends TFBlankNode = TFBlankNode, - Literal extends TFLiteral = TFLiteral, - Quad = TFQuad, - FactoryTypes = NamedNode | TFBlankNode | Literal | Quad, - Subject = TFSubject, - Predicate = TFPredicate, - Object = TFObject, - Graph = TFGraph, - DefaultGraph = NamedNode | BlankNode, -> extends TFDataFactory< - NamedNode, - BlankNode, - Literal, - Subject, - Predicate, - Object, - Graph, - DefaultGraph, - Quad -> { + FactoryTypes = DefaultFactoryTypes, + IndexType = Indexable +> extends TFDataFactory { /** * BlankNode index * @private @@ -37,20 +33,12 @@ export interface DataFactory< literal(value: string, languageOrDatatype?: string | TFNamedNode): Literal - isQuad(obj: any): obj is Quad + isQuad(obj: any): obj is Statement equals(a: Comparable, b: Comparable): boolean toNQ(term: FactoryTypes): string -} -export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm - -export interface IdentityFactory< - Quad = TFQuad, - IDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | Quad | TFVariable | TFTerm, - IndexType = Indexable, -> { /** * Generates a unique session-idempotent identifier for the given object. * @@ -59,22 +47,10 @@ export interface IdentityFactory< * * @return {Indexable} A unique value which must also be a valid JS object key type. */ - id(obj: IDFactoryTypes): IndexType + id(obj: FactoryTypes): IndexType } -/** - * Factory type which supports reverse id lookups. - * - * It should be able to resolve the value for any given id which it handed out. Passing an id not - * generated by the same instance might result in a value or an exception depending on the - * implementation. - */ -export interface ReversibleIdentityFactory< - IndexType = Indexable, - FactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad -> extends IdentityFactory { - fromId(id: IndexType): FactoryTypes; -} +export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm export type Namespace = (term:string) => TFNamedNode export type NamespaceCreator = (ns: string) => Namespace @@ -91,7 +67,10 @@ export enum Feature { equalsMethod = "EQUALS_METHOD", /** Whether the factory can create a unique idempotent identifier for the given term. */ id = "ID", - /** Whether the factory will return the same instance for subsequent calls (implies `===`). */ + /** + * Whether the factory will return the same instance for subsequent calls. + * This implies `===`, which means methods like `indexOf` can be used. + */ identity = "IDENTITY", /** Whether the factory supports mapping ids back to instances (should adhere to the identity setting) */ reversibleId = "REVERSIBLE_ID", diff --git a/src/data-factory.ts b/src/data-factory.ts index eddafde04..35c7a6030 100644 --- a/src/data-factory.ts +++ b/src/data-factory.ts @@ -1,42 +1,10 @@ import Collection from './collection' import CanonicalDataFactory from './data-factory-internal' -import { TFBlankNode, TFLiteral, TFNamedNode, ValueType } from './types' -import { Indexable, SupportTable } from './data-factory-type' -import NamedNode from './named-node' +import { ValueType } from './types' +import { DataFactory, Feature } from './data-factory-type' -const RDFlibjsSupports: SupportTable = { - COLLECTIONS: true, - DEFAULT_GRAPH_TYPE: true, - EQUALS_METHOD: true, - VARIABLE_TYPE: true, - IDENTITY: false, - REVERSIBLE_ID: false, - ID: false, -} - -function id (term: TFNamedNode | TFBlankNode | TFLiteral | Collection ): Indexable { - if (!term) { - return term - } - if (Object.prototype.hasOwnProperty.call(term, "id") && typeof (term as NamedNode).id === "function") { - return (term as NamedNode).id() - } - if (Object.prototype.hasOwnProperty.call(term, "hashString")) { - return (term as NamedNode).hashString() - } - - if (term.termType === "Collection") { - Collection.toNT(term) - } - - return CanonicalDataFactory.id(term as NamedNode) -} -/** - * Creates a new collection - * @param elements - The initial element - */ -function collection(elements: ReadonlyArray): Collection { - return new Collection(elements) +interface CollectionFactory extends DataFactory { + collection(elements: ReadonlyArray): Collection } /** @@ -44,11 +12,26 @@ function collection(elements: ReadonlyArray): Collection { * * Necessary for preventing circular dependencies. */ -const ExtendedTermFactory = { +const ExtendedTermFactory: CollectionFactory = { ...CanonicalDataFactory, - collection, - id, - supports: RDFlibjsSupports + + supports: { + [Feature.collections]: true, + [Feature.defaultGraphType]: false, + [Feature.equalsMethod]: true, + [Feature.identity]: false, + [Feature.id]: true, + [Feature.reversibleId]: false, + [Feature.variableType]: true, + }, + + /** + * Creates a new collection + * @param elements - The initial element + */ + collection (elements: ReadonlyArray): Collection { + return new Collection(elements) + }, } export default ExtendedTermFactory diff --git a/src/formula.ts b/src/formula.ts index 573a36a02..2985072a3 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -12,6 +12,7 @@ import { Bindings, TermType, TFBlankNode, + TFDataFactory, TFGraph, TFObject, TFPredicate, @@ -22,8 +23,6 @@ import { import { isStatement } from './utils/terms' import Variable from './variable' import { - DataFactory, - IdentityFactory, Indexable, TFIDFactoryTypes } from './data-factory-type' @@ -36,7 +35,7 @@ export function isFormula(value: T | TFTerm): value is Formula { export interface FormulaOpts { dataCallback?: (q: TFQuad) => void rdfArrayRemove?: (arr: TFQuad[], q: TFQuad) => void - rdfFactory?: IdentityFactory & DataFactory + rdfFactory?: TFDataFactory } interface BooleanMap { diff --git a/src/rdflib-data-factory.ts b/src/rdflib-data-factory.ts index 154283575..2238a8052 100644 --- a/src/rdflib-data-factory.ts +++ b/src/rdflib-data-factory.ts @@ -8,7 +8,6 @@ import { } from './types' import Literal from './literal' import Statement from './statement' -import CanonicalDataFactory from './data-factory-internal' import IndexedFormula from './store' import Fetcher from './fetcher' import ExtendedTermFactory from './data-factory' @@ -16,69 +15,65 @@ import ExtendedTermFactory from './data-factory' /** Full RDFLib.js Data Factory * @todo Add missing functions (isQuad, equals, toNQ), so Partial can be removed */ -const RDFlibDataFactory: Partial = { +const RDFlibDataFactory: IRDFlibDataFactory = { ...ExtendedTermFactory, - fetcher, - graph, - lit, - st, - triple, -} -/** - * Creates a new fetcher - * @param store - The store to use - * @param options - The options - */ -function fetcher (store: IndexedFormula, options: any): Fetcher { - return new Fetcher(store, options) -} + /** + * Creates a new fetcher + * @param store - The store to use + * @param options - The options + */ + fetcher (store: IndexedFormula, options: any): Fetcher { + return new Fetcher(store, options) + }, -/** - * Creates a new graph (store) - */ -function graph (features = undefined, opts = undefined): IndexedFormula { - return new IndexedFormula(features, opts || {rdfFactory: ExtendedTermFactory}) -} + /** + * Creates a new graph (store) + */ + graph (features = undefined, opts = undefined): IndexedFormula { + return new IndexedFormula(features, opts || {rdfFactory: ExtendedTermFactory}) + }, -/** - * Creates a new literal node - * @param val The lexical value - * @param lang The language - * @param dt The datatype - */ -function lit (val: string, lang?: string, dt?: TFNamedNode): Literal { - return new Literal('' + val, lang, dt) -} + /** + * Creates a new literal node + * @param val The lexical value + * @param lang The language + * @param dt The datatype + */ + lit (val: string, lang?: string, dt?: TFNamedNode): Literal { + return new Literal('' + val, lang, dt) + }, -/** - * Creates a new statement - * @param subject The subject - * @param predicate The predicate - * @param object The object - * @param graph The containing graph - */ -function st ( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType, - graph?: GraphType -): Statement { - return new Statement(subject, predicate, object, graph) -} + /** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @param graph The containing graph + */ + st ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType + ): Statement { + return new Statement(subject, predicate, object, graph) + }, -/** - * Creates a new statement - * @param subject The subject - * @param predicate The predicate - * @param object The object - */ -function triple ( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType -): Statement { - return CanonicalDataFactory.quad(subject, predicate, object) + /** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + */ + triple ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType + ): Statement { + // Statement is inherited from data-factory-internal#quad + return this.quad(subject, predicate, object) as Statement + }, } export default RDFlibDataFactory diff --git a/src/statement.ts b/src/statement.ts index 4e8f4a696..f5b17f160 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -7,7 +7,12 @@ import { PredicateType, SubjectType, TermType, + TFGraph, + TFObject, + TFPredicate, TFQuad, + TFSubject, + TFTerm, } from './types' import Literal from './literal' import { defaultGraphNode } from './utils/default-graph-uri' @@ -47,15 +52,15 @@ export default class Statement implements TFQuad { subject: S predicate: P object: O graph: G - equals(other: TFQuad): boolean } /** @@ -139,7 +138,6 @@ export interface TFLiteral extends TFTerm { language: string /** A NamedNode whose IRI represents the datatype of the literal. */ datatype: TFNamedNode - equals(other: TFTerm): boolean } /** @@ -151,11 +149,6 @@ export interface TFVariable extends TFTerm { termType: VariableTermType /** The name of the variable without leading "?" (example: "a"). */ value: string - /** - * Returns true if all general Term.equals conditions hold and term.value - * is the same string as other.value; otherwise, it returns false. - */ - equals(other: TFTerm): boolean } /** @@ -168,43 +161,40 @@ export interface TFDefaultGraph extends TFTerm { termType: DefaultGraphTermType; /** should return and empty string'' */ value: string; - equals(other: TFTerm): boolean } /** * RDF/JS taskforce DataFactory + * + * Not 100% compliant due to to practicality problems. + * * @link https://rdf.js.org/data-model-spec/#datafactory-interface */ -export interface TFDataFactory< - DFNamedNode extends TFNamedNode = TFNamedNode, - DFBlankNode extends TFBlankNode = TFBlankNode, - DFLiteral extends TFLiteral = TFLiteral, - DFSubject = TFSubject, - DFPredicate = TFPredicate, - DFObject = TFObject, - DFGraph = TFGraph, - DFDefaultGraph = TFDefaultGraph, - DFQuad = TFQuad, -> { +export interface TFDataFactory { /** Returns a new instance of NamedNode. */ - namedNode: (value: string) => DFNamedNode, + namedNode: (value: string) => TFNamedNode, + /** * Returns a new instance of BlankNode. * If the value parameter is undefined a new identifier for the * blank node is generated for each call. */ - blankNode: (value?: string) => DFBlankNode, + blankNode: (value?: string) => TFBlankNode, + /** * Returns a new instance of Literal. * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. * Otherwise languageOrDatatype is used for the value of language. */ - literal: (value: string, languageOrDatatype: string | DFNamedNode) => DFLiteral, + literal: (value: string, languageOrDatatype: string | TFNamedNode) => TFLiteral, + /** Returns a new instance of Variable. This method is optional. */ variable?: (value: string) => TFVariable, + /** * Returns an instance of DefaultGraph. */ - defaultGraph: () => DFDefaultGraph, + defaultGraph: () => TFDefaultGraph | TFNamedNode | TFBlankNode, + /** * Returns a new instance of the specific Term subclass given by original.termType * (e.g., NamedNode, BlankNode, Literal, etc.), @@ -212,22 +202,27 @@ export interface TFDataFactory< * Not implemented in RDFJS, so optional. */ fromTerm?: (original: TFTerm) => TFTerm + /** * Returns a new instance of Quad, such that newObject.equals(original) returns true. * Not implemented in RDFJS, so optional. */ - fromQuad?: (original: DFQuad) => DFQuad + fromQuad?: (original: TFQuad) => TFQuad + /** * Returns a new instance of Quad. * If graph is undefined or null it MUST set graph to a DefaultGraph. */ quad: ( - subject: DFSubject, - predicate: DFPredicate, - object: DFObject, - graph?: DFGraph, - ) => DFQuad + subject: TFTerm, + predicate: TFTerm, + object: TFTerm, + graph?: TFTerm, + ) => TFQuad + /** + * Check for specific features/behaviour on the factory. + * * This does not exist on the original RDF/JS spec */ supports: SupportTable @@ -249,7 +244,9 @@ export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable /** All the types that a .fromValue() method might return */ export type FromValueReturns = TFTerm | undefined | null | Collection -export interface IRDFlibDataFactory extends DataFactory { +export interface IRDFlibDataFactory extends DataFactory< + NamedNode | BlankNode | Literal | Collection | Statement +> { fetcher: (store: IndexedFormula, options: any) => Fetcher graph: (features, opts) => IndexedFormula lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal diff --git a/tsconfig.json b/tsconfig.json index 0ce1aedf8..bb63696f6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,8 @@ "allowSyntheticDefaultImports": true, "checkJs": false, "allowJs": true, - "target": "es5", - "module": "es6", + "target": "es2019", + "module": "esnext", "strict": true, "noImplicitAny": false, "lib": [ From 038ed764c65b370807abfe6d1be4ed07c662d213 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Tue, 3 Dec 2019 09:31:07 +0100 Subject: [PATCH 08/29] Patch up the test suite The inclusion of dirty-chai requires all `.to.be.false/true` assertions to be called explicitly. --- package-lock.json | 281 +++++++++++++++++++-------------- package.json | 3 +- tests/unit/fetcher-egp-test.js | 6 +- tests/unit/fetcher-test.js | 76 ++++----- tests/unit/literal-test.js | 16 +- tests/unit/parse-test.js | 4 +- tests/unit/query-test.js | 8 +- tests/unit/typings-test.ts | 2 +- 8 files changed, 219 insertions(+), 177 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebe5a3c07..54249ecd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1197,11 +1197,20 @@ } }, "@types/chai": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.3.tgz", - "integrity": "sha512-VRw2xEGbll3ZiTQ4J02/hUjNqZoue1bMhoo2dgM2LXjDdyaq4q80HgBDHwpI0/VKlo4Eg+BavyQMv/NYgTetzA==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.6.tgz", + "integrity": "sha512-HF8faEUA4JurIm+68VaA2KedtZf5LYdXpQEAbIAN79DwWQbO82BNTksZgCH3UMqbZHXex9C6TrBfg7OUInRISQ==", "dev": true }, + "@types/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, "@types/connect": { "version": "3.4.32", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", @@ -1211,6 +1220,16 @@ "@types/node": "*" } }, + "@types/dirty-chai": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/dirty-chai/-/dirty-chai-2.0.2.tgz", + "integrity": "sha512-BruwIN/UQEU0ePghxEX+OyjngpOfOUKJQh3cmfeq2h2Su/g001iljVi3+Y2y2EFp3IPgjf4sMrRU33Hxv1FUqw==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/chai-as-promised": "*" + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -3076,23 +3095,35 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.2.tgz", + "integrity": "sha512-jYo/J8XU2emLXl3OLwfwtuFfuF2w6DYPs+xy9ZfVyPkDcrauu6LYrw/q2TyCtrbc/KUdCiC5e9UajRhgNkVopA==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", + "has-symbols": "^1.0.1", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + } } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -3510,9 +3541,9 @@ }, "dependencies": { "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", "dev": true } } @@ -4474,9 +4505,9 @@ "dev": true }, "handlebars": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.2.tgz", - "integrity": "sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -4603,9 +4634,9 @@ "dev": true }, "highlight.js": { - "version": "9.15.10", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", - "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.16.2.tgz", + "integrity": "sha512-feMUrVLZvjy0oC7FVJQcSQRqbBq9kwqnYE4+Kj9ZjbHh3g+BisiPgF49NyQbVLNdrL/qqZr3Ca9yOKwgn2i/tw==", "dev": true }, "hmac-drbg": { @@ -5054,12 +5085,20 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + } } }, "is-typedarray": { @@ -5364,9 +5403,9 @@ } }, "lunr": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.7.tgz", - "integrity": "sha512-HjFSiy0Y0qZoW5OA1I6qBi7OnsDdqQnaUr03jhorh30maQoaP+4lQCKklYE3Nq3WJMSUfuBl6N+bKY5wxCb9hw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.8.tgz", + "integrity": "sha512-oxMeX/Y35PNFuZoHp+jUj5OSEmLCaIH4KTFJh7a93cHBoFmpw2IoPs22VIz7vyO2YUnx2Tn9dzIwO2P/4quIRg==", "dev": true }, "make-dir": { @@ -5668,9 +5707,9 @@ } }, "mocha": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", - "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz", + "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -5693,9 +5732,9 @@ "supports-color": "6.0.0", "which": "1.3.1", "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" + "yargs": "13.3.0", + "yargs-parser": "13.1.1", + "yargs-unparser": "1.6.0" }, "dependencies": { "debug": { @@ -5742,12 +5781,6 @@ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, "supports-color": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", @@ -6212,6 +6245,12 @@ } } }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -7961,6 +8000,26 @@ "strip-ansi": "^4.0.0" } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -7984,6 +8043,12 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8242,41 +8307,33 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typedoc": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.15.0.tgz", - "integrity": "sha512-NOtfq5Tis4EFt+J2ozhVq9RCeUnfEYMFKoU6nCXCXUULJz1UQynOM+yH3TkfZCPLzigbqB0tQYGVlktUWweKlw==", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.15.3.tgz", + "integrity": "sha512-RGX+dgnm9fyg5KHj81/ZhMiee0FfvJnjBXedhedhMWlrtM4YRv3pn8sYCWRt5TMi1Jli3/JG224pbFo3/3uaGw==", "dev": true, "requires": { "@types/minimatch": "3.0.3", "fs-extra": "^8.1.0", - "handlebars": "^4.1.2", - "highlight.js": "^9.15.8", + "handlebars": "^4.5.3", + "highlight.js": "^9.16.2", "lodash": "^4.17.15", "marked": "^0.7.0", "minimatch": "^3.0.0", "progress": "^2.0.3", "shelljs": "^0.8.3", - "typedoc-default-themes": "^0.6.0", - "typescript": "3.5.x" - }, - "dependencies": { - "typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", - "dev": true - } + "typedoc-default-themes": "^0.6.1", + "typescript": "3.7.x" } }, "typedoc-default-themes": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.6.0.tgz", - "integrity": "sha512-MdTROOojxod78CEv22rIA69o7crMPLnVZPefuDLt/WepXqJwgiSu8Xxq+H36x0Jj3YGc7lOglI2vPJ2GhoOybw==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.6.1.tgz", + "integrity": "sha512-z5AWKqQDz7igl9WkUuafx8cEm4MPVQGMpbWE+3lwVOaq+U4UoLKBMnpFQWh/4fqQ3bGysXpOstMxy2OOzHezyw==", "dev": true, "requires": { "backbone": "^1.4.0", "jquery": "^3.4.1", - "lunr": "^2.3.6", + "lunr": "^2.3.8", "underscore": "^1.9.1" } }, @@ -8287,16 +8344,23 @@ "dev": true }, "uglify-js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.1.tgz", - "integrity": "sha512-+dSJLJpXBb6oMHP+Yvw8hUgElz4gLTh82XuX68QiJVTXaE5ibl6buzhNkQdYhBlIhozWOC9ge16wyRmjG4TwVQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.1.tgz", + "integrity": "sha512-pnOF7jY82wdIhATVn87uUY/FHU+MDUdPLkmGFvGoclQmeu229eTkbG5gjGGBi3R7UuYYSEeYXY/TTY5j2aym2g==", "dev": true, "optional": true, "requires": { - "commander": "2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9069,22 +9133,21 @@ "dev": true }, "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", "dev": true, "requires": { - "cliui": "^4.0.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^13.1.1" }, "dependencies": { "ansi-regex": { @@ -9093,6 +9156,17 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -9112,13 +9186,24 @@ "requires": { "ansi-regex": "^4.1.0" } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } } } }, "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -9126,58 +9211,14 @@ } }, "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", "dev": true, "requires": { "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" - }, - "dependencies": { - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "lodash": "^4.17.15", + "yargs": "^13.3.0" } } } diff --git a/package.json b/package.json index afcdfe146..e1778002f 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@babel/preset-typescript": "^7.6.0", "@babel/register": "^7.5.5", "@types/chai": "^4.2.3", + "@types/dirty-chai": "^2.0.2", "@types/express": "^4.17.1", "@types/jsonld": "^1.5.0", "@types/mocha": "^5.2.7", @@ -70,7 +71,7 @@ "dirty-chai": "^2.0.1", "fork-ts-checker-webpack-plugin": "^1.6.0", "fs-grep": "^0.0.5", - "mocha": "^6.2.0", + "mocha": "^6.2.2", "nock": "^10.0.6", "node-fetch": "^2.6.0", "node-mkdirp": "0.0.1", diff --git a/tests/unit/fetcher-egp-test.js b/tests/unit/fetcher-egp-test.js index 7e38a3b69..05e20277b 100644 --- a/tests/unit/fetcher-egp-test.js +++ b/tests/unit/fetcher-egp-test.js @@ -22,7 +22,7 @@ describe('Fetcher', () => { let kb = rdf.graph(); let fetcher = rdf.fetcher(kb, {a:1}) fetcher.nowOrWhenFetched(kb.sym(goodServer + path), {force: true}, trywrap(done, function (ok, statusOrErrorText, resp) { - expect(ok).to.be.true + expect(ok).to.be.true() expect(resp.status).to.equal(200) expect(statusOrErrorText).to.equal('OK') expect(resp.responseText.length).to.equal(bodyText.length) @@ -51,7 +51,7 @@ describe('Fetcher', () => { console.log('@@@@@@ resp is ' + resp) console.log('@@@@@@ resp.status is ' + resp.status) - expect(ok).to.be.false + expect(ok).to.be.false() expect(statusOrErrorText).to.include(404) expect(resp.status).to.match(/404/) })) @@ -64,7 +64,7 @@ describe('Fetcher', () => { let kb = rdf.graph(); let fetcher = rdf.fetcher(kb, {a:1}) fetcher.nowOrWhenFetched(kb.sym(badServer + path), {force: true}, trywrap(done, function (ok, statusOrErrorText, resp) { - expect(ok).to.be.false + expect(ok).to.be.false() expect(statusOrErrorText).to.match(/ENOTFOUND/); expect(resp.status).to.equal(999) })) diff --git a/tests/unit/fetcher-test.js b/tests/unit/fetcher-test.js index 677d46fa8..2b92e7da5 100644 --- a/tests/unit/fetcher-test.js +++ b/tests/unit/fetcher-test.js @@ -46,7 +46,7 @@ describe('Fetcher', () => { } let fetcher = new Fetcher() - expect(fetcher.store.fetcher === fetcher).to.be.true + expect(fetcher.store.fetcher === fetcher).to.be.true() }) }) @@ -171,11 +171,11 @@ describe('Fetcher', () => { }) }) - describe('load', () => { - // let fetcher, uri, options, xhr - - it('should load multiple docs') - }) + // describe('load', () => { + // // let fetcher, uri, options, xhr + // + // it('should load multiple docs') + // }) describe('load', () => { let fetcher, uri, options @@ -436,35 +436,35 @@ describe('Fetcher', () => { }) }) - describe('guessContentType', () => { - it('should return null if uri has no extension') - - it('should return null if unknown extension') - - it('it should return the content type for a known extension') - }) - - describe('normalizedContentType', () => { - it('should return the forced content type if present') - - it('should try to guess content type if none returned in header') - - it('should try to guess content type for octet-stream generic type') - - it('should return the content type in the headers') - - it('should default to text/xml for file: protocol uris') - - it('should default to text/xml for chrome: protocol uris') - }) - - describe('handlerForContentType', () => { - it('should return null when no contentType given') - - it('should return a handler instance if content type matches') - - it('should return null when no handler match is found') - }) + // describe('guessContentType', () => { + // it('should return null if uri has no extension') + // + // it('should return null if unknown extension') + // + // it('it should return the content type for a known extension') + // }) + // + // describe('normalizedContentType', () => { + // it('should return the forced content type if present') + // + // it('should try to guess content type if none returned in header') + // + // it('should try to guess content type for octet-stream generic type') + // + // it('should return the content type in the headers') + // + // it('should default to text/xml for file: protocol uris') + // + // it('should default to text/xml for chrome: protocol uris') + // }) + // + // describe('handlerForContentType', () => { + // it('should return null when no contentType given') + // + // it('should return a handler instance if content type matches') + // + // it('should return null when no handler match is found') + // }) describe('load nock tests', () => { let fetcher @@ -566,7 +566,7 @@ describe('Fetcher', () => { }) }) - describe('createContainer', () => { - it('should invoke webOperation with the right options') - }) + // describe('createContainer', () => { + // it('should invoke webOperation with the right options') + // }) }) diff --git a/tests/unit/literal-test.js b/tests/unit/literal-test.js index 1529e3e20..6bf09db3a 100644 --- a/tests/unit/literal-test.js +++ b/tests/unit/literal-test.js @@ -104,17 +104,17 @@ describe('Literal', () => { it('compares termType, value, language, and datatype', () => { const a = new Literal('hello world', 'en', XSD.langString) const b = new Literal('', '', null) - expect(a.equals(b)).to.be.false - expect(b.equals(a)).to.be.false + expect(a.equals(b)).to.be.false() + expect(b.equals(a)).to.be.false() b.value = 'hello world' - expect(a.equals(b)).to.be.false - expect(b.equals(a)).to.be.false + expect(a.equals(b)).to.be.false() + expect(b.equals(a)).to.be.false() b.language = 'en' - expect(a.equals(b)).to.be.false - expect(b.equals(a)).to.be.false + expect(a.equals(b)).to.be.false() + expect(b.equals(a)).to.be.false() b.datatype = XSD.langString - expect(a.equals(b)).to.be.true - expect(b.equals(a)).to.be.true + expect(a.equals(b)).to.be.true() + expect(b.equals(a)).to.be.true() }) }) }) diff --git a/tests/unit/parse-test.js b/tests/unit/parse-test.js index 2bc3a3c0a..c4b3b5b51 100644 --- a/tests/unit/parse-test.js +++ b/tests/unit/parse-test.js @@ -113,7 +113,7 @@ describe('Parse', () => { }) it('uses the specified base IRI', () => { - expect(store.rdfFactory.supports["COLLECTIONS"]).to.be.false + expect(store.rdfFactory.supports["COLLECTIONS"]).to.be.false() const homePageHeight = 5 // homepage + height + 3 x name const list = 2 * 3 + 1 // (rdf:first + rdf:rest) * 3 items + listProp expect(store.statements).to.have.length(homePageHeight + list) @@ -207,7 +207,7 @@ describe('Parse', () => { }) it('uses the specified base IRI', () => { - expect(store.rdfFactory.supports["COLLECTIONS"]).to.be.true + expect(store.rdfFactory.supports["COLLECTIONS"]).to.be.true() expect(store.statements).to.have.length(1) const collection = store.statements[0] diff --git a/tests/unit/query-test.js b/tests/unit/query-test.js index 443eeaa23..e0baf969e 100644 --- a/tests/unit/query-test.js +++ b/tests/unit/query-test.js @@ -189,8 +189,8 @@ describe('Query', () => { result[(bindings['?x'].value)] = true }, null, () => { // fetcher, done callback result.sort() - expect(result[alice.uri]).to.be.true - expect(result[bob.uri]).to.be.true + expect(result[alice.uri]).to.be.true() + expect(result[bob.uri]).to.be.true() done() }) }) @@ -365,8 +365,8 @@ describe('Synchronous Query', () => { result[(bindings['?x'].value)] = true }, null, () => { // fetcher, done callback result.sort() - expect(result[alice.uri]).to.be.true - expect(result[bob.uri]).to.be.true + expect(result[alice.uri]).to.be.true() + expect(result[bob.uri]).to.be.true() done() }) }) diff --git a/tests/unit/typings-test.ts b/tests/unit/typings-test.ts index 38244c15b..e76ce8160 100644 --- a/tests/unit/typings-test.ts +++ b/tests/unit/typings-test.ts @@ -8,7 +8,7 @@ import { expect } from 'chai' */ describe('typings', () => { it('allows calling Literal.equal with non-literal', () => { - const test = (): Statement => undefined as unknown as Statement + const test = (): Statement => ({}) as unknown as Statement const b = test() const c = new Literal("") From 0e54248ecc8f11568a3256e1aef7102d224f1afa Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Tue, 3 Dec 2019 10:06:10 +0100 Subject: [PATCH 09/29] Rework, complete and test the data factories --- package.json | 2 +- src/data-factory-internal.ts | 154 ------------- src/data-factory-type.ts | 22 +- src/data-factory.ts | 37 --- src/empty.ts | 4 +- src/factories/canonical-data-factory.ts | 212 ++++++++++++++++++ src/factories/extended-term-factory.ts | 60 +++++ src/{ => factories}/rdflib-data-factory.ts | 35 +-- src/formula.ts | 2 +- src/index.ts | 2 +- src/node-internal.ts | 6 +- src/parse.ts | 2 +- src/rdfaparser.js | 2 +- src/serializer.js | 2 +- src/store.ts | 4 +- src/updates-via.js | 2 +- src/utils/terms.ts | 15 +- src/xsd.js | 2 +- .../factories/canonical-data-factory-test.ts | 64 ++++++ .../factories/extended-term-factory-test.ts | 37 +++ tests/unit/fetcher-test.js | 2 +- tests/unit/indexed-formula-test.js | 4 +- tests/unit/parse-test.js | 4 +- tests/unit/util-test.js | 2 +- 24 files changed, 440 insertions(+), 238 deletions(-) delete mode 100644 src/data-factory-internal.ts delete mode 100644 src/data-factory.ts create mode 100644 src/factories/canonical-data-factory.ts create mode 100644 src/factories/extended-term-factory.ts rename src/{ => factories}/rdflib-data-factory.ts (68%) create mode 100644 tests/unit/factories/canonical-data-factory-test.ts create mode 100644 tests/unit/factories/extended-term-factory-test.ts diff --git a/package.json b/package.json index e1778002f..509325cd1 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "test:serialize:11": "cd ./tests/serialize && node ./data.js -in=structures.n3 -format=application/rdf+xml -out=,structures.xml && node diff ,structures.xml t11-ref.xml", "test:serialize:12": "cd ./tests/serialize && node ./data.js -in=structures.n3 -format=text/turtle -out=,structures.ttl && node diff ,structures.ttl t12-ref.ttl", "test:serialize:13": "cd ./tests/serialize && node ./data.js -in=structures.n3 -format=application/n-triples -out=,structures.nt && node ./data.js -format=application/n-triples -in=,structures.nt -format=text/turtle -out=,structures.nt.ttl && node diff ,structures.nt.ttl t13-ref.ttl", - "test:unit": "mocha --growl --require ./tests/babel-register.js tests/unit/**-test.js", + "test:unit": "mocha --growl --require ./tests/babel-register.js tests/unit/**-test.*", "test:unit:egp": "mocha --require ./tests/babel-register.js tests/unit/fetcher-egp-test.js", "test:unit:dev": "mocha --watch --growl --require ./tests/babel-register.js tests/unit/**-test.js" }, diff --git a/src/data-factory-internal.ts b/src/data-factory-internal.ts deleted file mode 100644 index 06077d745..000000000 --- a/src/data-factory-internal.ts +++ /dev/null @@ -1,154 +0,0 @@ -import BlankNode from './blank-node' -import Literal from './literal' -import NamedNode from './named-node' -import Statement from './statement' -import Variable from './variable' -import { - TFNamedNode, - SubjectType, - PredicateType, - ObjectType, - GraphType, - TermType, - TFTerm, -} from './types' -import { defaultGraphNode } from './utils/default-graph-uri' -import { DataFactory, DefaultFactoryTypes, Feature } from './data-factory-type' -import Node from './node-internal' -import Collection from './collection' - -export { defaultGraphURI } from './utils/default-graph-uri' - -/** - * Gets the default graph - */ -export function defaultGraph(): NamedNode { - return defaultGraphNode -} - -/** The internal RDFlib datafactory, which uses Collections */ -const CanonicalDataFactory: DataFactory< - DefaultFactoryTypes & Collection & Variable -> = { - - supports: { - [Feature.collections]: false, - [Feature.defaultGraphType]: true, - [Feature.equalsMethod]: true, - [Feature.identity]: false, - [Feature.id]: true, - [Feature.reversibleId]: false, - [Feature.variableType]: true, - }, - - - /** - * Creates a new blank node - * @param value The blank node's identifier - */ - blankNode(value?: string): BlankNode { - return new BlankNode(value) - }, - - defaultGraph, - - /** - * Generates a uniquely identifiably idempotent string for the given {term}. - * - * Equivalent to {Term.hashString} - * - * @example Use this to associate data with a term in an object - * { obj[id(term)] = "myData" } - */ - id (term: TFTerm): string { - if (!term) { - return term - } - if (typeof term === "object" && term !== null && "id" in term && typeof term['id'] === "function") { - return (term as NamedNode).id() - } - if (Object.prototype.hasOwnProperty.call(term, "hashString")) { - return (term as Node).hashString() - } - - switch (term.termType) { - case TermType.BlankNode: - return '_:' + term.value - case TermType.Collection: - return Collection.toNT(term) - case TermType.DefaultGraph: - return 'defaultGraph' - case TermType.Empty: - return '()' - case TermType.Literal: - return Literal.toNT(term as Literal) - case TermType.NamedNode: - return '<' + term.value + '>' - case TermType.Variable: - return Variable.toString(term) - default: - throw new Error(`Can't id term with type '${term.termType}', add 'id' on your instance to override`) - } - }, - - /** - * Creates a new literal node - * @param value The lexical value - * @param languageOrDatatype Either the language or the datatype - */ - literal( - value: string, - languageOrDatatype?: string | TFNamedNode - ): Literal { - if (typeof value !== "string" && !languageOrDatatype) { - return Literal.fromValue(value) as Literal - } - - const strValue = typeof value === 'string' ? value : '' + value - if (typeof languageOrDatatype === 'string') { - if (languageOrDatatype.indexOf(':') === -1) { - return new Literal(strValue, languageOrDatatype) - } else { - return new Literal(strValue, null, this.namedNode(languageOrDatatype)) - } - } else { - return new Literal(strValue, null, languageOrDatatype) - } - }, - - /** - * Creates a new named node - * @param value The new named node - */ - namedNode(value: string): NamedNode { - return new NamedNode(value) - }, - - /** - * Creates a new statement - * @param subject The subject - * @param predicate The predicate - * @param object The object - * @param graph The containing graph - */ - quad( - subject: TFTerm | SubjectType, - predicate: TFTerm | PredicateType, - object: TFTerm | ObjectType, - graph?: TFTerm | GraphType - ): Statement { - graph = graph || defaultGraph() - return new Statement(subject, predicate, object, graph) - }, - - /** - * Creates a new variable - * @param name The name for the variable - */ - variable(name?: string): Variable { - return new Variable(name) - }, -} - -/** Contains the factory methods as defined in the spec, plus id */ -export default CanonicalDataFactory diff --git a/src/data-factory-type.ts b/src/data-factory-type.ts index a5256926a..aa0624579 100644 --- a/src/data-factory-type.ts +++ b/src/data-factory-type.ts @@ -11,12 +11,14 @@ import Literal from './literal' import Statement from './statement' import NamedNode from './named-node' import BlankNode from './blank-node' +import Variable from './variable' -export type DefaultFactoryTypes = NamedNode | BlankNode | Literal | Statement +export type DefaultFactoryTypes = NamedNode | BlankNode | Literal | Variable | Statement /** * Defines a DataFactory as used in rdflib, based on the RDF/JS: Data model specification, * but with additional extensions + * * bnIndex is optional but useful. */ export interface DataFactory< @@ -37,7 +39,18 @@ export interface DataFactory< equals(a: Comparable, b: Comparable): boolean - toNQ(term: FactoryTypes): string + toNQ(term: TFTerm | FactoryTypes): string + + quad( + subject: TFTerm, + predicate: TFTerm, + object: TFTerm, + graph?: TFTerm, + ): Statement; + + quadToNQ(term: Statement | TFQuad): string + + termToNQ(term: TFTerm): string /** * Generates a unique session-idempotent identifier for the given object. @@ -47,13 +60,12 @@ export interface DataFactory< * * @return {Indexable} A unique value which must also be a valid JS object key type. */ - id(obj: FactoryTypes): IndexType + id(obj: TFTerm | FactoryTypes): IndexType } export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm export type Namespace = (term:string) => TFNamedNode -export type NamespaceCreator = (ns: string) => Namespace /** A set of features that may be supported by a Data Factory */ export type SupportTable = Record @@ -78,6 +90,6 @@ export enum Feature { variableType = "VARIABLE_TYPE", } -export type Comparable = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null +export type Comparable = TFTerm | TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null export type Indexable = number | string diff --git a/src/data-factory.ts b/src/data-factory.ts deleted file mode 100644 index 35c7a6030..000000000 --- a/src/data-factory.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Collection from './collection' -import CanonicalDataFactory from './data-factory-internal' -import { ValueType } from './types' -import { DataFactory, Feature } from './data-factory-type' - -interface CollectionFactory extends DataFactory { - collection(elements: ReadonlyArray): Collection -} - -/** - * Data factory which also supports Collections - * - * Necessary for preventing circular dependencies. - */ -const ExtendedTermFactory: CollectionFactory = { - ...CanonicalDataFactory, - - supports: { - [Feature.collections]: true, - [Feature.defaultGraphType]: false, - [Feature.equalsMethod]: true, - [Feature.identity]: false, - [Feature.id]: true, - [Feature.reversibleId]: false, - [Feature.variableType]: true, - }, - - /** - * Creates a new collection - * @param elements - The initial element - */ - collection (elements: ReadonlyArray): Collection { - return new Collection(elements) - }, -} - -export default ExtendedTermFactory diff --git a/src/empty.ts b/src/empty.ts index 2067bdf55..b0300a967 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,10 +1,10 @@ import Node from './node-internal' -import { TermType } from './types' +import { TermType, TFTerm } from './types' /** * An empty node */ -export default class Empty extends Node { +export default class Empty extends Node implements TFTerm { static termType = TermType.Empty termType = TermType.Empty diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts new file mode 100644 index 000000000..de7dcd806 --- /dev/null +++ b/src/factories/canonical-data-factory.ts @@ -0,0 +1,212 @@ +import BlankNode from '../blank-node' +import Literal from '../literal' +import NamedNode from '../named-node' +import Statement from '../statement' +import Variable from '../variable' +import { + TFNamedNode, + SubjectType, + PredicateType, + ObjectType, + GraphType, + TermType, + TFTerm, + TFQuad, +} from '../types' +import { defaultGraphNode } from '../utils/default-graph-uri' +import { + Comparable, + DataFactory, + DefaultFactoryTypes, + Feature +} from '../data-factory-type' +import { isTFStatement, isTFTerm } from '../utils/terms' + +export { defaultGraphURI } from '../utils/default-graph-uri' + +/** + * Gets the default graph + */ +export function defaultGraph(): NamedNode { + return defaultGraphNode +} + +/** The internal RDFlib datafactory, which uses Collections */ +const CanonicalDataFactory: DataFactory = { + + supports: { + [Feature.collections]: false, + [Feature.defaultGraphType]: false, + [Feature.equalsMethod]: true, + [Feature.identity]: false, + [Feature.id]: true, + [Feature.reversibleId]: false, + [Feature.variableType]: true, + }, + + /** + * Creates a new blank node + * @param value - The blank node's identifier + */ + blankNode(value?: string): BlankNode { + return new BlankNode(value) + }, + + defaultGraph, + + /** + * Compares to (rdf) objects for equality. + */ + equals(a: Comparable, b: Comparable): boolean { + if (a === b || !a || !b) { + return true + } + + if (isTFStatement(a) || isTFStatement(b)) { + if (isTFStatement(a) && isTFStatement(b)) { + return ( + this.equals(a.subject, b.subject) && + this.equals(a.predicate, b.predicate) && + this.equals(a.object, b.object) && + this.equals(a.graph, b.graph) + ) + } + + return false + } + + if (isTFTerm(a) && isTFTerm(b)) { + return this.id(a) === this.id(b) + } + + return false + }, + + /** + * Generates a uniquely identifiably *idempotent* string for the given {term}. + * + * Equivalent to {Term.hashString} + * + * @example Use this to associate data with a term in an object + * { obj[id(term)] = "myData" } + */ + id (term: TFTerm | Statement | undefined): string { + if (!term) { + return 'undefined' + } + + if (isTFStatement(term)) { + return this.quadToNQ(term) + } + + switch (term.termType) { + case TermType.DefaultGraph: + return 'defaultGraph' + case TermType.Variable: + return Variable.toString(term) + default: + const nq = this.termToNQ(term) + if (nq) { + return nq + } + + throw new Error(`Can't id term with type '${term.termType}'`) + } + }, + + isQuad (obj: any): obj is Statement { + return obj instanceof Statement + }, + + /** + * Creates a new literal node. Does some JS literal parsing for ease of use. + * @param value - The lexical value + * @param languageOrDatatype - Either the language or the datatype + */ + literal( + value: string | number | boolean | Date, + languageOrDatatype?: string | TFNamedNode + ): Literal { + if (typeof value !== "string" && !languageOrDatatype) { + return Literal.fromValue(value) as Literal + } + + const strValue = typeof value === 'string' ? value : '' + value + if (typeof languageOrDatatype === 'string') { + if (languageOrDatatype.indexOf(':') === -1) { + return new Literal(strValue, languageOrDatatype) + } else { + return new Literal(strValue, null, this.namedNode(languageOrDatatype)) + } + } else { + return new Literal(strValue, null, languageOrDatatype) + } + }, + + /** + * Creates a new named node + * @param value - The new named node + */ + namedNode(value: string): NamedNode { + return new NamedNode(value) + }, + + /** + * Creates a new statement + * @param subject - The subject + * @param predicate - The predicate + * @param object - The object + * @param graph - The containing graph + */ + quad( + subject: TFTerm | SubjectType, + predicate: TFTerm | PredicateType, + object: TFTerm | ObjectType, + graph?: TFTerm | GraphType + ): Statement { + graph = graph || defaultGraph() + return new Statement(subject, predicate, object, graph) + }, + + quadToNQ(q: Statement | TFQuad): string { + return `${this.termToNQ(q.subject)} ${this.termToNQ(q.predicate)} ${this.termToNQ(q.object)} ${this.termToNQ(q.graph)} .`; + }, + + /** Stringify a {term} to n-quads serialization. */ + termToNQ(term: TFTerm): string { + switch (term.termType) { + case TermType.BlankNode: + return '_:' + term.value + case TermType.DefaultGraph: + return '' + case TermType.Empty: + return '' + case TermType.Literal: + return Literal.toNT(term as Literal) + case TermType.NamedNode: + return '<' + term.value + '>' + default: + throw new Error(`Can't serialize nonstandard term type (was '${term.termType}')`) + } + }, + + /** Convert an rdf object (term or quad) to n-quads serialization. */ + toNQ (term: TFTerm | (DefaultFactoryTypes & Variable)): string { + if (this.isQuad(term)) { + return this.quadToNQ(term); + } + + return this.termToNQ(term); + }, + + /** + * Creates a new variable + * @param name - The name for the variable + */ + variable(name?: string): Variable { + return new Variable(name) + }, +} + +/** Contains the factory methods as defined in the spec, plus id */ +export default CanonicalDataFactory diff --git a/src/factories/extended-term-factory.ts b/src/factories/extended-term-factory.ts new file mode 100644 index 000000000..5d1005fb3 --- /dev/null +++ b/src/factories/extended-term-factory.ts @@ -0,0 +1,60 @@ +import Collection from '../collection' +import CanonicalDataFactory from './canonical-data-factory' +import { TermType, TFTerm, ValueType } from '../types' +import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from '../data-factory-type' +import { isCollection, isVariable } from '../utils/terms' +import Variable from '../variable' + +interface CollectionFactory extends DataFactory { + collection(elements: ReadonlyArray): Collection +} + +/** + * Data factory which also supports Collections + * + * Necessary for preventing circular dependencies. + */ +const ExtendedTermFactory: CollectionFactory = { + ...CanonicalDataFactory, + + supports: { + [Feature.collections]: true, + [Feature.defaultGraphType]: false, + [Feature.equalsMethod]: true, + [Feature.identity]: false, + [Feature.id]: true, + [Feature.reversibleId]: false, + [Feature.variableType]: true, + }, + + /** + * Creates a new collection + * @param elements - The initial element + */ + collection (elements: ReadonlyArray): Collection { + return new Collection(elements) + }, + + id (term: TFTerm | DefaultFactoryTypes): Indexable { + if (isCollection(term)) { + return `( ${term.elements.map((e) => { + return this.id(e) }).join(', ')} )` + } + + if (isVariable(term)) { + return Variable.toString(term) + } + + return CanonicalDataFactory.id(term) + }, + + termToNQ (term: TFTerm): string { + if (term.termType === TermType.Collection) { + return Collection.toNT(term) + } + + return CanonicalDataFactory.termToNQ(term) + } +} + +export default ExtendedTermFactory diff --git a/src/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts similarity index 68% rename from src/rdflib-data-factory.ts rename to src/factories/rdflib-data-factory.ts index 2238a8052..ef65461a9 100644 --- a/src/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -1,20 +1,18 @@ import { - GraphType, IRDFlibDataFactory, ObjectType, PredicateType, SubjectType, - TFNamedNode -} from './types' -import Literal from './literal' -import Statement from './statement' -import IndexedFormula from './store' -import Fetcher from './fetcher' -import ExtendedTermFactory from './data-factory' + TFNamedNode, + TFTerm +} from '../types' +import Literal from '../literal' +import Statement from '../statement' +import IndexedFormula from '../store' +import Fetcher from '../fetcher' +import ExtendedTermFactory from './extended-term-factory' -/** Full RDFLib.js Data Factory - * @todo Add missing functions (isQuad, equals, toNQ), so Partial can be removed - */ +/** Full RDFLib.js Data Factory */ const RDFlibDataFactory: IRDFlibDataFactory = { ...ExtendedTermFactory, @@ -39,9 +37,10 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param val The lexical value * @param lang The language * @param dt The datatype + * @deprecated use {literal} with the second and third argument combined */ lit (val: string, lang?: string, dt?: TFNamedNode): Literal { - return new Literal('' + val, lang, dt) + return this.literal('' + val, lang || dt) }, /** @@ -50,14 +49,15 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param predicate The predicate * @param object The object * @param graph The containing graph + * @deprecated use {quad} instead */ st ( - subject: SubjectType, - predicate: PredicateType, - object: ObjectType, - graph?: GraphType + subject: TFTerm, + predicate: TFTerm, + object: TFTerm, + graph?: TFTerm ): Statement { - return new Statement(subject, predicate, object, graph) + return this.quad(subject, predicate, object, graph) }, /** @@ -65,6 +65,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param subject The subject * @param predicate The predicate * @param object The object + * @deprecated use {quad} without the last argument instead */ triple ( subject: SubjectType, diff --git a/src/formula.ts b/src/formula.ts index 2985072a3..0b0888dcd 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -1,7 +1,7 @@ import BlankNode from './blank-node' import ClassOrder from './class-order' import Collection from './collection' -import CanonicalDataFactory from './data-factory-internal' +import CanonicalDataFactory from './factories/canonical-data-factory' import log from './log' import NamedNode from './named-node' import Namespace from './namespace' diff --git a/src/index.ts b/src/index.ts index c34404f78..f36a4b12c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -28,7 +28,7 @@ import { UpdatesVia } from './updates-via' import * as uri from './uri' import * as Util from './util' import Variable from './variable' -import DataFactory from './rdflib-data-factory' +import DataFactory from './factories/rdflib-data-factory' export * from './utils/terms' diff --git a/src/node-internal.ts b/src/node-internal.ts index b3ffda72f..e2dd7c8e0 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -13,10 +13,6 @@ export default abstract class Node { static fromValue: (value: ValueType) => T // Specified in './node.ts' to prevent circular dependency static toJS: (term: any) => Date | Number | string | boolean | object | Array; - /** - * The nodes in this collection - */ - elements!: Node[]; /** The type of node */ termType!: string; @@ -27,7 +23,7 @@ export default abstract class Node { /** The node's value */ value: string; - constructor(value: string) { + protected constructor(value: string) { this.value = value } diff --git a/src/parse.ts b/src/parse.ts index db22fba15..0d6427b02 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,4 +1,4 @@ -import DataFactory from './data-factory' +import DataFactory from './factories/extended-term-factory' import jsonldParser from './jsonldparser' // @ts-ignore is this injected? import { Parser as N3jsParser } from 'n3' // @@ Goal: remove this dependency diff --git a/src/rdfaparser.js b/src/rdfaparser.js index 5283f2055..064a4e6e7 100644 --- a/src/rdfaparser.js +++ b/src/rdfaparser.js @@ -17,7 +17,7 @@ import Literal from './literal' import NamedNode from './named-node' import * as Uri from './uri' import * as Util from './util' -import rdf from './data-factory-internal' +import rdf from './factories/canonical-data-factory' if (typeof Node === 'undefined') { // @@@@@@ Global. Interface to xmldom. var Node = { diff --git a/src/serializer.js b/src/serializer.js index 4356fb73b..cf90aebc9 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -9,7 +9,7 @@ import NamedNode from './named-node' import BlankNode from './blank-node' import * as Uri from './uri' import * as Util from './util' -import CanonicalDataFactory from './data-factory-internal' +import CanonicalDataFactory from './factories/canonical-data-factory' import { createXSD } from './xsd' export default (function () { diff --git a/src/store.ts b/src/store.ts index b3c65dfde..050c1083b 100644 --- a/src/store.ts +++ b/src/store.ts @@ -18,7 +18,7 @@ /** @module store */ import ClassOrder from './class-order' -import { defaultGraphURI } from './data-factory-internal' +import { defaultGraphURI } from './factories/canonical-data-factory' import Formula, { FormulaOpts } from './formula' import { ArrayIndexOf } from './utils' import { RDFArrayRemove } from './util' @@ -394,7 +394,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass add ( subj: TFSubject | TFQuad | TFQuad[] | Statement | Statement[], pred?: TFPredicate, - obj?: TFObject | Collection, + obj?: TFTerm, why?: TFGraph ): TFQuad | null | IndexedFormula { var i: number diff --git a/src/updates-via.js b/src/updates-via.js index 0ea1aab37..f56da7b1c 100644 --- a/src/updates-via.js +++ b/src/updates-via.js @@ -1,7 +1,7 @@ /* * Updates-Via */ -import DataFactory from './rdflib-data-factory' +import DataFactory from './factories/rdflib-data-factory' export class UpdatesSocket { constructor (parent, via) { diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 4d8deeb2f..77b9c0bfe 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -15,6 +15,7 @@ import Collection from '../collection' import IndexedFormula from '../store' import Statement from '../statement' import NamedNode from '../named-node' +import Variable from '../variable' export function isStatement(obj): obj is Statement { return typeof obj === 'object' && obj !== null && 'subject' in obj @@ -46,6 +47,12 @@ export function isRDFObject (obj: any): obj is ObjectType { ) } +/** TypeGuard for RDFLib Variables */ +export function isVariable(obj: any): obj is Variable { + return isTFTerm(obj) + && (obj as TFTerm).termType === TermType.Variable +} + /** TypeGuard for RDF/JS TaskForce Terms */ export function isTFTerm (obj: any): obj is TFTerm { return typeof obj === 'object' @@ -60,8 +67,12 @@ export function isTFLiteral (value: any): value is TFLiteral { } /** TypeGuard for RDF/JS TaskForce Quads */ -export function isTFStatement (obj: any): obj is TFQuad { - return typeof obj === "object" && obj !== null && 'subject' in obj +export function isTFStatement (obj: any): obj is TFQuad { + return typeof obj === "object" && obj !== null && ( + 'subject' in obj + && 'predicate' in obj + && 'object' in obj + ) } /** TypeGuard for RDF/JS TaskForce NamedNodes */ diff --git a/src/xsd.js b/src/xsd.js index 8e852ca22..814c5cc16 100644 --- a/src/xsd.js +++ b/src/xsd.js @@ -1,4 +1,4 @@ -import CanonicalDataFactory from './data-factory-internal' +import CanonicalDataFactory from './factories/canonical-data-factory' export function createXSD(localFactory = CanonicalDataFactory) { class XSD {} diff --git a/tests/unit/factories/canonical-data-factory-test.ts b/tests/unit/factories/canonical-data-factory-test.ts new file mode 100644 index 000000000..85f43d9e6 --- /dev/null +++ b/tests/unit/factories/canonical-data-factory-test.ts @@ -0,0 +1,64 @@ +import { expect } from 'chai' + +import Factory from '../../../src/factories/canonical-data-factory'; +import { Feature } from '../../../src/data-factory-type' +import NamedNode from '../../../src/named-node' +import Literal from '../../../src/literal' +import DefaultGraph from '../../../src/default-graph' +import Empty from '../../../src/empty' + +describe('data-factory', () => { + describe('DataFactory', () => { + describe('#supports', () => { + it('collections', () => { expect(Factory.supports[Feature.collections]).to.be.false() }) + it('defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) + it('equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) + it('identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) + it('id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) + it('reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) + }) + + describe('equals', () => {}); + + describe('id', () => { + it('handles default graph', () => expect(Factory.id(new DefaultGraph())).to.equal('defaultGraph')) + }); + + describe('isQuad', () => {}); + + describe('literal', () => { + const plain = Factory.literal('s') + it('keeps the value', () => expect(plain.value).to.equal('s')) + it('creates a literal', () => expect(plain).to.be.instanceOf(Literal)) + it('defaults to string', () => expect(plain.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#string')) + + const integer = Factory.literal('23', Factory.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + it('keeps the datatype', () => expect(integer.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#integer')) + + const langString = Factory.literal('s', 'en') + it('sets a language', () => expect(langString.language).to.equal('en')) + it('sets a language', () => expect(langString.datatype.value).to.equal('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')) + }); + + describe('namedNode', () => { + it('creates a named node', () => expect(Factory.namedNode('about:config')).to.be.instanceOf(NamedNode)) + it('preserves the value', () => expect(Factory.blankNode('http://example.com/').value).to.equal('http://example.com/')) + }); + + describe('quad', () => {}); + + describe('quadToNQ', () => {}); + + describe('termToNQ', () => { + it('handles blank nodes', () => expect(Factory.termToNQ(Factory.blankNode('g123'))).to.equal('_:g123')) + it('handles default graph', () => expect(Factory.termToNQ(new DefaultGraph())).to.equal('')) + it('handles the empty collection', () => expect(Factory.termToNQ(new Empty())).to.equal('')) + it('handles literals', () => expect(Factory.termToNQ(Factory.literal('text with "quotes"'))).to.equal('"text with \\"quotes\\""')) + it('handles named nodes', () => expect(Factory.termToNQ(Factory.namedNode('http://example.com/'))).to.equal('')) + }); + + describe('toNQ', () => {}); + + describe('variable', () => {}); + }) +}) diff --git a/tests/unit/factories/extended-term-factory-test.ts b/tests/unit/factories/extended-term-factory-test.ts new file mode 100644 index 000000000..2c0be50f1 --- /dev/null +++ b/tests/unit/factories/extended-term-factory-test.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai' + +import Factory from '../../../src/factories/extended-term-factory'; +import { Feature } from '../../../src/data-factory-type' +import Collection from '../../../src/collection' + +/** + * @ignore + * Inherits from CanonicalDataFactory (internal), so we only have to test the added features + * e.g. Collection + */ +describe('extended-term-factory', () => { + describe('#supports', () => { + it('collections', () => { expect(Factory.supports[Feature.collections]).to.be.true() }) + it('defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) + it('equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) + it('identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) + it('id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) + it('reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) + }) + + describe('id', () => { + it('handles collections', () => { + expect(Factory.id(new Collection([ + Factory.literal('1'), + Factory.namedNode('http://example.com/'), + ]))).to.equal('( "1", )') + }) + }) + + describe('termToNQ', () => { + it('handles collections', () => { + const c = new Collection([ '1', '2' ]) + expect(Factory.termToNQ(c)).to.equal(`_:${c.id}`) + }) + }); +}) diff --git a/tests/unit/fetcher-test.js b/tests/unit/fetcher-test.js index 2b92e7da5..0dccbeefb 100644 --- a/tests/unit/fetcher-test.js +++ b/tests/unit/fetcher-test.js @@ -10,7 +10,7 @@ import nock from 'nock' import * as rdf from '../../src/index' import NamedNode from '../../src/named-node' import IndexedFormula from '../../src/store' -import CanonicalDataFactory from '../../src/data-factory-internal' +import CanonicalDataFactory from '../../src/factories/canonical-data-factory' chai.use(sinonChai) chai.use(dirtyChai) diff --git a/tests/unit/indexed-formula-test.js b/tests/unit/indexed-formula-test.js index eef7b1204..8df886783 100644 --- a/tests/unit/indexed-formula-test.js +++ b/tests/unit/indexed-formula-test.js @@ -1,12 +1,12 @@ /* eslint-env mocha */ import { expect } from 'chai' -import CanonicalDataFactory from '../../src/data-factory-internal' +import CanonicalDataFactory from '../../src/factories/canonical-data-factory' import Formula from '../../src/formula' import IndexedFormula from '../../src/store' import NamedNode from '../../src/named-node' import { RDFArrayRemove } from '../../src/util' -import DataFactory from '../../src/rdflib-data-factory' +import DataFactory from '../../src/factories/rdflib-data-factory' describe('IndexedFormula', () => { const g0 = NamedNode.fromValue('https://example.com/graph0') diff --git a/tests/unit/parse-test.js b/tests/unit/parse-test.js index c4b3b5b51..6cacdc9fe 100644 --- a/tests/unit/parse-test.js +++ b/tests/unit/parse-test.js @@ -2,9 +2,9 @@ import { expect } from 'chai' import parse from '../../src/parse' -import CanonicalDataFactory from '../../src/data-factory-internal' +import CanonicalDataFactory from '../../src/factories/canonical-data-factory' import defaultXSD from '../../src/xsd' -import DataFactory from '../../src/rdflib-data-factory' +import DataFactory from '../../src/factories/rdflib-data-factory' describe('Parse', () => { describe('ttl', () => { diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index 1f1c23ffb..4c41a2118 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ import { expect } from 'chai' -import CanonicalDataFactory from '../../src/data-factory-internal' +import CanonicalDataFactory from '../../src/factories/canonical-data-factory' import Literal from '../../src/literal' import NamedNode from '../../src/named-node' import Statement from '../../src/statement' From 82910ded2dd644c0c5c51af4018af47022348e27 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Tue, 3 Dec 2019 13:03:06 +0100 Subject: [PATCH 10/29] Cleanup some TS & build things --- package-lock.json | 107 --------- package.json | 3 +- src/factories/canonical-data-factory.ts | 2 +- src/factories/rdflib-data-factory.ts | 3 +- src/formula.ts | 5 - src/node.ts | 1 - src/serialize.ts | 8 +- src/statement.ts | 4 +- src/store.ts | 1 - src/utils.ts | 2 +- src/wip.md | 111 --------- tsconfig.json | 1 + types-temp.ts | 293 ------------------------ webpack.config.js | 3 - 14 files changed, 10 insertions(+), 534 deletions(-) delete mode 100644 src/wip.md delete mode 100644 types-temp.ts diff --git a/package-lock.json b/package-lock.json index 54249ecd9..7cf2e03dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1760,65 +1760,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -3627,22 +3568,6 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, - "fork-ts-checker-webpack-plugin": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-1.6.0.tgz", - "integrity": "sha512-vqOY5gakcoon2s12V7MMe01OPwfgqulUWFzm+geQaPPOBKjW1I7aqqoBVlU0ECn97liMB0ECs16pRdIGe9qdRw==", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "chalk": "^2.4.1", - "chokidar": "^2.0.4", - "micromatch": "^3.1.10", - "minimatch": "^3.0.4", - "semver": "^5.6.0", - "tapable": "^1.0.0", - "worker-rpc": "^0.1.0" - } - }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", @@ -4547,23 +4472,6 @@ "function-bind": "^1.1.1" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5536,12 +5444,6 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, - "microevent.ts": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", - "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", - "dev": true - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -9029,15 +8931,6 @@ "errno": "~0.1.7" } }, - "worker-rpc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", - "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", - "dev": true, - "requires": { - "microevent.ts": "~0.1.1" - } - }, "wrap-ansi": { "version": "2.1.0", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", diff --git a/package.json b/package.json index 509325cd1..93f37fdef 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "chai": "^4.2.0", "diff": "^4.0.1", "dirty-chai": "^2.0.1", - "fork-ts-checker-webpack-plugin": "^1.6.0", "fs-grep": "^0.0.5", "mocha": "^6.2.2", "nock": "^10.0.6", @@ -91,7 +90,7 @@ "build:browser": "webpack --progress", "build:types": "tsc --emitDeclarationOnly -d --declarationDir lib --allowJs false", "doc": "rm -r doc ; typedoc", - "prepare": "npm run build && npm run build:browser", + "prepare": "npm run build && npm run build:types && npm run build:browser", "start": "webpack-dev-server --https --port 4800", "test": "npm run test:unit && npm run test:serialize", "test:clean": "rimraf tests/serialize/,*", diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index de7dcd806..28fee7f34 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -128,7 +128,7 @@ const CanonicalDataFactory: DataFactory = { languageOrDatatype?: string | TFNamedNode ): Literal { if (typeof value !== "string" && !languageOrDatatype) { - return Literal.fromValue(value) as Literal + return Literal.fromValue(value) } const strValue = typeof value === 'string' ? value : '' + value diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts index ef65461a9..e3a26ca50 100644 --- a/src/factories/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -72,8 +72,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { predicate: PredicateType, object: ObjectType ): Statement { - // Statement is inherited from data-factory-internal#quad - return this.quad(subject, predicate, object) as Statement + return this.quad(subject, predicate, object) }, } diff --git a/src/formula.ts b/src/formula.ts index 0b0888dcd..76aaa302b 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -1,4 +1,3 @@ -import BlankNode from './blank-node' import ClassOrder from './class-order' import Collection from './collection' import CanonicalDataFactory from './factories/canonical-data-factory' @@ -28,10 +27,6 @@ import { } from './data-factory-type' import { appliedFactoryMethods, arrayToStatements } from './utils' -export function isFormula(value: T | TFTerm): value is Formula { - return (value as Node).termType === TermType.Graph -} - export interface FormulaOpts { dataCallback?: (q: TFQuad) => void rdfArrayRemove?: (arr: TFQuad[], q: TFQuad) => void diff --git a/src/node.ts b/src/node.ts index 9d360c0ba..d5b887961 100644 --- a/src/node.ts +++ b/src/node.ts @@ -41,7 +41,6 @@ Node.toJS = function (term: TFTerm): TFTerm | boolean | number | Date | string | term.datatype.equals(ns.xsd('float')) || term.datatype.equals(ns.xsd('decimal')) ) { - let z = Number(term.value) return Number(term.value) } return term.value diff --git a/src/serialize.ts b/src/serialize.ts index 80d512048..39029a5ef 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -18,7 +18,7 @@ export default function serialize ( * Defaults to Turtle. */ contentType?: string | ContentType, - callback?: (err?: Error | null, result?: string ) => any, + callback?: (err: Error | undefined | null, result?: string | null) => any, options?: { /** * A string of letters, each of which set an options @@ -74,17 +74,17 @@ export default function serialize ( } } catch (err) { if (callback) { - return callback(err) + return callback(err, undefined) } throw err // Don't hide problems from caller in sync mode } - function executeCallback (err?: Error | null, result?: string) { + function executeCallback (err: Error | null | undefined, result: string | null | undefined): string | undefined { if (callback) { callback(err, result) return } else { - return result + return result as string } } } diff --git a/src/statement.ts b/src/statement.ts index f5b17f160..3e054d121 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -1,4 +1,3 @@ -import NamedNode from './named-node' import Node from './node-internal' import { Bindings, @@ -14,7 +13,6 @@ import { TFSubject, TFTerm, } from './types' -import Literal from './literal' import { defaultGraphNode } from './utils/default-graph-uri' /** A Statement represents an RDF Triple or Quad. */ @@ -80,7 +78,7 @@ export default class Statement implements TFQuad((id, _listObj, i, listData) => { - statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i] as TFLiteral)) + statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i])) let nextNode if (i < listData.length - 1) { diff --git a/src/wip.md b/src/wip.md deleted file mode 100644 index 209422fb9..000000000 --- a/src/wip.md +++ /dev/null @@ -1,111 +0,0 @@ -Builds upon the approved #363 PR for [typescript migration](https://github.com/linkeddata/rdflib.js/issues/355): - -## Changes included in PR #363 - -- Converted some of the most fundamental classes to typescript, including `Node`, `Literal`, `BlankNode`, `NamedNode`, `Collection`, `Statement`. -- Introduced a `.types` file for shared types. -- Included a temporary `types-temp.ts` file in project root as a reference file for documentation and keeping track of the ts migration process. -- The `.isVar` method is set to boolean values, instead of `0` or `1`. This seemed reasonable, as it's only used for boolean type checks, and the existing types already define it as a boolean value. Timbl confirmed that `isVar` is only used for boolean operations. -- JSDoc is replaced with Typedoc. Combined with types and comments from `@types/rdflib`, this makes the documentation far more complete. -- I used many of the types and comments from `@types/rdflib` by [Cénotélie](https://github.com/cenotelie/). Added credits in `package.json`, discussed this with Cénotélie. - -## New changes - -- Migrated `formula`, `variable`, `store`, `update-manager`, `data-factory`, `default-graph`, `namespace`, `parse`,`serialize`, `parse`, `uri` and `utils` tot ts. -- Added `fork-ts-checker-webpack-plugin`, which enables errors in the log when ts errors occur. -- Added and implemented RDF/JS Taskforce (TF) types, included these in the `types.ts` file. I tried implementing the TF types in the major classes, but some of the incompatibilities make it difficult. Many available methods on rdfjs instances (e.g. `.toNt()` in NamedNode), are missing in TF classes. To improve TF comatibility, we should minimize using rdflib specific functions. This would for example enable using Forumla methods on RDFExt nodes. We should use the Taskforce types (TFTerm, TFQuad) as much as possible, instead of rdflib types (Node, Statement). -- Added typeguards, e.g. `isTFNamedNode` and `isTFPredicate` in `Utils`, and used these at various locations. -- Use enums for `termType` and `contentType`, without breaking compatibility with regular strings. -- `Formula` Constructor arguments are optional - since some functions initialize empty Formulas. -- In `Formula.fromNT()` `return this.literal(str, lang || dt)` seemed wrong, converted it to -- The various `fromValue` methods conflict with the base Node class, type wise. Since they don't use `this`, I feel like they should be converted to functions. - -## Compatibility with RDFJS taskforce and external datafactories - -- Variables (from rdfjs taskforce) make typings a lot more complex (many methods would require explicit type checks, e.g. you can't serialize a variable to N-Triples), so I disabled them. -- Switched internal calls from `sameTerm` to `equals` in order to comply to TF spec, so that these functions also work with external datafactories. Alias still exists, so nothing changes externally. -- Switched internal calls from `.why` to `graph`. Alias still exists, so nothing changes externally. -- Calls to `kb.sym` have been replaced with `kb.rdfFactory.namedNode`, which makes all these functions more compatible with external datafactories. Added a deprecation warning to `.sym`. - -## Minor fixes - -- Removed the last conditional of `Formula.holds()`, since it did not make sense -- Removed some unreachable code, unused variables and functions that didn't do anything such as `Node.substitute()`. -- Removed the `justOne` argument from `formula.statementsMatching`, since it was unused. -- The `uri.document` function called `.uri` on a string, I removed that. -- Transformed inline comments to JSDoc, moved them to type declarations instead of constructor. -- Some types are set to any, because I didn't fully understand them. I've added TODO comments for these. -- Removed the fourth argument from the `parser.parse` function in `fetcher.parse`, since the function only takes three. -- Removed the `response` argument from `fetcher.parse`, `XHTMLHandler.parse`, `RDFXMLHander.parse`, `XMLHandler`, since it was not used. -- `Fetcher.failfetch` added strings as objects to the store. Changed that to literals. -- Internal calls to `NamedNode.uri` are changed to `.value` to comply with TF spec. This enables these functions to work with external datafactories. -- Removed unused second argument from `Fetcher.cleanupFetchRequest` -- Created one huge `Options` type for Fetcher. Not sure if this is the way to go. -- In `Node.toJS`, the boolean only returned true if the `xsd:boolean` value is `'1'`, now it it should also work for `'true'`. -- Converted `kb.add(someString)` to `kb.add(new Namednode(somestring))` to enhance compatibility with other datafactories. This happens in `Fetcher` and -- `Fetcher.refreshIfExpired` passed an array of headers, but it needs only one string. -- `Fethcer` uses `Headers` a lot. I've changed empty objects to empty `new Headers` instances, which enhances compatibility with default `Fetch` behavior. -- `Serializer.tripleCallback` had an unused third argument. -- `UpdateManager.update` checked an undefined `secondTry` variable. Since later in the same function, `.update` is called with a 4th argument, I assume this is `secondTry`. I've added it as an optional argument. Perhaps this is -- `Formula.add()` now uses `this.rdfFactory.defaultGraph` instead of the non-existent `this.defaultGraph` -- `IndexedFormula.replaceWith` now passes a Node instead of a string to `.add` in the `if (big.value)` block - -## Possible bugs discovered, which are not fixed by this PR - -- `Formula.substitute` uses `this.add(Statments[])`, which will crash. I think it should be removed, since `IndexedFormula.substitute` is used all the time anyway. -- The `Formula.serialize` function calls `serialize.ts` with only one argument, so without a store. I think this will crash every time. Also`Formula.serialize` uses `this.namespaces`, but this is only defined in `IndexedFormula`. Is it rotten code and should it be removed? -- `IndexedFormula.add()` accepts many types of inputs, but this will lead to invalid statements (e.g. a Literal as a Subject). I suggest we make this more strict and throw more errors on wrong inputs. Relates to #362. We could still make the allowed inputs bigger by allowing other types with some explicit behavior, e.g. in subject arguments, create `NamedNodes` from `URL` objects and `strings` that look like URLs . In any case, I thinkg the `Node.fromValue` behavior is too unpredictable for `store.add`. For now, I've updated the docs to match its behavior. -- The types for `Node.fromValue` and `Literal.fromValue` show how unpredictable these methods are. I suggest we make them more strict (also relates to #362), so they either return a `TFTerm` (`node`) or throw an error - they should not return `undefined` or `null`. Also, I think they should be converted to functions in `Utils`: this would fix the circular dependency issue (why we need `node_internal`) and it would fix the type issues in `Literal.fromValue` (which tends to give issues since it's signature does not correctly extend from `Node.fromValue`) -- In `Fetcher.addtype`, the final logic will allways return `true`, since `redirection` is a `NamedNode`. Should it call `.value`? -- Various `Hanlder.parse()` functions in `Fetcher` return either a `Response` or a `Promise`. This seems like weird behavior - should it not always return an array? -- The `defaultGraph` iri is set to `chrome:theSession`, but this errors in Firefox. I suggest we change it to something else. See #370. -- The `Parse.executeErrorCallback` conditional logic is always `true`. -- I've added many `// @ts-ignore` comments. Ideally, these should be resolved by fixing the underlying type issues. -- `UpdateManager.update_statement` seems to refer to the wrong `this`. It calls `this.anonimize`, but it is not available in that scope. -- `UpdateManager.updateLocalFile` uses `Component`, but this is not defined anywhere. Is this deprecated? -- `Data-factory-internal.id()` returns `string | undefined`, I feel like undefined should not be possible - it should throw an error. This would resolve the type incompatibility on line 146. -- `IndexedFormula.copy` runs `.copy` on a Collection, but that method is not available there. -- `IndexedFormula.predicateCallback` is checked, but never used in this codebase. - - -## Other things I noticed - -- Literals can apparently be `null` or `undefined`, when nodes are created using the `.fromValue` method. This causes faulty behavior. This happens in the `new Statement()` constructor as well. See #362. -- The `IndexedFormula.add()` method has logic for Statement array inputs and store inputs, but this behavior is not documented. It also refers to `this.fetcher` and `this.defaultGraph`, which both should not be available. I've added types that accept these arrays. -- The filenames of major classes differ from their default exports, e.g. `store.ts` is called `IndexedFormula`. -- Aliases (e.g. `IndexedFormula.match` for `IndexefFormula.statementsMatching`) introduce complexity, documentation and type duplication. I suggest adding deprecation warnings. -- The various calling methods of `Fetcher.nowOrWhenFetched` are quite dynamic. A simpler, stricter input type might be preferable. -- The Variable type (or `TFVariable`) really messes with some assumptions. I feel like they should not be valid in regular quads, since it's impossible to serialize them decently. If I'd add it to the accepted types, we'd require a lot of typeguards in functions. -- `Fetcher` `StatusValues` can be many types in RDFlib: string, number, true, undefined... This breaks compatibility with extending `Response` types, since these only use numbers. I feel we should only use the `499` browser error and add the text message to the `requestbody`. I've created a type for the internal `InternalResponse`; it shows how it differs from a regular `Response`. The `.responseText` property, for example, is used frequently in Fetcher, but -- The `IndexedFormula` and `Formula` methods have incompatible types, such as in `compareTerm`, `variable` and `add`. I've added `//@ts-ignore` lines with comments. -- The fourth `reponse` argument in `.parse()` methods in `Handler` classes was unused (except in N3Handler), so I removed it everywhere it was unused. -- `Serializer`'s fourth `options` argument is undocumented, and I couldn't find out how it worked. -- `Fetcher` `saveResponseMetadata` creates literals -- Many functions in `Fetcher` assume that specific `Opts` are defined. I've included all these in a single `Options` type and added documentation for the props I understood. I've also created an `AutoInitOptions` type, which sets auto-initialized. I extended Options in each function where specific opts seemed to be required. I'm not entirely confident about the types I've set. I feel like the truly required items should never be `Opts`, since they aren't optional. Refactoring this requires a seperate PR. -- `Fetcher.load` allows arrays as inputs. This causes the output types to be more unpredictable. `Promise | Result[]`. I suggest splitting this in two functions, e.g. add `loadMany` -- `Utils.callbackify` seems to be used only in `Fetcher`. -- `UpdateManager.editable` has a confusing return type (`string | boolean | undefined`). I suggest we refactor it to always return one of multiple pre-defined strings,. -- The `optional` argument in `formula.js` does not seem to be documented, used or tested - should it be removed? - -## Need review - -- Some of the `Formula` and `IndexedFormula` functions (e.g. `anyStatementMatching`) might have too strict types - perhaps Collections are allowed in some of them. -- `IndexedFormula.declareExistential` & `newExistential` have different assumptions on types - should they be blanknodes or namednodes? -- `IndexedFormula.check` passes a single statement to `checkStatementList`, which expects an array -- I've added many type assertions (e.g. `as TFObject`), but Ideally, these do not exist. Ultimately, these should be replaced by TypeGuards that work on runtime/ -- The `data-factory-types` are quite complex. This is a result of the differences between the RDF//JS Taskforce spec and rdflib itself. Perhaps there is an easier / cleaner way to setup the types (without heavy use of generics), but I'm afraid I can't think of one. - -## Some thoughts on simplifying language - -Getting started with Linked Data or RDF can be difficult, since it introduces quite a few new concepts. -Since this library is powerful and generic, it might be one of the first and most important RDF tools that a developer will use. -Therefore, we should try to use consistent langauge and keep synonyms to a minimum. - -- The name `Node` and `Term` seem to refer to the same concept. Both are used in this repo. I think Term is slightly more suited, partially because it complies to the TF spec, but also because it seems more sementically correct. A `Literal`, for example, is not really a node in the mathematical sense, it's more of an edge, since it cannot connect to other nodes. -- `Statement`, `Triple` and `Quad` refer to the same concept, at least technically. Maybe we could pick one. I suggest `Statement`, because it covers both triples and quads. -- The concept `graph` is referred to as `why`, `doc` and `graph` in the code and API. I think this might be confusing - should we just call it `graph` everywhere? -- the `IndexedFormula` default export name is different from the `store` filename. It might be easier to just call it `store` everywhere, including where it's called `kb`. - -## Probably OK, but I don't get it: - -- I'm a bit embarassed about this, but even after rewriting so much of the code I still don't understand all methods. E.g. `Forumla.transitiveClosure()` diff --git a/tsconfig.json b/tsconfig.json index bb63696f6..485ae3d98 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "module": "esnext", "strict": true, "noImplicitAny": false, + "noUnusedLocals": true, "lib": [ "es2019", "dom" diff --git a/types-temp.ts b/types-temp.ts deleted file mode 100644 index bee81ed0e..000000000 --- a/types-temp.ts +++ /dev/null @@ -1,293 +0,0 @@ -// This is a Temporary file to help with the migration to typescript. -// See issue: https://github.com/linkeddata/rdflib.js/issues/355 -// Migrate these types and comments to the according files, then remove them from this list. -// Don't import types from this file. -// When you do want to use a type from this file, move it to `./types.ts` -// And import it here. - -import { - Bindings, - ValueType, -} from './src/types' -import { NamedNode } from './src' - -// Type definitions for rdflib 0.20 -// Project: http://github.com/linkeddata/rdflib.js -// Definitions by: Cénotélie -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// TypeScript Version: 3.0 -// Acknowledgements: This work has been financed by Logilab SA, FRANCE, logilab.fr - -export namespace log { - /** - * Logs a debug event - * @param x The event - */ - function debug(x: any): void; - /** - * Logs a warning event - * @param x The event - */ - function warn(x: any): void; - /** - * Logs an information event - * @param x The event - */ - function info(x: any): void; - /** - * Logs an error event - * @param x The event - */ - function error(x: any): void; - /** - * Logs a success event - * @param x The event - */ - function success(x: any): void; - /** - * Logs a message event - * @param x The event - */ - function msg(x: any): void; -} - -export namespace convert { - /** - * Converts an n3 string to JSON - * @param n3String The n3 string - * @param jsonCallback Callback when the operation terminated - */ - function convertToJson( - n3String: string, - jsonCallback: (err: string, jsonString: string) => void - ): void; - /** - * Converts an n3 string to n-quads - * @param n3String The n3 string - * @param nquadCallback Callback when the operation terminated - */ - function convertToNQuads( - n3String: string, - nquadCallback: (err: string, nquadString: string) => void - ): void; -} - -export class Query { - pat: IndexedFormula; - name: string; - id?: string; - constructor(name: string, id?: any); - } - -export namespace Util { - /** - * Gets a named node for a media type - * @param mediaType A media type - */ - function mediaTypeClass(mediaType: string): NamedNode; - /** - * Gets a named node from the name of a relation - * @param relation The name of a relation - */ - function linkRelationProperty(relation: string): NamedNode; - /** - * Loads ontologies of the data we load (this is the callback from the kb to - * the fetcher). Exports as `AJAR_handleNewTerm` - * @param kb The store - * @param p A property - * @param requestedBy - */ - function AJAR_handleNewTerm( - kb: Formula, - p: NamedNode, - requestedBy: string - ): Promise; -} -/** -* A datatype-specific handler for fetching data -*/ -export interface Handler { - response: any; - dom: any; -} -export interface FetchOptions { - fetch?: typeof fetch; - /** - * The resource which referred to this (for tracking bad links). - */ - referringTerm?: NamedNode; - /** - * Provided content type (for writes). - */ - contentType?: string; - /** - * Override the incoming header to force the data to be treated as this content-type (for reads). - */ - forceContentType?: string; - /** - * Load the data even if loaded before. Also sets the `Cache-Control:` header to `no-cache`. - */ - force?: boolean; - /** - * Original uri to preserve through proxying etc (`xhr.original`). - */ - baseUri?: Node | string; - /** - * Whether this request is a retry via a proxy (generally done from an error handler). - */ - proxyUsed?: boolean; - /** - * Flag for XHR/CORS etc - */ - withCredentials?: boolean; - /** - * Before we parse new data, clear old, but only on status 200 responses. - */ - clearPreviousData?: boolean; - /** - * Prevents the addition of various metadata triples (about the fetch request) to the store. - */ - noMeta?: boolean; - noRDFa?: boolean; -} -/** -* Responsible for fetching RDF data -*/ -export class Fetcher { - store: any; - timeout: number; - appNode: BlankNode; - requested: { - [uri: string]: any; - }; - timeouts: any; - redirectedTo: any; - constructor(store: any, options: any); - static HANDLERS: { - RDFXMLHandler: Handler; - XHTMLHandler: Handler; - XMLHandler: Handler; - HTMLHandler: Handler; - TextHandler: Handler; - N3Handler: Handler; - }; - static CONTENT_TYPE_BY_EXT: { - [ext: string]: string; - }; - /** - * Loads a web resource or resources into the store. - * @param uri Resource to load, provided either as a NamedNode object or a plain URL. If multiple resources are passed as an array, they will be fetched in parallel. - */ - load: (uri: ReadonlyArray | ReadonlyArray | NamedNode | string, options?: FetchOptions) => Promise; -} -/** -* Gets a node for the specified input -* @param value An input value -*/ -export function term(value: ValueType): Node | Collection | ValueType; -/** -* Gets a namespace -* @param nsuri The URI for the namespace -*/ -export function Namespace(nsuri: string): (ln: string) => NamedNode; -/** -* Transforms an NTriples string format into a Node. -* The bnode bit should not be used on program-external values; designed -* for internal work such as storing a bnode id in an HTML attribute. -* This will only parse the strings generated by the vaious toNT() methods. -* @param str A string representation -*/ -export function fromNT(str: string): Node; -/** -* Creates a new fetcher -* @param store The store to use -* @param options The options -*/ -export function fetcher(store: Formula, options: any): Fetcher; -/** -* Creates a new graph (store) -*/ -export function graph(): IndexedFormula; -/** -* Creates a new literal node -* @param val The lexical value -* @param lang The language -* @param dt The datatype -*/ -export function lit(val: string, lang: string, dt: NamedNode): Literal; -/** -* Creates a new statement -* @param subject The subject -* @param predicate The predicate -* @param object The object -* @param graph The containing graph -*/ -export function st( - subject: Node | Date | string, - predicate: Node, - object: Node | Date | string, - graph: Node -): Statement; -/** -* Creates a new named node -* @param value The new named node -*/ -export function sym(value: string): NamedNode; -/** -* Creates a new variable -* @param name The name for the variable -*/ -export function variable(name: string): Variable; -/** -* Creates a new blank node -* @param value The blank node's identifier -*/ -export function blankNode(value: string): BlankNode; -/** -* Gets the default graph -*/ -export function defaultGraph(): DefaultGraph; -/** -* Creates a new literal node -* @param value The lexical value -* @param languageOrDatatype Either the language or the datatype -*/ -export function literal( - value: string, - languageOrDatatype: string | NamedNode -): Literal; -/** -* Creates a new named node -* @param value The new named node -*/ -export function namedNode(value: string): NamedNode; - -/** -* Creates a new statement -* @param subject The subject -* @param predicate The predicate -* @param object The object -*/ -export function triple(subject: Node, predicate: Node, object: Node): Statement; -/** -* Parse a string and put the result into the graph kb. -* Normal method is sync. -* Unfortunately jsdonld is currently written to need to be called async. -* Hence the mess below with executeCallback. -* @param str The input string to parse -* @param kb The store to use -* @param base The base URI to use -* @param contentType The content type for the input -* @param callback The callback to call when the data has been loaded -*/ -export function parse( - str: string, - kb: Formula, - base: string, - contentType: string, - callback: (error: any, kb: Formula) => void -): void; -/** -* Get the next available unique identifier -*/ -export let NextId: number; diff --git a/webpack.config.js b/webpack.config.js index 35324cd7a..5d76939a2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,5 @@ const path = require('path') const WrapperPlugin = require('wrapper-webpack-plugin'); -const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); module.exports = (env, args) => { return { @@ -39,8 +38,6 @@ module.exports = (env, args) => { window.solid.auth = { fetch: (a, b) => window.fetch(a, b) } }` }), - // Comment out the next line if you want to disable type checking. - new ForkTsCheckerWebpackPlugin() ], externals: { '@trust/webcrypto': 'crypto', From eb86e632c05245f405be86250787312d78c77d58 Mon Sep 17 00:00:00 2001 From: Fletcher91 Date: Tue, 3 Dec 2019 13:54:51 +0100 Subject: [PATCH 11/29] Extract all 'tf' types to a separate file Rename and move factory-types --- src/blank-node.ts | 3 +- src/collection.ts | 2 +- src/default-graph.ts | 3 +- src/empty.ts | 3 +- src/factories/canonical-data-factory.ts | 8 +- src/factories/extended-term-factory.ts | 5 +- .../factory-types.ts} | 78 ++++---- src/factories/rdflib-data-factory.ts | 3 +- src/fetcher.ts | 16 +- src/formula.ts | 20 +- src/literal.ts | 3 +- src/named-node.ts | 3 +- src/namespace.ts | 2 +- src/node-internal.ts | 4 +- src/node.ts | 2 +- src/parse.ts | 3 +- src/serialize.ts | 3 +- src/statement.ts | 7 +- src/store.ts | 21 ++- src/tf-types.ts | 173 ++++++++++++++++++ src/types.ts | 162 +--------------- src/update-manager.ts | 9 +- src/utils.ts | 2 +- src/utils/termValue.ts | 2 +- src/utils/terms.ts | 20 +- src/variable.ts | 3 +- .../factories/canonical-data-factory-test.ts | 2 +- .../factories/extended-term-factory-test.ts | 2 +- 28 files changed, 297 insertions(+), 267 deletions(-) rename src/{data-factory-type.ts => factories/factory-types.ts} (92%) create mode 100644 src/tf-types.ts diff --git a/src/blank-node.ts b/src/blank-node.ts index ac0d5e050..7e17d162d 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -1,7 +1,8 @@ import ClassOrder from './class-order' import Node from './node-internal' import IndexedFormula from './store' -import { BlankNodeTermType, TermType, TFBlankNode } from './types' +import { BlankNodeTermType, TermType} from './types' +import { TFBlankNode } from './tf-types' /** * An RDF blank node is a Node without a URI diff --git a/src/collection.ts b/src/collection.ts index 63c4e3a53..9c20471a7 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -7,11 +7,11 @@ import { CollectionTermType, FromValueReturns, TermType, - TFTerm, ValueType } from './types' import Variable from './variable' import { isTFTerm } from './utils/terms' +import { TFTerm } from './tf-types' /** * Creates an RDF Node from a native javascript value. diff --git a/src/default-graph.ts b/src/default-graph.ts index fe7d53cab..941ccc5ff 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -1,6 +1,7 @@ 'use strict' import Node from './node-internal' -import { TFDefaultGraph, TermType, DefaultGraphTermType } from './types' +import { TermType, DefaultGraphTermType } from './types' +import { TFDefaultGraph } from './tf-types' /** The RDF default graph */ export default class DefaultGraph extends Node implements TFDefaultGraph { diff --git a/src/empty.ts b/src/empty.ts index b0300a967..dd87a44eb 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,5 +1,6 @@ import Node from './node-internal' -import { TermType, TFTerm } from './types' +import { TermType} from './types' +import { TFTerm } from './tf-types' /** * An empty node diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index 28fee7f34..cb15db0bf 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -4,23 +4,21 @@ import NamedNode from '../named-node' import Statement from '../statement' import Variable from '../variable' import { - TFNamedNode, SubjectType, PredicateType, ObjectType, GraphType, TermType, - TFTerm, - TFQuad, } from '../types' import { defaultGraphNode } from '../utils/default-graph-uri' import { Comparable, DataFactory, DefaultFactoryTypes, - Feature -} from '../data-factory-type' + Feature, +} from './factory-types' import { isTFStatement, isTFTerm } from '../utils/terms' +import { TFNamedNode, TFQuad, TFTerm } from '../tf-types' export { defaultGraphURI } from '../utils/default-graph-uri' diff --git a/src/factories/extended-term-factory.ts b/src/factories/extended-term-factory.ts index 5d1005fb3..a0440b85b 100644 --- a/src/factories/extended-term-factory.ts +++ b/src/factories/extended-term-factory.ts @@ -1,9 +1,10 @@ import Collection from '../collection' import CanonicalDataFactory from './canonical-data-factory' -import { TermType, TFTerm, ValueType } from '../types' -import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from '../data-factory-type' +import { TermType, ValueType } from '../types' +import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from './factory-types' import { isCollection, isVariable } from '../utils/terms' import Variable from '../variable' +import { TFTerm } from '../tf-types' interface CollectionFactory extends DataFactory { collection(elements: ReadonlyArray): Collection diff --git a/src/data-factory-type.ts b/src/factories/factory-types.ts similarity index 92% rename from src/data-factory-type.ts rename to src/factories/factory-types.ts index aa0624579..dbc7a0e01 100644 --- a/src/data-factory-type.ts +++ b/src/factories/factory-types.ts @@ -1,20 +1,51 @@ +import Literal from '../literal' +import Statement from '../statement' +import NamedNode from '../named-node' +import BlankNode from '../blank-node' +import Variable from '../variable' import { - TFNamedNode, TFBlankNode, + TFDataFactory, TFLiteral, + TFNamedNode, TFQuad, TFTerm, TFVariable, - TFDataFactory, -} from './types' -import Literal from './literal' -import Statement from './statement' -import NamedNode from './named-node' -import BlankNode from './blank-node' -import Variable from './variable' +} from '../tf-types' + +export type Comparable = TFTerm | TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null export type DefaultFactoryTypes = NamedNode | BlankNode | Literal | Variable | Statement +export type Indexable = number | string + +export type Namespace = (term:string) => TFNamedNode + +/** A set of features that may be supported by a Data Factory */ +export type SupportTable = Record + +export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm + +export enum Feature { + /** Whether the factory supports termType:Collection terms */ + collections = "COLLECTIONS", + /** Whether the factory supports termType:DefaultGraph terms */ + defaultGraphType = "DEFAULT_GRAPH_TYPE", + /** Whether the factory supports equals on produced instances */ + equalsMethod = "EQUALS_METHOD", + /** Whether the factory can create a unique idempotent identifier for the given term. */ + id = "ID", + /** + * Whether the factory will return the same instance for subsequent calls. + * This implies `===`, which means methods like `indexOf` can be used. + */ + identity = "IDENTITY", + /** Whether the factory supports mapping ids back to instances (should adhere to the identity setting) */ + reversibleId = "REVERSIBLE_ID", + /** Whether the factory supports termType:Variable terms */ + variableType = "VARIABLE_TYPE", +} + /** * Defines a DataFactory as used in rdflib, based on the RDF/JS: Data model specification, * but with additional extensions @@ -62,34 +93,3 @@ export interface DataFactory< */ id(obj: TFTerm | FactoryTypes): IndexType } - -export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm - -export type Namespace = (term:string) => TFNamedNode - -/** A set of features that may be supported by a Data Factory */ -export type SupportTable = Record - -export enum Feature { - /** Whether the factory supports termType:Collection terms */ - collections = "COLLECTIONS", - /** Whether the factory supports termType:DefaultGraph terms */ - defaultGraphType = "DEFAULT_GRAPH_TYPE", - /** Whether the factory supports equals on produced instances */ - equalsMethod = "EQUALS_METHOD", - /** Whether the factory can create a unique idempotent identifier for the given term. */ - id = "ID", - /** - * Whether the factory will return the same instance for subsequent calls. - * This implies `===`, which means methods like `indexOf` can be used. - */ - identity = "IDENTITY", - /** Whether the factory supports mapping ids back to instances (should adhere to the identity setting) */ - reversibleId = "REVERSIBLE_ID", - /** Whether the factory supports termType:Variable terms */ - variableType = "VARIABLE_TYPE", -} - -export type Comparable = TFTerm | TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null - -export type Indexable = number | string diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts index e3a26ca50..997531365 100644 --- a/src/factories/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -3,14 +3,13 @@ import { ObjectType, PredicateType, SubjectType, - TFNamedNode, - TFTerm } from '../types' import Literal from '../literal' import Statement from '../statement' import IndexedFormula from '../store' import Fetcher from '../fetcher' import ExtendedTermFactory from './extended-term-factory' +import { TFNamedNode, TFTerm } from '../tf-types' /** Full RDFLib.js Data Factory */ const RDFlibDataFactory: IRDFlibDataFactory = { diff --git a/src/fetcher.ts b/src/fetcher.ts index fc9030fab..53cb5853a 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -43,15 +43,17 @@ import { fetch as solidAuthCli } from 'solid-auth-cli' // @ts-ignore This is injected import { fetch as solidAuthClient } from 'solid-auth-client' import { - TFBlankNode, - TFNamedNode, - TFGraph, - TFSubject, - ContentType, - TFDataFactory, - TFPredicate + ContentType } from './types' import { termValue } from './utils/termValue' +import { + TFBlankNode, + TFDataFactory, + TFGraph, + TFNamedNode, + TFPredicate, + TFSubject +} from './tf-types' // This is a special fetch which does OIDC auth, catching 401 errors const fetch = typeof window === 'undefined' ? solidAuthCli : solidAuthClient diff --git a/src/formula.ts b/src/formula.ts index 76aaa302b..6c4f26795 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -10,6 +10,15 @@ import Statement from './statement' import { Bindings, TermType, +} from './types' +import { isStatement } from './utils/terms' +import Variable from './variable' +import { + Indexable, + TFIDFactoryTypes, +} from './factories/factory-types' +import { appliedFactoryMethods, arrayToStatements } from './utils' +import { TFBlankNode, TFDataFactory, TFGraph, @@ -17,15 +26,8 @@ import { TFPredicate, TFQuad, TFSubject, - TFTerm -} from './types' -import { isStatement } from './utils/terms' -import Variable from './variable' -import { - Indexable, - TFIDFactoryTypes -} from './data-factory-type' -import { appliedFactoryMethods, arrayToStatements } from './utils' + TFTerm, +} from './tf-types' export interface FormulaOpts { dataCallback?: (q: TFQuad) => void diff --git a/src/literal.ts b/src/literal.ts index 257e28e6c..d40ddb98c 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -4,12 +4,11 @@ import Node from './node-internal' import { LiteralTermType, TermType, - TFLiteral, - TFTerm, ValueType } from './types' import { isTFLiteral } from './utils/terms' import XSD from './xsd-internal' +import { TFLiteral, TFTerm } from './tf-types' /** * An RDF literal, containing some value which isn't expressed as an IRI. diff --git a/src/named-node.ts b/src/named-node.ts index c243b7e2e..e1cace1fb 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -1,8 +1,9 @@ 'use strict' import ClassOrder from './class-order' import Node from './node-internal' -import { NamedNodeTermType, TermType, TFNamedNode } from './types' +import { NamedNodeTermType, TermType} from './types' import { termValue } from './utils/termValue' +import { TFNamedNode } from './tf-types' /** * A named (IRI) RDF node diff --git a/src/namespace.ts b/src/namespace.ts index 63066df4b..68c1921ba 100644 --- a/src/namespace.ts +++ b/src/namespace.ts @@ -1,5 +1,5 @@ import NamedNode from './named-node' -import { TFDataFactory, TFNamedNode } from './types' +import { TFDataFactory, TFNamedNode } from './tf-types' /** * Gets a namespace for the specified namespace's URI diff --git a/src/node-internal.ts b/src/node-internal.ts index e2dd7c8e0..fed3941b4 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -1,4 +1,5 @@ -import { ValueType, Bindings, TFTerm, FromValueReturns } from './types' +import { ValueType, Bindings, FromValueReturns } from './types' +import { TFTerm } from './tf-types' /** * The superclass of all RDF Statement objects, that is @@ -38,6 +39,7 @@ export default abstract class Node { /** * Compares this node with another + * @see {equals} to check if two nodes are equal * @param other - The other node */ compareTerm (other: Node): number { diff --git a/src/node.ts b/src/node.ts index d5b887961..8566908af 100644 --- a/src/node.ts +++ b/src/node.ts @@ -2,9 +2,9 @@ // that would otherwise require circular dependencies. import { fromValue } from './collection' import Node from './node-internal' -import { TFTerm } from './types' import Namespace from './namespace' import { isCollection, isTFLiteral } from './utils/terms' +import { TFTerm } from './tf-types' /** * Creates an RDF Node from a native javascript value. diff --git a/src/parse.ts b/src/parse.ts index 0d6427b02..f972494e8 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -8,7 +8,8 @@ import RDFParser from './rdfxmlparser' import sparqlUpdateParser from './patch-parser' import * as Util from './util' import Formula from './formula' -import { TFQuad, ContentType } from './types' +import { ContentType } from './types' +import { TFQuad } from './tf-types' type CallbackFunc = (error: any, kb: Formula | null) => void diff --git a/src/serialize.ts b/src/serialize.ts index 39029a5ef..1f948b773 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,8 +1,9 @@ import * as convert from './convert' import Formula from './formula' import Serializer from './serializer' -import { ContentType, TFNamedNode, TFBlankNode } from './types' +import { ContentType} from './types' import IndexedFormula from './store' +import { TFBlankNode, TFNamedNode } from './tf-types' /** * Serialize to the appropriate format diff --git a/src/statement.ts b/src/statement.ts index 3e054d121..5bd25a905 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -6,14 +6,9 @@ import { PredicateType, SubjectType, TermType, - TFGraph, - TFObject, - TFPredicate, - TFQuad, - TFSubject, - TFTerm, } from './types' import { defaultGraphNode } from './utils/default-graph-uri' +import { TFGraph, TFObject, TFPredicate, TFQuad, TFSubject, TFTerm } from './tf-types' /** A Statement represents an RDF Triple or Quad. */ export default class Statement implements TFQuad { diff --git a/src/store.ts b/src/store.ts index 8840542ec..8527e5fc3 100644 --- a/src/store.ts +++ b/src/store.ts @@ -36,19 +36,22 @@ import { Query, indexedFormulaQuery } from './query' import UpdateManager from './update-manager' import { Bindings, - TFTerm, - TFPredicate, - TFSubject, - TFObject, - TFGraph, - TFQuad, - TFNamedNode, - TFBlankNode, + + + } from './types' import Statement from './statement' -import { Indexable } from './data-factory-type' +import { Indexable } from './factories/factory-types' import NamedNode from './named-node' import Fetcher from './fetcher' +import { + TFBlankNode, + TFGraph, TFNamedNode, + TFObject, + TFPredicate, + TFQuad, + TFSubject, TFTerm +} from './tf-types' const owlNamespaceURI = 'http://www.w3.org/2002/07/owl#' diff --git a/src/tf-types.ts b/src/tf-types.ts new file mode 100644 index 000000000..5f8c488f4 --- /dev/null +++ b/src/tf-types.ts @@ -0,0 +1,173 @@ +import { SupportTable } from './factories/factory-types' +import { + BlankNodeTermType, + DefaultGraphTermType, + LiteralTermType, + NamedNodeTermType, + VariableTermType, +} from './types' + +/** + * RDF/JS taskforce Term + * @link https://rdf.js.org/data-model-spec/#term-interface + */ +export interface TFTerm { + termType: string + value: string + + /** + * Compare this term with {other} for structural equality + * + * Note that the task force spec only allows comparison with other terms + */ + equals (other: any): boolean +} + +/** + * RDF/JS taskforce NamedNode + * @link https://rdf.js.org/data-model-spec/#namednode-interface + */ +export interface TFNamedNode extends TFTerm { + termType: NamedNodeTermType + value: string +} + +/** + * RDF/JS taskforce Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface TFBlankNode extends TFTerm { + termType: BlankNodeTermType + value: string +} + +/** + * RDF/JS taskforce Quad + * @link https://rdf.js.org/data-model-spec/#quad-interface + */ +export interface TFQuad< + S extends TFTerm = TFSubject, + P extends TFTerm = TFPredicate, + O extends TFTerm = TFObject, + G extends TFTerm = TFGraph +> { + subject: S + predicate: P + object: O + graph: G +} + +/** + * RDF/JS taskforce Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface TFLiteral extends TFTerm { + /** Contains the constant "Literal". */ + termType: LiteralTermType + /** The text value, unescaped, without language or type (example: "Brad Pitt") */ + value: string + /** + * The language as lowercase BCP-47 [BCP47] string (examples: "en", "en-gb") + * or an empty string if the literal has no language. + */ + language: string + /** A NamedNode whose IRI represents the datatype of the literal. */ + datatype: TFNamedNode +} + +/** + * RDF/JS taskforce Variable + * @link https://rdf.js.org/data-model-spec/#variable-interface + */ +export interface TFVariable extends TFTerm { + /** Contains the constant "Variable". */ + termType: VariableTermType + /** The name of the variable without leading "?" (example: "a"). */ + value: string +} + +/** + * RDF/JS taskforce DefaultGraph + * An instance of DefaultGraph represents the default graph. + * It's only allowed to assign a DefaultGraph to the graph property of a Quad. + * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface + */ +export interface TFDefaultGraph extends TFTerm { + termType: DefaultGraphTermType; + /** should return and empty string'' */ + value: string; +} + +/** + * RDF/JS taskforce DataFactory + * + * Not 100% compliant due to to practicality problems. + * + * @link https://rdf.js.org/data-model-spec/#datafactory-interface + */ +export interface TFDataFactory { + /** Returns a new instance of NamedNode. */ + namedNode: (value: string) => TFNamedNode, + + /** + * Returns a new instance of BlankNode. + * If the value parameter is undefined a new identifier for the + * blank node is generated for each call. + */ + blankNode: (value?: string) => TFBlankNode, + + /** + * Returns a new instance of Literal. + * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. + * Otherwise languageOrDatatype is used for the value of language. */ + literal: (value: string, languageOrDatatype: string | TFNamedNode) => TFLiteral, + + /** Returns a new instance of Variable. This method is optional. */ + variable?: (value: string) => TFVariable, + + /** + * Returns an instance of DefaultGraph. + */ + defaultGraph: () => TFDefaultGraph | TFNamedNode | TFBlankNode, + + /** + * Returns a new instance of the specific Term subclass given by original.termType + * (e.g., NamedNode, BlankNode, Literal, etc.), + * such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. + */ + fromTerm?: (original: TFTerm) => TFTerm + + /** + * Returns a new instance of Quad, such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. + */ + fromQuad?: (original: TFQuad) => TFQuad + + /** + * Returns a new instance of Quad. + * If graph is undefined or null it MUST set graph to a DefaultGraph. + */ + quad: ( + subject: TFTerm, + predicate: TFTerm, + object: TFTerm, + graph?: TFTerm, + ) => TFQuad + + /** + * Check for specific features/behaviour on the factory. + * + * This does not exist on the original RDF/JS spec + */ + supports: SupportTable +} + +/** A RDF/JS taskforce Subject */ +export type TFSubject = TFNamedNode | TFBlankNode | TFVariable +/** A RDF/JS taskforce Predicate */ +export type TFPredicate = TFNamedNode | TFVariable +/** A RDF/JS taskforce Object */ +export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable +/** A RDF/JS taskforce Graph */ +export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable diff --git a/src/types.ts b/src/types.ts index cacdf5649..119bd5302 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,11 +5,12 @@ import Collection from './collection' import Literal from './literal' import NamedNode from './named-node' import DefaultGraph from './default-graph' -import { DataFactory, SupportTable } from './data-factory-type' +import { DataFactory } from './factories/factory-types' import IndexedFormula from './store' import Fetcher from './fetcher' import Statement from './statement' import Empty from './empty' +import { TFNamedNode, TFTerm } from './tf-types' /** * Types that support both Enums (for typescript) and regular strings @@ -78,169 +79,10 @@ export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable /** An RDF/JS Graph */ export type GraphType = DefaultGraph | NamedNode | Variable // | Formula -/** - * RDF/JS taskforce Term - * @link https://rdf.js.org/data-model-spec/#term-interface - */ -export interface TFTerm { - termType: string - value: string - equals(other: any): boolean -} - -/** - * RDF/JS taskforce NamedNode - * @link https://rdf.js.org/data-model-spec/#namednode-interface - */ -export interface TFNamedNode extends TFTerm { - termType: NamedNodeTermType - value: string -} - -/** - * RDF/JS taskforce Literal - * @link https://rdf.js.org/data-model-spec/#literal-interface - */ -export interface TFBlankNode extends TFTerm { - termType: BlankNodeTermType - value: string -} - -/** - * RDF/JS taskforce Quad - * @link https://rdf.js.org/data-model-spec/#quad-interface - */ -export interface TFQuad< - S extends TFTerm = TFSubject, - P extends TFTerm = TFPredicate, - O extends TFTerm = TFObject, - G extends TFTerm = TFGraph -> { - subject: S - predicate: P - object: O - graph: G -} - -/** - * RDF/JS taskforce Literal - * @link https://rdf.js.org/data-model-spec/#literal-interface - */ -export interface TFLiteral extends TFTerm { - /** Contains the constant "Literal". */ - termType: LiteralTermType - /** The text value, unescaped, without language or type (example: "Brad Pitt") */ - value: string - /** - * The language as lowercase BCP-47 [BCP47] string (examples: "en", "en-gb") - * or an empty string if the literal has no language. - */ - language: string - /** A NamedNode whose IRI represents the datatype of the literal. */ - datatype: TFNamedNode -} - -/** - * RDF/JS taskforce Variable - * @link https://rdf.js.org/data-model-spec/#variable-interface - */ -export interface TFVariable extends TFTerm { - /** Contains the constant "Variable". */ - termType: VariableTermType - /** The name of the variable without leading "?" (example: "a"). */ - value: string -} - -/** - * RDF/JS taskforce DefaultGraph - * An instance of DefaultGraph represents the default graph. - * It's only allowed to assign a DefaultGraph to the graph property of a Quad. - * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface - */ -export interface TFDefaultGraph extends TFTerm { - termType: DefaultGraphTermType; - /** should return and empty string'' */ - value: string; -} - -/** - * RDF/JS taskforce DataFactory - * - * Not 100% compliant due to to practicality problems. - * - * @link https://rdf.js.org/data-model-spec/#datafactory-interface - */ -export interface TFDataFactory { - /** Returns a new instance of NamedNode. */ - namedNode: (value: string) => TFNamedNode, - - /** - * Returns a new instance of BlankNode. - * If the value parameter is undefined a new identifier for the - * blank node is generated for each call. - */ - blankNode: (value?: string) => TFBlankNode, - - /** - * Returns a new instance of Literal. - * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. - * Otherwise languageOrDatatype is used for the value of language. */ - literal: (value: string, languageOrDatatype: string | TFNamedNode) => TFLiteral, - - /** Returns a new instance of Variable. This method is optional. */ - variable?: (value: string) => TFVariable, - - /** - * Returns an instance of DefaultGraph. - */ - defaultGraph: () => TFDefaultGraph | TFNamedNode | TFBlankNode, - - /** - * Returns a new instance of the specific Term subclass given by original.termType - * (e.g., NamedNode, BlankNode, Literal, etc.), - * such that newObject.equals(original) returns true. - * Not implemented in RDFJS, so optional. - */ - fromTerm?: (original: TFTerm) => TFTerm - - /** - * Returns a new instance of Quad, such that newObject.equals(original) returns true. - * Not implemented in RDFJS, so optional. - */ - fromQuad?: (original: TFQuad) => TFQuad - - /** - * Returns a new instance of Quad. - * If graph is undefined or null it MUST set graph to a DefaultGraph. - */ - quad: ( - subject: TFTerm, - predicate: TFTerm, - object: TFTerm, - graph?: TFTerm, - ) => TFQuad - - /** - * Check for specific features/behaviour on the factory. - * - * This does not exist on the original RDF/JS spec - */ - supports: SupportTable -} - export interface Bindings { [id: string]: TFTerm; } -/** A RDF/JS taskforce Subject */ -export type TFSubject = TFNamedNode | TFBlankNode | TFVariable -/** A RDF/JS taskforce Predicate */ -export type TFPredicate = TFNamedNode | TFVariable -/** A RDF/JS taskforce Object */ -export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable -/** A RDF/JS taskforce Graph */ -export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable - /** All the types that a .fromValue() method might return */ export type FromValueReturns = TFTerm | undefined | null | Collection diff --git a/src/update-manager.ts b/src/update-manager.ts index 08df881ff..5b01e5e88 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -14,8 +14,15 @@ import { isStore, isTFBlankNode } from './utils/terms' import * as Util from './util' import Statement from './statement' import NamedNode from './named-node' -import { TFNamedNode, TFQuad, TFBlankNode, TFSubject, TFPredicate, TFObject, TFGraph, TFTerm } from './types' import { termValue } from './utils/termValue' +import { + TFBlankNode, + TFGraph, TFNamedNode, + TFObject, + TFPredicate, + TFQuad, + TFSubject, TFTerm +} from './tf-types' interface UpdateManagerFormula extends IndexedFormula { fetcher: Fetcher diff --git a/src/utils.ts b/src/utils.ts index ab9529e72..389fff8fa 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,8 @@ import Fetcher from './fetcher' import log from './log' -import { TFDataFactory, TFLiteral, TFQuad, TFSubject, TFTerm } from './types' import { docpart } from './uri' import { string_startswith } from './util' +import { TFDataFactory, TFQuad, TFSubject, TFTerm } from './tf-types' /** RDF/JS Taskforce Typeguards */ diff --git a/src/utils/termValue.ts b/src/utils/termValue.ts index 8c34f1504..3017f2621 100644 --- a/src/utils/termValue.ts +++ b/src/utils/termValue.ts @@ -1,4 +1,4 @@ -import { TFTerm } from '../types' +import { TFTerm } from '../tf-types' /** Retrieve the value of a term, or self if already a string. */ export function termValue (node: TFTerm | string): string { diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 77b9c0bfe..3b9f9194e 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -1,21 +1,21 @@ import { ObjectType, - TermType, - TFBlankNode, - TFGraph, - TFLiteral, - TFNamedNode, - TFObject, - TFPredicate, - TFQuad, - TFSubject, - TFTerm + TermType } from '../types' import Collection from '../collection' import IndexedFormula from '../store' import Statement from '../statement' import NamedNode from '../named-node' import Variable from '../variable' +import { + TFBlankNode, + TFGraph, + TFLiteral, TFNamedNode, + TFObject, + TFPredicate, + TFQuad, + TFSubject, TFTerm +} from '../tf-types' export function isStatement(obj): obj is Statement { return typeof obj === 'object' && obj !== null && 'subject' in obj diff --git a/src/variable.ts b/src/variable.ts index 7f7704325..c347f543e 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -1,7 +1,8 @@ import ClassOrder from './class-order' import Node from './node-internal' -import { TermType, TFVariable, VariableTermType } from './types' +import { TermType, VariableTermType } from './types' import * as Uri from './uri' +import { TFVariable } from './tf-types' /** * Variables are placeholders used in patterns to be matched. diff --git a/tests/unit/factories/canonical-data-factory-test.ts b/tests/unit/factories/canonical-data-factory-test.ts index 85f43d9e6..b26f3e2c4 100644 --- a/tests/unit/factories/canonical-data-factory-test.ts +++ b/tests/unit/factories/canonical-data-factory-test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import Factory from '../../../src/factories/canonical-data-factory'; -import { Feature } from '../../../src/data-factory-type' +import { Feature } from '../../../src/factories/factory-types' import NamedNode from '../../../src/named-node' import Literal from '../../../src/literal' import DefaultGraph from '../../../src/default-graph' diff --git a/tests/unit/factories/extended-term-factory-test.ts b/tests/unit/factories/extended-term-factory-test.ts index 2c0be50f1..953325432 100644 --- a/tests/unit/factories/extended-term-factory-test.ts +++ b/tests/unit/factories/extended-term-factory-test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import Factory from '../../../src/factories/extended-term-factory'; -import { Feature } from '../../../src/data-factory-type' +import { Feature } from '../../../src/factories/factory-types' import Collection from '../../../src/collection' /** From cae463f59afa46b06e5db836a345a7e93764fef4 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 9 Dec 2019 09:29:48 +0100 Subject: [PATCH 12/29] fixup! Patch up the test suite --- tests/unit/fetcher-test.js | 74 +++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/tests/unit/fetcher-test.js b/tests/unit/fetcher-test.js index 0dccbeefb..f9b66dfe9 100644 --- a/tests/unit/fetcher-test.js +++ b/tests/unit/fetcher-test.js @@ -171,11 +171,11 @@ describe('Fetcher', () => { }) }) - // describe('load', () => { - // // let fetcher, uri, options, xhr - // - // it('should load multiple docs') - // }) + describe('load', () => { + // let fetcher, uri, options, xhr + + it('should load multiple docs') + }) describe('load', () => { let fetcher, uri, options @@ -436,35 +436,35 @@ describe('Fetcher', () => { }) }) - // describe('guessContentType', () => { - // it('should return null if uri has no extension') - // - // it('should return null if unknown extension') - // - // it('it should return the content type for a known extension') - // }) - // - // describe('normalizedContentType', () => { - // it('should return the forced content type if present') - // - // it('should try to guess content type if none returned in header') - // - // it('should try to guess content type for octet-stream generic type') - // - // it('should return the content type in the headers') - // - // it('should default to text/xml for file: protocol uris') - // - // it('should default to text/xml for chrome: protocol uris') - // }) - // - // describe('handlerForContentType', () => { - // it('should return null when no contentType given') - // - // it('should return a handler instance if content type matches') - // - // it('should return null when no handler match is found') - // }) + describe('guessContentType', () => { + it('should return null if uri has no extension') + + it('should return null if unknown extension') + + it('it should return the content type for a known extension') + }) + + describe('normalizedContentType', () => { + it('should return the forced content type if present') + + it('should try to guess content type if none returned in header') + + it('should try to guess content type for octet-stream generic type') + + it('should return the content type in the headers') + + it('should default to text/xml for file: protocol uris') + + it('should default to text/xml for chrome: protocol uris') + }) + + describe('handlerForContentType', () => { + it('should return null when no contentType given') + + it('should return a handler instance if content type matches') + + it('should return null when no handler match is found') + }) describe('load nock tests', () => { let fetcher @@ -566,7 +566,7 @@ describe('Fetcher', () => { }) }) - // describe('createContainer', () => { - // it('should invoke webOperation with the right options') - // }) + describe('createContainer', () => { + it('should invoke webOperation with the right options') + }) }) From 4f3053dce31de81e3eda2dcb2f12c941832a870c Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 9 Dec 2019 09:53:35 +0100 Subject: [PATCH 13/29] Typeguard fixes & refactor TF names --- reference/fetcher-classes.js | 2 +- src/collection.ts | 8 +-- src/empty.ts | 4 +- src/factories/canonical-data-factory.ts | 28 ++++---- src/factories/extended-term-factory.ts | 8 +-- src/factories/factory-types.ts | 24 +++---- src/factories/rdflib-data-factory.ts | 10 +-- src/formula.ts | 46 ++++++------- src/literal.ts | 4 +- src/node-internal.ts | 4 +- src/node.ts | 4 +- src/parse.ts | 4 +- src/statement.ts | 14 ++-- src/store.ts | 88 ++++++++++++------------- src/tf-types.ts | 36 +++++----- src/types.ts | 8 +-- src/update-manager.ts | 26 ++++---- src/utils.ts | 8 +-- src/utils/termValue.ts | 4 +- src/utils/terms.ts | 33 +++++----- tests/unit/util-test.js | 25 ++++++- 21 files changed, 203 insertions(+), 185 deletions(-) diff --git a/reference/fetcher-classes.js b/reference/fetcher-classes.js index 7635a9847..b907ceb23 100644 --- a/reference/fetcher-classes.js +++ b/reference/fetcher-classes.js @@ -1,4 +1,4 @@ -import { isTFNamedNode } from '../src/utils' +import { isTFNamedNode} from './utils/terms' const log = require('./log') const N3Parser = require('./n3parser') diff --git a/src/collection.ts b/src/collection.ts index 9c20471a7..91736e254 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -10,8 +10,8 @@ import { ValueType } from './types' import Variable from './variable' -import { isTFTerm } from './utils/terms' -import { TFTerm } from './tf-types' +import { isTerm } from './utils/terms' +import { Term } from './tf-types' /** * Creates an RDF Node from a native javascript value. @@ -26,7 +26,7 @@ export function fromValue = any, C extends Node = return value as T } - if (isTFTerm(value)) { // a Node subclass or a Collection + if (isTerm(value)) { // a Node subclass or a Collection return value as T } @@ -44,7 +44,7 @@ export function fromValue = any, C extends Node = */ export default class Collection< T extends Node = Node | BlankNode | Collection | Literal | Variable -> extends Node implements TFTerm { +> extends Node implements Term { static termType = TermType.Collection classOrder = ClassOrder.Collection diff --git a/src/empty.ts b/src/empty.ts index dd87a44eb..0ae4db5ec 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,11 +1,11 @@ import Node from './node-internal' import { TermType} from './types' -import { TFTerm } from './tf-types' +import { Term } from './tf-types' /** * An empty node */ -export default class Empty extends Node implements TFTerm { +export default class Empty extends Node implements Term { static termType = TermType.Empty termType = TermType.Empty diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index cb15db0bf..665abeeff 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -17,8 +17,8 @@ import { DefaultFactoryTypes, Feature, } from './factory-types' -import { isTFStatement, isTFTerm } from '../utils/terms' -import { TFNamedNode, TFQuad, TFTerm } from '../tf-types' +import { isQuad, isTerm } from '../utils/terms' +import { TFNamedNode, Quad, Term } from '../tf-types' export { defaultGraphURI } from '../utils/default-graph-uri' @@ -60,8 +60,8 @@ const CanonicalDataFactory: DataFactory = { return true } - if (isTFStatement(a) || isTFStatement(b)) { - if (isTFStatement(a) && isTFStatement(b)) { + if (isQuad(a) || isQuad(b)) { + if (isQuad(a) && isQuad(b)) { return ( this.equals(a.subject, b.subject) && this.equals(a.predicate, b.predicate) && @@ -73,7 +73,7 @@ const CanonicalDataFactory: DataFactory = { return false } - if (isTFTerm(a) && isTFTerm(b)) { + if (isTerm(a) && isTerm(b)) { return this.id(a) === this.id(b) } @@ -88,12 +88,12 @@ const CanonicalDataFactory: DataFactory = { * @example Use this to associate data with a term in an object * { obj[id(term)] = "myData" } */ - id (term: TFTerm | Statement | undefined): string { + id (term: Term | Statement | undefined): string { if (!term) { return 'undefined' } - if (isTFStatement(term)) { + if (isQuad(term)) { return this.quadToNQ(term) } @@ -157,21 +157,21 @@ const CanonicalDataFactory: DataFactory = { * @param graph - The containing graph */ quad( - subject: TFTerm | SubjectType, - predicate: TFTerm | PredicateType, - object: TFTerm | ObjectType, - graph?: TFTerm | GraphType + subject: Term | SubjectType, + predicate: Term | PredicateType, + object: Term | ObjectType, + graph?: Term | GraphType ): Statement { graph = graph || defaultGraph() return new Statement(subject, predicate, object, graph) }, - quadToNQ(q: Statement | TFQuad): string { + quadToNQ(q: Statement | Quad): string { return `${this.termToNQ(q.subject)} ${this.termToNQ(q.predicate)} ${this.termToNQ(q.object)} ${this.termToNQ(q.graph)} .`; }, /** Stringify a {term} to n-quads serialization. */ - termToNQ(term: TFTerm): string { + termToNQ(term: Term): string { switch (term.termType) { case TermType.BlankNode: return '_:' + term.value @@ -189,7 +189,7 @@ const CanonicalDataFactory: DataFactory = { }, /** Convert an rdf object (term or quad) to n-quads serialization. */ - toNQ (term: TFTerm | (DefaultFactoryTypes & Variable)): string { + toNQ (term: Term | (DefaultFactoryTypes & Variable)): string { if (this.isQuad(term)) { return this.quadToNQ(term); } diff --git a/src/factories/extended-term-factory.ts b/src/factories/extended-term-factory.ts index a0440b85b..f89f2e269 100644 --- a/src/factories/extended-term-factory.ts +++ b/src/factories/extended-term-factory.ts @@ -4,7 +4,7 @@ import { TermType, ValueType } from '../types' import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from './factory-types' import { isCollection, isVariable } from '../utils/terms' import Variable from '../variable' -import { TFTerm } from '../tf-types' +import { Term } from '../tf-types' interface CollectionFactory extends DataFactory { collection(elements: ReadonlyArray): Collection @@ -36,9 +36,9 @@ const ExtendedTermFactory: CollectionFactory = { return new Collection(elements) }, - id (term: TFTerm | DefaultFactoryTypes): Indexable { + id (term: Term | DefaultFactoryTypes): Indexable { if (isCollection(term)) { - return `( ${term.elements.map((e) => { + return `( ${term.elements.map((e) => { return this.id(e) }).join(', ')} )` } @@ -49,7 +49,7 @@ const ExtendedTermFactory: CollectionFactory = { return CanonicalDataFactory.id(term) }, - termToNQ (term: TFTerm): string { + termToNQ (term: Term): string { if (term.termType === TermType.Collection) { return Collection.toNT(term) } diff --git a/src/factories/factory-types.ts b/src/factories/factory-types.ts index dbc7a0e01..b24369352 100644 --- a/src/factories/factory-types.ts +++ b/src/factories/factory-types.ts @@ -8,12 +8,12 @@ import { TFDataFactory, TFLiteral, TFNamedNode, - TFQuad, - TFTerm, + Quad, + Term, TFVariable, } from '../tf-types' -export type Comparable = TFTerm | TFNamedNode | TFBlankNode | TFLiteral | TFQuad | undefined | null +export type Comparable = Term | TFNamedNode | TFBlankNode | TFLiteral | Quad | undefined | null export type DefaultFactoryTypes = NamedNode | BlankNode | Literal | Variable | Statement @@ -24,7 +24,7 @@ export type Namespace = (term:string) => TFNamedNode /** A set of features that may be supported by a Data Factory */ export type SupportTable = Record -export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | TFQuad | TFVariable | TFTerm +export type TFIDFactoryTypes = TFNamedNode | TFBlankNode | TFLiteral | Quad | TFVariable | Term export enum Feature { /** Whether the factory supports termType:Collection terms */ @@ -70,18 +70,18 @@ export interface DataFactory< equals(a: Comparable, b: Comparable): boolean - toNQ(term: TFTerm | FactoryTypes): string + toNQ(term: Term | FactoryTypes): string quad( - subject: TFTerm, - predicate: TFTerm, - object: TFTerm, - graph?: TFTerm, + subject: Term, + predicate: Term, + object: Term, + graph?: Term, ): Statement; - quadToNQ(term: Statement | TFQuad): string + quadToNQ(term: Statement | Quad): string - termToNQ(term: TFTerm): string + termToNQ(term: Term): string /** * Generates a unique session-idempotent identifier for the given object. @@ -91,5 +91,5 @@ export interface DataFactory< * * @return {Indexable} A unique value which must also be a valid JS object key type. */ - id(obj: TFTerm | FactoryTypes): IndexType + id(obj: Term | FactoryTypes): IndexType } diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts index 997531365..71b11ebf1 100644 --- a/src/factories/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -9,7 +9,7 @@ import Statement from '../statement' import IndexedFormula from '../store' import Fetcher from '../fetcher' import ExtendedTermFactory from './extended-term-factory' -import { TFNamedNode, TFTerm } from '../tf-types' +import { TFNamedNode, Term } from '../tf-types' /** Full RDFLib.js Data Factory */ const RDFlibDataFactory: IRDFlibDataFactory = { @@ -51,10 +51,10 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @deprecated use {quad} instead */ st ( - subject: TFTerm, - predicate: TFTerm, - object: TFTerm, - graph?: TFTerm + subject: Term, + predicate: Term, + object: Term, + graph?: Term ): Statement { return this.quad(subject, predicate, object, graph) }, diff --git a/src/formula.ts b/src/formula.ts index 6c4f26795..c265a00d5 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -24,14 +24,14 @@ import { TFGraph, TFObject, TFPredicate, - TFQuad, + Quad, TFSubject, - TFTerm, + Term, } from './tf-types' export interface FormulaOpts { - dataCallback?: (q: TFQuad) => void - rdfArrayRemove?: (arr: TFQuad[], q: TFQuad) => void + dataCallback?: (q: Quad) => void + rdfArrayRemove?: (arr: Quad[], q: Quad) => void rdfFactory?: TFDataFactory } @@ -40,7 +40,7 @@ interface BooleanMap { } interface MembersMap { - [uri: string]: TFQuad; + [uri: string]: Quad; } interface UriMap { @@ -81,7 +81,7 @@ export default class Formula extends Node { rdfFactory: any /** The stored statements */ - statements: TFQuad[]; + statements: Quad[]; termType = TermType.Graph @@ -96,7 +96,7 @@ export default class Formula extends Node { * @param opts.rdfFactory - The rdf factory that should be used by the store */ constructor ( - statements?: TFQuad[], + statements?: Quad[], constraints?: ReadonlyArray, initBindings?: ReadonlyArray, optional?: ReadonlyArray, @@ -135,7 +135,7 @@ export default class Formula extends Node { /** Add a statment object * @param {Statement} statement - An existing constructed statement to add */ - addStatement (statement: TFQuad): number { + addStatement (statement: Quad): number { return this.statements.push(statement) } @@ -148,7 +148,7 @@ export default class Formula extends Node { * Adds all the statements to this formula * @param statements - A collection of statements */ - addAll (statements: TFQuad[]): void { + addAll (statements: Quad[]): void { statements.forEach(quad => { this.add(quad.subject, quad.predicate, quad.object, quad.graph) }) @@ -171,7 +171,7 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): TFTerm | null | undefined { + ): Term | null | undefined { const st = this.anyStatementMatching(s, p, o, g) if (st == null) { return void 0 @@ -228,7 +228,7 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): TFQuad | undefined { + ): Quad | undefined { let x = this.statementsMatching(s, p, o, g, true) if (!x || x.length === 0) { return undefined @@ -263,7 +263,7 @@ export default class Formula extends Node { o?: TFObject | null, g?: TFGraph | null, justOne?: boolean - ): TFQuad[] { + ): Quad[] { const sts = this.statements.filter(st => (!s || s.equals(st.subject)) && (!p || p.equals(st.predicate)) && @@ -342,8 +342,8 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): TFTerm[] { - const results: TFTerm[] = [] + ): Term[] { + const results: Term[] = [] let sts = this.statementsMatching(s, p, o, g, false) if (s == null) { for (let i = 0, len = sts.length; i < len; i++) { @@ -393,11 +393,11 @@ export default class Formula extends Node { let members: MembersMap let pred: TFPredicate let ref - let ref1: TFQuad[] - let ref2: TFTerm[] - let ref3: TFQuad[] - let ref4: TFTerm[] - let ref5: TFQuad[] + let ref1: Quad[] + let ref2: Term[] + let ref3: Quad[] + let ref4: Term[] + let ref5: Quad[] let seeds let st let u: number @@ -566,12 +566,12 @@ export default class Formula extends Node { subject: TFSubject, doc: TFGraph, excludePredicateURIs?: ReadonlyArray - ): TFQuad[] { + ): Quad[] { excludePredicateURIs = excludePredicateURIs || [] let todo = [subject] let done: { [k: string]: boolean } = {} let doneArcs: { [k: string]: boolean } = {} - let result: TFQuad[] = [] + let result: Quad[] = [] let self = this let follow = function (x) { let queue = function (x) { @@ -771,7 +771,7 @@ export default class Formula extends Node { }) console.log('Formula subs statmnts:' + statementsCopy) const y = new Formula() - y.addAll(statementsCopy as TFQuad[]) + y.addAll(statementsCopy as Quad[]) console.log('indexed-form subs formula:' + y) return y } @@ -798,7 +798,7 @@ export default class Formula extends Node { p?: TFPredicate | null, o?: TFObject | null, g?: TFGraph | null - ): TFTerm | null | undefined { + ): Term | null | undefined { let x = this.any(s, p, o, g) if (x == null) { log.error('No value found for the() {' + s + ' ' + p + ' ' + o + '}.') diff --git a/src/literal.ts b/src/literal.ts index d40ddb98c..9642c0fc2 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -8,7 +8,7 @@ import { } from './types' import { isTFLiteral } from './utils/terms' import XSD from './xsd-internal' -import { TFLiteral, TFTerm } from './tf-types' +import { TFLiteral, Term } from './tf-types' /** * An RDF literal, containing some value which isn't expressed as an IRI. @@ -64,7 +64,7 @@ export default class Literal extends Node implements TFLiteral { * Gets whether two literals are the same * @param other The other statement */ - equals (other: TFTerm): boolean { + equals (other: Term): boolean { if (!other) { return false } diff --git a/src/node-internal.ts b/src/node-internal.ts index fed3941b4..a3e728bb6 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -1,5 +1,5 @@ import { ValueType, Bindings, FromValueReturns } from './types' -import { TFTerm } from './tf-types' +import { Term } from './tf-types' /** * The superclass of all RDF Statement objects, that is @@ -62,7 +62,7 @@ export default abstract class Node { * Compares whether the two nodes are equal * @param other The other node */ - equals (other: TFTerm): boolean { + equals (other: Term): boolean { if (!other) { return false } diff --git a/src/node.ts b/src/node.ts index 8566908af..e757c0543 100644 --- a/src/node.ts +++ b/src/node.ts @@ -4,7 +4,7 @@ import { fromValue } from './collection' import Node from './node-internal' import Namespace from './namespace' import { isCollection, isTFLiteral } from './utils/terms' -import { TFTerm } from './tf-types' +import { Term } from './tf-types' /** * Creates an RDF Node from a native javascript value. @@ -24,7 +24,7 @@ const ns = { xsd: Namespace('http://www.w3.org/2001/XMLSchema#') } * Gets the javascript object equivalent to a node * @param term The RDF node */ -Node.toJS = function (term: TFTerm): TFTerm | boolean | number | Date | string | any[] { +Node.toJS = function (term: Term): Term | boolean | number | Date | string | any[] { if (isCollection(term)) { return term.elements.map(Node.toJS) // Array node (not standard RDFJS) } diff --git a/src/parse.ts b/src/parse.ts index f972494e8..5477e9035 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -9,7 +9,7 @@ import sparqlUpdateParser from './patch-parser' import * as Util from './util' import Formula from './formula' import { ContentType } from './types' -import { TFQuad } from './tf-types' +import { Quad } from './tf-types' type CallbackFunc = (error: any, kb: Formula | null) => void @@ -127,7 +127,7 @@ export default function parse ( } } - function tripleCallback (err: Error, triple: TFQuad) { + function tripleCallback (err: Error, triple: Quad) { if (triple) { kb.add(triple.subject, triple.predicate, triple.object, triple.graph) } else { diff --git a/src/statement.ts b/src/statement.ts index 5bd25a905..e3bb658bf 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -8,10 +8,10 @@ import { TermType, } from './types' import { defaultGraphNode } from './utils/default-graph-uri' -import { TFGraph, TFObject, TFPredicate, TFQuad, TFSubject, TFTerm } from './tf-types' +import { TFGraph, TFObject, TFPredicate, Quad, TFSubject, Term } from './tf-types' /** A Statement represents an RDF Triple or Quad. */ -export default class Statement implements TFQuad { +export default class Statement implements Quad { /** The subject of the triple. What the Statement is about. */ subject: SubjectType @@ -45,10 +45,10 @@ export default class Statement implements TFQuad void + private rdfArrayRemove: (arr: Quad[], q: Quad) => void /** Callbacks which are triggered after a statement has been added to the store */ - private dataCallbacks?: Array<(q: TFQuad) => void> + private dataCallbacks?: Array<(q: Quad) => void> /** * Creates a new formula @@ -216,7 +216,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass */ //@ts-ignore different from signature in Formula substitute(bindings: Bindings): IndexedFormula { - var statementsCopy = this.statements.map(function (ea: TFQuad) { + var statementsCopy = this.statements.map(function (ea: Quad) { return (ea as Statement).substitute(bindings) }) var y = new IndexedFormula() @@ -228,7 +228,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Add a callback which will be triggered after a statement has been added to the store. * @param cb */ - addDataCallback(cb: (q: TFQuad) => void): void { + addDataCallback(cb: (q: Quad) => void): void { if (!this.dataCallbacks) { this.dataCallbacks = [] } @@ -263,8 +263,8 @@ export default class IndexedFormula extends Formula { // IN future - allow pass if (binding) ds = ds.substitute(binding) // console.log('applyPatch: delete: ' + ds) ds = ds.statements as Statement[] - var bad: TFQuad[] = [] - var ds2 = ds.map(function (st: TFQuad) { // Find the actual statemnts in the store + var bad: Quad[] = [] + var ds2 = ds.map(function (st: Quad) { // Find the actual statemnts in the store var sts = targetKB.statementsMatching(st.subject, st.predicate, st.object, target) if (sts.length === 0) { // log.info("NOT FOUND deletable " + st) @@ -280,7 +280,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // console.log('despite ' + targetKB.statementsMatching(bad[0].subject, bad[0].predicate)[0]) return patchCallback('Could not find to delete: ' + bad.join('\n or ')) } - ds2.map(function (st: TFQuad) { + ds2.map(function (st: Quad) { targetKB.remove(st) }) } @@ -289,7 +289,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass ds = patch['insert'] if (binding) ds = ds.substitute(binding) ds = ds.statements - ds.map(function (st: TFQuad) { + ds.map(function (st: Quad) { st.graph = target targetKB.add(st.subject, st.predicate, st.object, st.graph) }) @@ -377,7 +377,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } /** @deprecated Use {add} instead */ - addStatement (st: TFQuad): number { + addStatement (st: Quad): number { this.add(st.subject, st.predicate, st.object, st.graph) return this.statements.length } @@ -394,18 +394,18 @@ export default class IndexedFormula extends Formula { // IN future - allow pass */ // @ts-ignore differs from signature in Formula add ( - subj: TFSubject | TFQuad | TFQuad[] | Statement | Statement[], + subj: TFSubject | Quad | Quad[] | Statement | Statement[], pred?: TFPredicate, - obj?: TFTerm, + obj?: Term, why?: TFGraph - ): TFQuad | null | IndexedFormula { + ): Quad | null | IndexedFormula { var i: number if (arguments.length === 1) { if (subj instanceof Array) { for (i = 0; i < subj.length; i++) { this.add(subj[i]) } - } else if (isTFStatement(subj)) { + } else if (isQuad(subj)) { this.add(subj.subject, subj.predicate, subj.object, subj.graph) } else if (isStore(subj)) { this.add(subj.statements) @@ -413,7 +413,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass return this } var actions: Function[] - var st: TFQuad + var st: Quad if (!why) { // system generated why = this.fetcher ? this.fetcher.appNode : this.rdfFactory.defaultGraph() @@ -492,7 +492,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Returns the symbol with canonical URI as smushed * @param term - An RDF node */ - canon(term: TFTerm): TFTerm { + canon(term: Term): Term { if (!term) { return term } @@ -526,7 +526,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param from - An index with the array ['subject', 'predicate', 'object', 'why'] */ checkStatementList( - sts: ReadonlyArray, + sts: ReadonlyArray, from?: number ): boolean | void { if (from === undefined) { @@ -534,11 +534,11 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } var names = ['subject', 'predicate', 'object', 'why'] var origin = ' found in ' + names[from] + ' index.' - var st: TFQuad + var st: Quad for (var j = 0; j < sts.length; j++) { st = sts[j] var term = [ st.subject, st.predicate, st.object, st.graph ] - var arrayContains = function (a: Array, x: TFQuad) { + var arrayContains = function (a: Array, x: Quad) { for (var i = 0; i < a.length; i++) { if (a[i].subject.equals(x.subject) && a[i].predicate.equals(x.predicate) && @@ -573,7 +573,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } // @ts-ignore incompatible with Forumala.compareTerm - compareTerm(u1: TFTerm, u2: TFTerm): number { + compareTerm(u1: Term, u2: Term): number { // Keep compatibility with downstream classOrder changes if (Object.prototype.hasOwnProperty.call(u1, "compareTerm")) { return (u1 as Node).compareTerm(u2 as Node) @@ -636,7 +636,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param u1in The first node * @param u2in The second node */ - equate(u1in: TFTerm, u2in : TFTerm): boolean { + equate(u1in: Term, u2in : Term): boolean { // log.warn("Equating "+u1+" and "+u2); // @@ // @@JAMBO Must canonicalize the uris to prevent errors from a=b=c // 03-21-2010 @@ -693,7 +693,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass predicate?: TFPredicate | null, object?: TFObject | null, graph?: TFGraph | null - ): TFQuad[] { + ): Quad[] { return this.statementsMatching( Node.fromValue(subject), Node.fromValue(predicate), @@ -717,7 +717,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Existentials are BNodes - something exists without naming * @param uri An URI */ - newExistential(uri: string): TFTerm { + newExistential(uri: string): Term { if (!uri) return this.bnode() var x = this.sym(uri) // @ts-ignore x should be blanknode, but is namedNode. @@ -828,7 +828,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Removes one or multiple statement(s) from this formula * @param st - A Statement or array of Statements to remove */ - remove(st: TFQuad | TFQuad[]): IndexedFormula { + remove(st: Quad | Quad[]): IndexedFormula { if (st instanceof Array) { for (var i = 0; i < st.length; i++) { this.remove(st[i]) @@ -851,7 +851,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param doc - The document / graph */ removeDocument(doc: TFGraph): IndexedFormula { - var sts: TFQuad[] = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index + var sts: Quad[] = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index for (var i = 0; i < sts.length; i++) { this.removeStatement(sts[i]) } @@ -879,7 +879,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // The fact is, this.statementsMatching returns this.whyIndex instead of a copy of it // but for perfromance consideration, it's better to just do that // so make a copy here. - var statements: TFQuad[] = [] + var statements: Quad[] = [] for (var i = 0; i < sts.length; i++) statements.push(sts[i]) if (limit) statements = statements.slice(0, limit) for (i = 0; i < statements.length; i++) this.remove(statements[i]) @@ -911,7 +911,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Make sure you only use this for these. * Otherwise, you should use remove() above. */ - removeStatement(st: TFQuad): IndexedFormula { + removeStatement(st: Quad): IndexedFormula { // log.debug("entering remove w/ st=" + st) var term = [ st.subject, st.predicate, st.object, st.graph ] for (var p = 0; p < 4; p++) { @@ -931,7 +931,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Removes statements * @param sts The statements to remove */ - removeStatements(sts: ReadonlyArray): IndexedFormula { + removeStatements(sts: ReadonlyArray): IndexedFormula { for (var i = 0; i < sts.length; i++) { this.remove(sts[i]) } @@ -1044,10 +1044,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass obj?: TFObject | null, why?: TFGraph | null, justOne?: boolean - ): TFQuad[] { + ): Quad[] { // log.debug("Matching {"+subj+" "+pred+" "+obj+"}") var pat = [ subj, pred, obj, why ] - var pattern: TFTerm[] = [] + var pattern: Term[] = [] var hash: Indexable[] = [] var wild: number[] = [] // wildcards var given: number[] = [] // Not wild @@ -1095,12 +1095,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass } // Ok, we have picked the shortest index but now we have to filter it var pBest = given[iBest] - var possibles: TFQuad[] = this.index[pBest][hash[pBest]] + var possibles: Quad[] = this.index[pBest][hash[pBest]] var check = given.slice(0, iBest).concat(given.slice(iBest + 1)) // remove iBest - var results: TFQuad[] = [] + var results: Quad[] = [] var parts = [ 'subject', 'predicate', 'object', 'why' ] for (var j = 0; j < possibles.length; j++) { - var st: TFQuad | null = possibles[j] + var st: Quad | null = possibles[j] for (i = 0; i < check.length; i++) { // for each position to be checked p = check[i] diff --git a/src/tf-types.ts b/src/tf-types.ts index 5f8c488f4..5e09608bf 100644 --- a/src/tf-types.ts +++ b/src/tf-types.ts @@ -11,7 +11,7 @@ import { * RDF/JS taskforce Term * @link https://rdf.js.org/data-model-spec/#term-interface */ -export interface TFTerm { +export interface Term { termType: string value: string @@ -27,7 +27,7 @@ export interface TFTerm { * RDF/JS taskforce NamedNode * @link https://rdf.js.org/data-model-spec/#namednode-interface */ -export interface TFNamedNode extends TFTerm { +export interface TFNamedNode extends Term { termType: NamedNodeTermType value: string } @@ -36,7 +36,7 @@ export interface TFNamedNode extends TFTerm { * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ -export interface TFBlankNode extends TFTerm { +export interface TFBlankNode extends Term { termType: BlankNodeTermType value: string } @@ -45,11 +45,11 @@ export interface TFBlankNode extends TFTerm { * RDF/JS taskforce Quad * @link https://rdf.js.org/data-model-spec/#quad-interface */ -export interface TFQuad< - S extends TFTerm = TFSubject, - P extends TFTerm = TFPredicate, - O extends TFTerm = TFObject, - G extends TFTerm = TFGraph +export interface Quad< + S extends Term = TFSubject, + P extends Term = TFPredicate, + O extends Term = TFObject, + G extends Term = TFGraph > { subject: S predicate: P @@ -61,7 +61,7 @@ export interface TFQuad< * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ -export interface TFLiteral extends TFTerm { +export interface TFLiteral extends Term { /** Contains the constant "Literal". */ termType: LiteralTermType /** The text value, unescaped, without language or type (example: "Brad Pitt") */ @@ -79,7 +79,7 @@ export interface TFLiteral extends TFTerm { * RDF/JS taskforce Variable * @link https://rdf.js.org/data-model-spec/#variable-interface */ -export interface TFVariable extends TFTerm { +export interface TFVariable extends Term { /** Contains the constant "Variable". */ termType: VariableTermType /** The name of the variable without leading "?" (example: "a"). */ @@ -92,7 +92,7 @@ export interface TFVariable extends TFTerm { * It's only allowed to assign a DefaultGraph to the graph property of a Quad. * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface */ -export interface TFDefaultGraph extends TFTerm { +export interface TFDefaultGraph extends Term { termType: DefaultGraphTermType; /** should return and empty string'' */ value: string; @@ -136,24 +136,24 @@ export interface TFDataFactory { * such that newObject.equals(original) returns true. * Not implemented in RDFJS, so optional. */ - fromTerm?: (original: TFTerm) => TFTerm + fromTerm?: (original: Term) => Term /** * Returns a new instance of Quad, such that newObject.equals(original) returns true. * Not implemented in RDFJS, so optional. */ - fromQuad?: (original: TFQuad) => TFQuad + fromQuad?: (original: Quad) => Quad /** * Returns a new instance of Quad. * If graph is undefined or null it MUST set graph to a DefaultGraph. */ quad: ( - subject: TFTerm, - predicate: TFTerm, - object: TFTerm, - graph?: TFTerm, - ) => TFQuad + subject: Term, + predicate: Term, + object: Term, + graph?: Term, + ) => Quad /** * Check for specific features/behaviour on the factory. diff --git a/src/types.ts b/src/types.ts index 119bd5302..7f3466137 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ import IndexedFormula from './store' import Fetcher from './fetcher' import Statement from './statement' import Empty from './empty' -import { TFNamedNode, TFTerm } from './tf-types' +import { TFNamedNode, Term } from './tf-types' /** * Types that support both Enums (for typescript) and regular strings @@ -59,7 +59,7 @@ export enum ContentType { } /** A type for values that serves as inputs */ -export type ValueType = TFTerm | Node | Date | string | number | boolean | undefined | null | Collection +export type ValueType = Term | Node | Date | string | number | boolean | undefined | null | Collection /** * In this project, there exist two types for the same kind of RDF concept. @@ -80,11 +80,11 @@ export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable export type GraphType = DefaultGraph | NamedNode | Variable // | Formula export interface Bindings { - [id: string]: TFTerm; + [id: string]: Term; } /** All the types that a .fromValue() method might return */ -export type FromValueReturns = TFTerm | undefined | null | Collection +export type FromValueReturns = Term | undefined | null | Collection export interface IRDFlibDataFactory extends DataFactory< NamedNode | BlankNode | Literal | Collection | Statement diff --git a/src/update-manager.ts b/src/update-manager.ts index 5b01e5e88..2956702c5 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -20,8 +20,8 @@ import { TFGraph, TFNamedNode, TFObject, TFPredicate, - TFQuad, - TFSubject, TFTerm + Quad, + TFSubject, Term } from './tf-types' interface UpdateManagerFormula extends IndexedFormula { @@ -205,7 +205,7 @@ export default class UpdateManager { : obj.toNT() } - anonymizeNT (stmt: TFQuad) { + anonymizeNT (stmt: Quad) { return this.anonymize(stmt.subject) + ' ' + this.anonymize(stmt.predicate) + ' ' + this.anonymize(stmt.object) + ' .' @@ -215,7 +215,7 @@ export default class UpdateManager { * Returns a list of all bnodes occurring in a statement * @private */ - statementBnodes (st: TFQuad): TFBlankNode[] { + statementBnodes (st: Quad): TFBlankNode[] { return [st.subject, st.predicate, st.object].filter(function (x) { return isTFBlankNode(x) }) as TFBlankNode[] @@ -225,7 +225,7 @@ export default class UpdateManager { * Returns a list of all bnodes occurring in a list of statements * @private */ - statementArrayBnodes (sts: TFQuad[]) { + statementArrayBnodes (sts: Quad[]) { var bnodes: TFBlankNode[] = [] for (let i = 0; i < sts.length; i++) { bnodes = bnodes.concat(this.statementBnodes(sts[i])) @@ -347,7 +347,7 @@ export default class UpdateManager { * Returns the best context for a single statement * @private */ - statementContext (st: TFQuad) { + statementContext (st: Quad) { var bnodes = this.statementBnodes(st) return this.bnodeContext(bnodes, st.graph) } @@ -414,7 +414,7 @@ export default class UpdateManager { * It returns an object which includes * function which can be used to change the object of the statement. */ - update_statement (statement: TFQuad) { + update_statement (statement: Quad) { if (statement && !statement.graph) { return } @@ -442,7 +442,7 @@ export default class UpdateManager { } } - insert_statement (st: TFQuad, callbackFunction: CallBackFunction): void { + insert_statement (st: Quad, callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -460,7 +460,7 @@ export default class UpdateManager { this.fire(st0.graph.value, query, callbackFunction) } - delete_statement (st: TFQuad | TFQuad[], callbackFunction: CallBackFunction): void { + delete_statement (st: Quad | Quad[], callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -746,7 +746,7 @@ export default class UpdateManager { var verbs = ['insert', 'delete'] var clauses = { 'delete': ds, 'insert': is } verbs.map(function (verb) { - clauses[verb].map(function (st: TFQuad) { + clauses[verb].map(function (st: Quad) { if (!doc.equals(st.graph)) { throw new Error('update: destination ' + doc + ' inconsistent with delete quad ' + st.graph) @@ -887,7 +887,7 @@ export default class UpdateManager { if (!response) { return null // throw "No record HTTP GET response for document: "+doc } - var contentType = (kb.the(response, this.ns.httph('content-type'))as TFTerm).value + var contentType = (kb.the(response, this.ns.httph('content-type'))as Term).value // prepare contents of revised document let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy! @@ -1013,7 +1013,7 @@ export default class UpdateManager { * * @returns {string} */ - serialize (uri: string, data: string | TFQuad[], contentType: string): string { + serialize (uri: string, data: string | Quad[], contentType: string): string { const kb = this.store let documentString @@ -1049,7 +1049,7 @@ export default class UpdateManager { */ put( doc: NamedNode, - data: string | TFQuad[], + data: string | Quad[], contentType: string, callback: (uri: string, ok: boolean, errorMessage?: string, response?: unknown) => void, ): Promise { diff --git a/src/utils.ts b/src/utils.ts index 389fff8fa..81dac469d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import Fetcher from './fetcher' import log from './log' import { docpart } from './uri' import { string_startswith } from './util' -import { TFDataFactory, TFQuad, TFSubject, TFTerm } from './tf-types' +import { TFDataFactory, Quad, TFSubject, Term } from './tf-types' /** RDF/JS Taskforce Typeguards */ @@ -81,9 +81,9 @@ const rdf = { export function arrayToStatements( rdfFactory: TFDataFactory, subject: TFSubject, - data: TFTerm[] -): TFQuad[] { - const statements: TFQuad[] = [] + data: Term[] +): Quad[] { + const statements: Quad[] = [] data.reduce((id, _listObj, i, listData) => { statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i])) diff --git a/src/utils/termValue.ts b/src/utils/termValue.ts index 3017f2621..43d5630cf 100644 --- a/src/utils/termValue.ts +++ b/src/utils/termValue.ts @@ -1,7 +1,7 @@ -import { TFTerm } from '../tf-types' +import { Term } from '../tf-types' /** Retrieve the value of a term, or self if already a string. */ -export function termValue (node: TFTerm | string): string { +export function termValue (node: Term | string): string { if (typeof node === 'string') { return node } diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 3b9f9194e..26e7c89fb 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -13,8 +13,8 @@ import { TFLiteral, TFNamedNode, TFObject, TFPredicate, - TFQuad, - TFSubject, TFTerm + Quad, + TFSubject, Term } from '../tf-types' export function isStatement(obj): obj is Statement { @@ -27,13 +27,13 @@ export function isStore(obj): obj is IndexedFormula { } export function isNamedNode(obj): obj is NamedNode { - return isTFTerm(obj) && obj.termType === 'NamedNode' + return isTerm(obj) && obj.termType === 'NamedNode' } /** TypeGuard for RDFLib Collections */ export function isCollection(obj: any): obj is Collection { - return isTFTerm(obj) - && (obj as TFTerm).termType === TermType.Collection + return isTerm(obj) + && (obj as Term).termType === TermType.Collection } /** TypeGuard for valid RDFlib Object types, also allows Collections */ @@ -49,25 +49,24 @@ export function isRDFObject (obj: any): obj is ObjectType { /** TypeGuard for RDFLib Variables */ export function isVariable(obj: any): obj is Variable { - return isTFTerm(obj) - && (obj as TFTerm).termType === TermType.Variable + return isTerm(obj) + && (obj as Term).termType === TermType.Variable } /** TypeGuard for RDF/JS TaskForce Terms */ -export function isTFTerm (obj: any): obj is TFTerm { +export function isTerm (obj: any): obj is Term { return typeof obj === 'object' && obj !== null && 'termType' in obj - && 'value' in obj } /** TypeGuard for RDF/JS TaskForce Literals */ export function isTFLiteral (value: any): value is TFLiteral { - return (value as TFTerm).termType === TermType.Literal + return (value as Term).termType === TermType.Literal } /** TypeGuard for RDF/JS TaskForce Quads */ -export function isTFStatement (obj: any): obj is TFQuad { +export function isQuad (obj: any): obj is Quad { return typeof obj === "object" && obj !== null && ( 'subject' in obj && 'predicate' in obj @@ -77,17 +76,17 @@ export function isTFStatement (obj: any): obj is TFQuad { /** TypeGuard for RDF/JS TaskForce NamedNodes */ export function isTFNamedNode (obj: any): obj is TFNamedNode { - return isTFTerm(obj) && 'termType' in obj && obj.termType === 'NamedNode' + return isTerm(obj) && 'termType' in obj && obj.termType === 'NamedNode' } /** TypeGuard for RDF/JS TaskForce BlankNodes */ export function isTFBlankNode (obj: any): obj is TFBlankNode { - return isTFTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' + return isTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' } /** TypeGuard for valid RDFJS Taskforce Subject types */ export function isTFSubject (obj: any): obj is TFSubject { - return isTFTerm(obj) && ( + return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || obj.termType === TermType.BlankNode @@ -96,7 +95,7 @@ export function isTFSubject (obj: any): obj is TFSubject { /** TypeGuard for valid RDFJS Taskforce Predicate types */ export function isTFPredicate (obj: any): obj is TFPredicate { - return isTFTerm(obj) && ( + return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable ) @@ -104,7 +103,7 @@ export function isTFPredicate (obj: any): obj is TFPredicate { /** TypeGuard for valid RDFJS Taskforce Object types */ export function isTFObject (obj: any): obj is TFObject { - return isTFTerm(obj) && ( + return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || obj.termType === TermType.BlankNode || @@ -114,7 +113,7 @@ export function isTFObject (obj: any): obj is TFObject { /** TypeGuard for valid RDFJS Graph types */ export function isTFGraph (obj: any): obj is TFGraph { - return isTFTerm(obj) && ( + return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || obj.termType === TermType.BlankNode || diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index 4c41a2118..d86534960 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -6,10 +6,10 @@ import Literal from '../../src/literal' import NamedNode from '../../src/named-node' import Statement from '../../src/statement' import { arrayToStatements } from '../../src/utils' -import { isNamedNode, isStatement, isTFTerm } from '../../src/utils/terms' +import { isNamedNode, isStatement, isTerm } from '../../src/utils/terms' describe('util', () => { - describe('isTFTerm', () => { + describe('isTerm', () => { it('handles undefined', () => { expect(isNamedNode(undefined)).to.be.false() }) @@ -26,7 +26,7 @@ describe('util', () => { }) it ('handles literals', () => { - expect(isTFTerm(new Literal('test'))).to.be.true() + expect(isTerm(new Literal('test'))).to.be.true() }); }) @@ -49,6 +49,25 @@ describe('util', () => { }) }) + describe('isNamedNode', () => { + it('handles prototype based objects', () => { + const proto = { + termType: 'NamedNode' + } + const obj = Object.create(proto) + obj.value = '' + expect(isNamedNode(obj)).to.be.true() + }) + + it('handles NamedNode instances', () => { + expect(isNamedNode(new NamedNode('http://example.org/'))).to.be.true() + }) + + it('handles plain objects', () => { + expect(isNamedNode({ termType: 'NamedNode', value: '' })).to.be.true() + }) + }) + describe('isStatement', () => { it ('handles Statement objects', () => { const t = new NamedNode('http://example.org') From 0f613036d6fb706958ca64efad0b7b71bc9a5a02 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 9 Dec 2019 10:07:31 +0100 Subject: [PATCH 14/29] Add tests to utils --- tests/unit/util-test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index d86534960..c101a3430 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -44,6 +44,10 @@ describe('util', () => { expect(isNamedNode(new NamedNode('http://example.org/'))).to.be.true() }) + it('handles Literal instances', () => { + expect(isNamedNode(new Literal('http://example.org/'))).to.be.false() + }) + it('handles plain objects', () => { expect(isNamedNode({ termType: 'NamedNode', value: '' })).to.be.true() }) @@ -73,6 +77,11 @@ describe('util', () => { const t = new NamedNode('http://example.org') expect(isStatement(new Statement(t, t, t))).to.be.true() }) + + it ('handles NamedNode objects', () => { + const t = new NamedNode('http://example.org') + expect(isStatement(t)).to.be.false() + }) }) describe('arrayToStatements', () => { From 963ab63262782f3ba744072b1fab66512a45b95a Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 9 Dec 2019 14:36:00 +0100 Subject: [PATCH 15/29] TypeGuard tests, renames #355 --- src/fetcher.ts | 6 +- src/literal.ts | 4 +- src/node.ts | 4 +- src/store.ts | 19 ++-- src/update-manager.ts | 4 +- src/utils/terms.ts | 37 ++++---- tests/unit/util-test.js | 195 ++++++++++++++++++++++++++++++++++++---- 7 files changed, 213 insertions(+), 56 deletions(-) diff --git a/src/fetcher.ts b/src/fetcher.ts index 53cb5853a..a48a3a752 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -34,7 +34,7 @@ import rdfParse from './parse' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import * as Uri from './uri' -import { isCollection, isTFNamedNode} from './utils/terms' +import { isCollection, isNamedNode} from './utils/terms' import * as Util from './util' import serialize from './serialize' @@ -1149,7 +1149,7 @@ export default class Fetcher implements CallbackifyInterface { userCallback = p2 } else if (typeof p2 === 'undefined') { // original calling signature // referringTerm = undefined - } else if (isTFNamedNode(p2)) { + } else if (isNamedNode(p2)) { // referringTerm = p2 options.referringTerm = p2 } else { @@ -1616,7 +1616,7 @@ export default class Fetcher implements CallbackifyInterface { this.addStatus(options.req, 'Accept: ' + options.headers['accept']) - if (isTFNamedNode(rterm)) { + if (isNamedNode(rterm)) { kb.add(kb.rdfFactory.namedNode(docuri), this.ns.link('requestedBy'), rterm, this.appNode) } diff --git a/src/literal.ts b/src/literal.ts index 9642c0fc2..44c61bd46 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -6,7 +6,7 @@ import { TermType, ValueType } from './types' -import { isTFLiteral } from './utils/terms' +import { isLiteral } from './utils/terms' import XSD from './xsd-internal' import { TFLiteral, Term } from './tf-types' @@ -168,7 +168,7 @@ export default class Literal extends Node implements TFLiteral { * @param value - The input value */ static fromValue(value: ValueType): T { - if (isTFLiteral(value)) { + if (isLiteral(value)) { return value as T } switch (typeof value) { diff --git a/src/node.ts b/src/node.ts index e757c0543..baeed84f6 100644 --- a/src/node.ts +++ b/src/node.ts @@ -3,7 +3,7 @@ import { fromValue } from './collection' import Node from './node-internal' import Namespace from './namespace' -import { isCollection, isTFLiteral } from './utils/terms' +import { isCollection, isLiteral } from './utils/terms' import { Term } from './tf-types' /** @@ -28,7 +28,7 @@ Node.toJS = function (term: Term): Term | boolean | number | Date | string | any if (isCollection(term)) { return term.elements.map(Node.toJS) // Array node (not standard RDFJS) } - if (!isTFLiteral(term)) return term + if (!isLiteral(term)) return term if (term.datatype.equals(ns.xsd('boolean'))) { return term.value === '1' || term.value === 'true' } diff --git a/src/store.ts b/src/store.ts index 8444be3a9..9d99819d2 100644 --- a/src/store.ts +++ b/src/store.ts @@ -23,12 +23,12 @@ import Formula, { FormulaOpts } from './formula' import { ArrayIndexOf } from './utils' import { RDFArrayRemove } from './util' import { - isRDFObject, + isRDFlibObject, isStore, - isTFGraph, - isTFPredicate, + isGraph, + isPredicate, isQuad, - isTFSubject + isSubject } from './utils/terms' import Node from './node' import Variable from './variable' @@ -36,9 +36,6 @@ import { Query, indexedFormulaQuery } from './query' import UpdateManager from './update-manager' import { Bindings, - - - } from './types' import Statement from './statement' import { Indexable } from './factories/factory-types' @@ -424,16 +421,16 @@ export default class IndexedFormula extends Formula { // IN future - allow pass pred = Node.fromValue(pred) obj = Node.fromValue(obj) why = Node.fromValue(why) - if (!isTFSubject(subj)) { + if (!isSubject(subj)) { throw new Error('Subject is not a subject type') } - if (!isTFPredicate(pred)) { + if (!isPredicate(pred)) { throw new Error(`Predicate ${pred} is not a predicate type`) } - if (!isRDFObject(obj)) { + if (!isRDFlibObject(obj)) { throw new Error(`Object ${obj} is not an object type`) } - if (!isTFGraph(why)) { + if (!isGraph(why)) { throw new Error("Why is not a graph type") } //@ts-ignore This is not used internally diff --git a/src/update-manager.ts b/src/update-manager.ts index 2956702c5..ed69cb373 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -10,7 +10,7 @@ import Fetcher from './fetcher' import Namespace from './namespace' import Serializer from './serializer' import { join as uriJoin } from './uri' -import { isStore, isTFBlankNode } from './utils/terms' +import { isStore, isBlankNode } from './utils/terms' import * as Util from './util' import Statement from './statement' import NamedNode from './named-node' @@ -217,7 +217,7 @@ export default class UpdateManager { */ statementBnodes (st: Quad): TFBlankNode[] { return [st.subject, st.predicate, st.object].filter(function (x) { - return isTFBlankNode(x) + return isBlankNode(x) }) as TFBlankNode[] } diff --git a/src/utils/terms.ts b/src/utils/terms.ts index 26e7c89fb..b3f81a20b 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -5,8 +5,6 @@ import { import Collection from '../collection' import IndexedFormula from '../store' import Statement from '../statement' -import NamedNode from '../named-node' -import Variable from '../variable' import { TFBlankNode, TFGraph, @@ -14,22 +12,21 @@ import { TFObject, TFPredicate, Quad, - TFSubject, Term + TFSubject, + Term, + TFVariable, } from '../tf-types' +/** TypeGuard for RDFLib Statements */ export function isStatement(obj): obj is Statement { return typeof obj === 'object' && obj !== null && 'subject' in obj } -/** TypeGuard for RDF/JS TaskForce Stores */ +/** TypeGuard for RDFlib Stores */ export function isStore(obj): obj is IndexedFormula { return typeof obj === 'object' && obj !== null && 'statements' in obj } -export function isNamedNode(obj): obj is NamedNode { - return isTerm(obj) && obj.termType === 'NamedNode' -} - /** TypeGuard for RDFLib Collections */ export function isCollection(obj: any): obj is Collection { return isTerm(obj) @@ -37,7 +34,7 @@ export function isCollection(obj: any): obj is Collection { } /** TypeGuard for valid RDFlib Object types, also allows Collections */ -export function isRDFObject (obj: any): obj is ObjectType { +export function isRDFlibObject(obj: any): obj is ObjectType { return obj && Object.prototype.hasOwnProperty.call(obj, 'termType') && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || @@ -48,25 +45,25 @@ export function isRDFObject (obj: any): obj is ObjectType { } /** TypeGuard for RDFLib Variables */ -export function isVariable(obj: any): obj is Variable { +export function isVariable(obj: any): obj is TFVariable { return isTerm(obj) && (obj as Term).termType === TermType.Variable } /** TypeGuard for RDF/JS TaskForce Terms */ -export function isTerm (obj: any): obj is Term { +export function isTerm(obj: any): obj is Term { return typeof obj === 'object' && obj !== null && 'termType' in obj } /** TypeGuard for RDF/JS TaskForce Literals */ -export function isTFLiteral (value: any): value is TFLiteral { +export function isLiteral(value: any): value is TFLiteral { return (value as Term).termType === TermType.Literal } /** TypeGuard for RDF/JS TaskForce Quads */ -export function isQuad (obj: any): obj is Quad { +export function isQuad(obj: any): obj is Quad { return typeof obj === "object" && obj !== null && ( 'subject' in obj && 'predicate' in obj @@ -75,17 +72,17 @@ export function isQuad (obj: any): obj is Quad { } /** TypeGuard for RDF/JS TaskForce NamedNodes */ -export function isTFNamedNode (obj: any): obj is TFNamedNode { - return isTerm(obj) && 'termType' in obj && obj.termType === 'NamedNode' +export function isNamedNode(obj: any): obj is TFNamedNode { + return isTerm(obj) && obj.termType === 'NamedNode' } /** TypeGuard for RDF/JS TaskForce BlankNodes */ -export function isTFBlankNode (obj: any): obj is TFBlankNode { +export function isBlankNode(obj: any): obj is TFBlankNode { return isTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' } /** TypeGuard for valid RDFJS Taskforce Subject types */ -export function isTFSubject (obj: any): obj is TFSubject { +export function isSubject(obj: any): obj is TFSubject { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || @@ -94,7 +91,7 @@ export function isTFSubject (obj: any): obj is TFSubject { } /** TypeGuard for valid RDFJS Taskforce Predicate types */ -export function isTFPredicate (obj: any): obj is TFPredicate { +export function isPredicate(obj: any): obj is TFPredicate { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable @@ -102,7 +99,7 @@ export function isTFPredicate (obj: any): obj is TFPredicate { } /** TypeGuard for valid RDFJS Taskforce Object types */ -export function isTFObject (obj: any): obj is TFObject { +export function isRDFObject(obj: any): obj is TFObject { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || @@ -112,7 +109,7 @@ export function isTFObject (obj: any): obj is TFObject { } /** TypeGuard for valid RDFJS Graph types */ -export function isTFGraph (obj: any): obj is TFGraph { +export function isGraph(obj: any): obj is TFGraph { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index c101a3430..12b0b463b 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -6,28 +6,52 @@ import Literal from '../../src/literal' import NamedNode from '../../src/named-node' import Statement from '../../src/statement' import { arrayToStatements } from '../../src/utils' -import { isNamedNode, isStatement, isTerm } from '../../src/utils/terms' +import { + isBlankNode, + isCollection, + isNamedNode, + isQuad, + isRDFlibObject, + isRDFObject, + isStatement, + isStore, + isTerm, + isVariable, + isLiteral, +} from '../../src/utils/terms' +import IndexedFormula from '../../src/store' +import BlankNode from '../../src/blank-node' +import Collection from '../../src/collection' +import Variable from '../../src/variable' describe('util', () => { describe('isTerm', () => { it('handles undefined', () => { - expect(isNamedNode(undefined)).to.be.false() + expect(isTerm(undefined)).to.be.false() }) it('handles null', () => { - expect(isNamedNode(null)).to.be.false() + expect(isTerm(null)).to.be.false() }) it('handles other objects', () => { - expect(isNamedNode(1)).to.be.false() - expect(isNamedNode(true)).to.be.false() - expect(isNamedNode(NaN)).to.be.false() - expect(isNamedNode({})).to.be.false() + expect(isTerm(1)).to.be.false() + expect(isTerm(true)).to.be.false() + expect(isTerm(NaN)).to.be.false() + expect(isTerm({})).to.be.false() }) - it ('handles literals', () => { + it('handles literals', () => { expect(isTerm(new Literal('test'))).to.be.true() }); + + it('handles namedNodes', () => { + expect(isTerm(new NamedNode('https://example.com/test'))).to.be.true() + }); + + it('handles blankNodes', () => { + expect(isTerm(new BlankNode('test'))).to.be.true() + }); }) describe('isNamedNode', () => { @@ -51,9 +75,7 @@ describe('util', () => { it('handles plain objects', () => { expect(isNamedNode({ termType: 'NamedNode', value: '' })).to.be.true() }) - }) - describe('isNamedNode', () => { it('handles prototype based objects', () => { const proto = { termType: 'NamedNode' @@ -63,27 +85,168 @@ describe('util', () => { expect(isNamedNode(obj)).to.be.true() }) - it('handles NamedNode instances', () => { - expect(isNamedNode(new NamedNode('http://example.org/'))).to.be.true() - }) - it('handles plain objects', () => { expect(isNamedNode({ termType: 'NamedNode', value: '' })).to.be.true() }) }) describe('isStatement', () => { - it ('handles Statement objects', () => { + it('handles Statement objects', () => { const t = new NamedNode('http://example.org') expect(isStatement(new Statement(t, t, t))).to.be.true() }) - it ('handles NamedNode objects', () => { + it('handles other objects', () => { const t = new NamedNode('http://example.org') expect(isStatement(t)).to.be.false() }) }) + describe('isStore', () => { + it('handles IndexedFormula objects', () => { + const t = new IndexedFormula() + expect(isStore(t)).to.be.true() + }) + + it('handles other objects', () => { + expect(isStore(NaN)).to.be.false() + }) + }) + + describe('isCollection', () => { + it('handles Collection objects', () => { + const t = new Collection() + expect(isCollection(t)).to.be.true() + }) + + it('handles other objects', () => { + const t = new NamedNode('http://example.org') + expect(isCollection(t)).to.be.false() + }) + }) + + describe('isRDFlibObject', () => { + it('handles Collection objects', () => { + const t = new Collection() + expect(isRDFlibObject(t)).to.be.true() + }) + + it('handles NamedNode objects', () => { + const t = new NamedNode('http://example.org') + expect(isRDFlibObject(t)).to.be.true() + }) + + it('handles Variable objects', () => { + const t = new Variable() + expect(isRDFlibObject(t)).to.be.true() + }) + + it('handles BlankNode objects', () => { + const t = new BlankNode() + expect(isRDFlibObject(t)).to.be.true() + }) + + it('handles Literal objects', () => { + const t = new Literal() + expect(isRDFlibObject(t)).to.be.true() + }) + + it('handles other objects', () => { + const t = { + some: "object" + } + expect(isRDFlibObject(t)).to.be.false() + }) + }) + + describe('isVariable', () => { + it('handles Variable objects', () => { + const t = new Variable() + expect(isVariable(t)).to.be.true() + }) + + it('handles other objects', () => { + const t = new NamedNode('http://example.org') + expect(isVariable(t)).to.be.false() + }) + }) + + describe('isLiteral', () => { + it('handles Literal objects', () => { + const t = new Literal() + expect(isLiteral(t)).to.be.true() + }) + + it('handles other objects', () => { + const nn = new NamedNode('http://example.org') + expect(isLiteral(nn)).to.be.false() + const v = new Variable('http://example.org') + expect(isLiteral(v)).to.be.false() + const bn = new BlankNode('http://example.org') + expect(isLiteral(bn)).to.be.false() + }) + }) + + describe('isQuad', () => { + it('handles Statement objects', () => { + const t = new NamedNode('http://example.org') + expect(isQuad(new Statement(t, t, t))).to.be.true() + }) + + it('handles other objects', () => { + const nn = new NamedNode('http://example.org') + expect(isQuad(nn)).to.be.false() + const v = new Variable('http://example.org') + expect(isQuad(v)).to.be.false() + const bn = new BlankNode('http://example.org') + expect(isQuad(bn)).to.be.false() + }) + }) + + describe('isBlankNode', () => { + it('handles BlankNode objects', () => { + const t = new BlankNode() + expect(isBlankNode(t)).to.be.true() + }) + + it('handles other objects', () => { + const nn = new NamedNode('http://example.org') + expect(isBlankNode(nn)).to.be.false() + const v = new Variable('http://example.org') + expect(isBlankNode(v)).to.be.false() + const bn = new Literal('http://example.org') + expect(isBlankNode(bn)).to.be.false() + }) + }) + + describe('isObject', () => { + it('handles BlankNode objects', () => { + const t = new BlankNode() + expect(isRDFObject(t)).to.be.true() + }) + + it('handles Variable objects', () => { + const t = new Variable() + expect(isRDFObject(t)).to.be.true() + }) + + it('handles Literal objects', () => { + const t = new Literal() + expect(isRDFObject(t)).to.be.true() + }) + + it('handles NamedNode objects', () => { + const t = new NamedNode("https://someurl.com") + expect(isRDFObject(t)).to.be.true() + }) + + it('handles other objects', () => { + expect(isRDFObject(2)).to.be.false() + expect(isRDFObject({})).to.be.false() + expect(isRDFObject(undefined)).to.be.false() + }) + }) + describe('arrayToStatements', () => { it('converts an array to statements', () => { const first = CanonicalDataFactory.blankNode() From 3ba17c69200c6734334ca9a1bb1d21d7c56fa416 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 9 Dec 2019 15:45:01 +0100 Subject: [PATCH 16/29] Rename TFTypes, remove TF namespace --- src/blank-node.ts | 2 +- src/collection.ts | 10 +- src/default-graph.ts | 2 +- src/empty.ts | 2 +- src/factories/canonical-data-factory.ts | 2 +- src/factories/factory-types.ts | 8 +- src/factories/rdflib-data-factory.ts | 4 +- src/fetcher.ts | 144 ++++++++++++------------ src/formula.ts | 110 +++++++++--------- src/literal.ts | 10 +- src/named-node.ts | 2 +- src/namespace.ts | 10 +- src/serialize.ts | 6 +- src/statement.ts | 10 +- src/store.ts | 96 ++++++++-------- src/tf-types.ts | 38 +++---- src/types.ts | 24 ++-- src/update-manager.ts | 58 +++++----- src/uri.ts | 6 +- src/utils.ts | 6 +- src/utils/terms.ts | 31 ++--- src/variable.ts | 2 +- 22 files changed, 294 insertions(+), 289 deletions(-) diff --git a/src/blank-node.ts b/src/blank-node.ts index 7e17d162d..3d5c369ea 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -2,7 +2,7 @@ import ClassOrder from './class-order' import Node from './node-internal' import IndexedFormula from './store' import { BlankNodeTermType, TermType} from './types' -import { TFBlankNode } from './tf-types' +import { BlankNode as TFBlankNode } from './tf-types' /** * An RDF blank node is a Node without a URI diff --git a/src/collection.ts b/src/collection.ts index 91736e254..3181063e6 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -1,4 +1,4 @@ -import BlankNode from './blank-node' +import RdflibBlankNode from './blank-node' import ClassOrder from './class-order' import Literal from './literal' import Node from './node-internal' @@ -43,13 +43,13 @@ export function fromValue = any, C extends Node = * Use generic T to control the contents of the array. */ export default class Collection< - T extends Node = Node | BlankNode | Collection | Literal | Variable + T extends Node = Node | RdflibBlankNode | Collection | Literal | Variable > extends Node implements Term { static termType = TermType.Collection classOrder = ClassOrder.Collection closed: boolean = false - compareTerm = BlankNode.prototype.compareTerm + compareTerm = RdflibBlankNode.prototype.compareTerm /** * The nodes in this collection */ @@ -58,7 +58,7 @@ export default class Collection< termType: CollectionTermType = TermType.Collection constructor (initial?: ReadonlyArray) { - super((BlankNode.nextId++).toString()) + super((RdflibBlankNode.nextId++).toString()) if (initial && initial.length > 0) { initial.forEach(element => { @@ -113,7 +113,7 @@ export default class Collection< } static toNT (collection) { - return BlankNode.NTAnonymousNodePrefix + collection.id + return RdflibBlankNode.NTAnonymousNodePrefix + collection.id } /** diff --git a/src/default-graph.ts b/src/default-graph.ts index 941ccc5ff..65b4fe439 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -1,7 +1,7 @@ 'use strict' import Node from './node-internal' import { TermType, DefaultGraphTermType } from './types' -import { TFDefaultGraph } from './tf-types' +import { DefaultGraph as TFDefaultGraph } from './tf-types' /** The RDF default graph */ export default class DefaultGraph extends Node implements TFDefaultGraph { diff --git a/src/empty.ts b/src/empty.ts index 0ae4db5ec..fdd23664b 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,5 +1,5 @@ import Node from './node-internal' -import { TermType} from './types' +import { TermType } from './types' import { Term } from './tf-types' /** diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index 665abeeff..d7e5af9e5 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -18,7 +18,7 @@ import { Feature, } from './factory-types' import { isQuad, isTerm } from '../utils/terms' -import { TFNamedNode, Quad, Term } from '../tf-types' +import { NamedNode as TFNamedNode, Quad, Term } from '../tf-types' export { defaultGraphURI } from '../utils/default-graph-uri' diff --git a/src/factories/factory-types.ts b/src/factories/factory-types.ts index b24369352..1d5cc43ff 100644 --- a/src/factories/factory-types.ts +++ b/src/factories/factory-types.ts @@ -4,13 +4,13 @@ import NamedNode from '../named-node' import BlankNode from '../blank-node' import Variable from '../variable' import { - TFBlankNode, + BlankNode as TFBlankNode, TFDataFactory, - TFLiteral, - TFNamedNode, + Literal as TFLiteral, + NamedNode as TFNamedNode, Quad, Term, - TFVariable, + Variable as TFVariable, } from '../tf-types' export type Comparable = Term | TFNamedNode | TFBlankNode | TFLiteral | Quad | undefined | null diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts index 71b11ebf1..f64ca8252 100644 --- a/src/factories/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -9,7 +9,7 @@ import Statement from '../statement' import IndexedFormula from '../store' import Fetcher from '../fetcher' import ExtendedTermFactory from './extended-term-factory' -import { TFNamedNode, Term } from '../tf-types' +import { NamedNode, Term } from '../tf-types' /** Full RDFLib.js Data Factory */ const RDFlibDataFactory: IRDFlibDataFactory = { @@ -38,7 +38,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param dt The datatype * @deprecated use {literal} with the second and third argument combined */ - lit (val: string, lang?: string, dt?: TFNamedNode): Literal { + lit (val: string, lang?: string, dt?: NamedNode): Literal { return this.literal('' + val, lang || dt) }, diff --git a/src/fetcher.ts b/src/fetcher.ts index a48a3a752..da6477ab2 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -28,7 +28,7 @@ import IndexedFormula from './store' import log from './log' import N3Parser from './n3parser' -import NamedNode from './named-node' +import RDFlibNamedNode from './named-node' import Namespace from './namespace' import rdfParse from './parse' import { parseRDFaDOM } from './rdfaparser' @@ -47,12 +47,12 @@ import { } from './types' import { termValue } from './utils/termValue' import { - TFBlankNode, + BlankNode, TFDataFactory, - TFGraph, - TFNamedNode, - TFPredicate, - TFSubject + Quad_Graph, + NamedNode, + Quad_Predicate, + Quad_Subject } from './tf-types' // This is a special fetch which does OIDC auth, catching 401 errors @@ -108,7 +108,7 @@ interface ExtendedResponse extends Response { /** String representation of the Body */ responseText?: string /** Identifier of the reqest */ - req?: TFSubject + req?: Quad_Subject size?: number timeout?: number /** Used in UpdateManager.updateDav */ @@ -146,7 +146,7 @@ interface AutoInitOptions extends RequestInit{ * referred to this (for tracking bad links). * The document in which this link was found. */ - referringTerm?: TFNamedNode + referringTerm?: NamedNode /** Provided content type (for writes) */ contentType?: string /** @@ -183,14 +183,14 @@ interface AutoInitOptions extends RequestInit{ retriedWithNoCredentials?: boolean requestedURI?: string // Seems to be required in some functions, such as XHTML parse and RedirectToProxy - resource: TFSubject + resource: Quad_Subject /** The serialized resource in the body*/ // Used for storing metadata of requests - original: TFNamedNode + original: NamedNode // Like requeststatus? Can contain text with error. data?: string // Probably an identifier for request?s - req: TFBlankNode + req: BlankNode // Might be the same as Options.data body?: string headers: Headers @@ -228,8 +228,8 @@ class RDFXMLHandler extends Handler { responseText: String, /** Requires .original */ options: { - original: TFSubject - req: TFSubject + original: Quad_Subject + req: Quad_Subject } & Options, ) { let kb = fetcher.store @@ -271,8 +271,8 @@ class XHTMLHandler extends Handler { fetcher: Fetcher, responseText: string, options: { - resource: TFSubject - original: TFSubject + resource: Quad_Subject + original: Quad_Subject } & Options, ): Promise | ExtendedResponse { let relation, reverse: boolean @@ -349,9 +349,9 @@ class XMLHandler extends Handler { fetcher: Fetcher, responseText: string, options: { - original: TFSubject - req: TFBlankNode - resource: TFSubject + original: Quad_Subject + req: BlankNode + resource: Quad_Subject } & Options, ): ExtendedResponse | Promise { let dom = Util.parseXML(responseText) @@ -432,9 +432,9 @@ class HTMLHandler extends Handler { fetcher: Fetcher, responseText: string, options: { - req: TFBlankNode, - resource: TFSubject, - original: TFSubject, + req: BlankNode, + resource: Quad_Subject, + original: Quad_Subject, } & Options ): Promise | ExtendedResponse { let kb = fetcher.store @@ -497,9 +497,9 @@ class TextHandler extends Handler { fetcher: Fetcher, responseText: string, options: { - req: TFSubject - original: TFSubject - resource: TFSubject + req: Quad_Subject + original: Quad_Subject + resource: Quad_Subject } & Options ): ExtendedResponse | Promise { // We only speak dialects of XML right now. Is this XML? @@ -554,8 +554,8 @@ class N3Handler extends Handler { fetcher: Fetcher, responseText: string, options: { - original: TFNamedNode - req: TFSubject + original: NamedNode + req: Quad_Subject } & Options, response: ExtendedResponse ): ExtendedResponse | Promise { @@ -681,7 +681,7 @@ export default class Fetcher implements CallbackifyInterface { _fetch: Fetch mediatypes: MediatypesMap /** Denoting this session */ - appNode: TFBlankNode + appNode: BlankNode /** * this.requested[uri] states: * undefined no record of web access or records reset @@ -707,7 +707,7 @@ export default class Fetcher implements CallbackifyInterface { nonexistent: BooleanMap lookedUp: BooleanMap handlers: Array - ns: { [k: string]: (ln: string) => TFPredicate } + ns: { [k: string]: (ln: string) => Quad_Predicate } static HANDLERS: { [handlerName: number]: Handler } @@ -880,13 +880,13 @@ export default class Fetcher implements CallbackifyInterface { * By default, the HTTP headers are recorded also, in the same store, in a separate graph. * This allows code like editable() for example to test things about the resource. * - * @param uri {Array|Array|NamedNode|string} + * @param uri {Array|Array|RDFlibNamedNode|string} * * @param [options={}] {Object} * * @param [options.fetch] {Function} * - * @param [options.referringTerm] {NamedNode} Referring term, the resource which + * @param [options.referringTerm] {RDFlibNamedNode} Referring term, the resource which * referred to this (for tracking bad links) * * @param [options.contentType] {string} Provided content type (for writes) @@ -916,7 +916,7 @@ export default class Fetcher implements CallbackifyInterface { * @returns {Promise} */ load ( - uri: TFNamedNode | string | Array, + uri: NamedNode | string | Array, options: Options = {} ): Promise | Promise[] { options = Object.assign({}, options) // Take a copy as we add stuff to the options!! @@ -927,7 +927,7 @@ export default class Fetcher implements CallbackifyInterface { ) } - let docuri = termValue(uri as NamedNode) + let docuri = termValue(uri as RDFlibNamedNode) docuri = docuri.split('#')[0] options = this.initFetchOptions(docuri, options) @@ -1137,7 +1137,7 @@ export default class Fetcher implements CallbackifyInterface { * includes response.status as the HTTP status if any. */ nowOrWhenFetched ( - uriIn: string | TFNamedNode, + uriIn: string | NamedNode, p2?: UserCallback | Options, userCallback?: UserCallback, options: Options = {} @@ -1194,7 +1194,7 @@ export default class Fetcher implements CallbackifyInterface { * request's metadata status collection. * */ - addStatus (req: TFBlankNode, statusMessage: string) { + addStatus (req: BlankNode, statusMessage: string) { // let now = new Date() statusMessage = '[' + now.getHours() + ':' + now.getMinutes() + ':' + @@ -1220,8 +1220,8 @@ export default class Fetcher implements CallbackifyInterface { */ failFetch ( options: { - req: TFBlankNode - original: TFSubject + req: BlankNode + original: Quad_Subject } & Options, errorMessage: string, statusCode: StatusValues, @@ -1263,10 +1263,10 @@ export default class Fetcher implements CallbackifyInterface { // in the why part of the quad distinguish between HTML and HTTP header // Reverse is set iif the link was rev= as opposed to rel= linkData ( - originalUri: TFNamedNode, + originalUri: NamedNode, rel: string, uri: string, - why: TFGraph, + why: Quad_Graph, reverse?: boolean ) { if (!uri) return @@ -1299,8 +1299,8 @@ export default class Fetcher implements CallbackifyInterface { parseLinkHeader ( linkHeader: string, - originalUri: TFNamedNode, - reqNode: TFGraph + originalUri: NamedNode, + reqNode: Quad_Graph ): void { if (!linkHeader) { return } @@ -1336,8 +1336,8 @@ export default class Fetcher implements CallbackifyInterface { doneFetch ( options: { - req: TFSubject, - original: TFSubject + req: Quad_Subject, + original: Quad_Subject } & Options, response: ExtendedResponse ): Response { @@ -1356,7 +1356,7 @@ export default class Fetcher implements CallbackifyInterface { * If only one was flagged as looked up, then the new node is looked up again, * which will make sure all the URIs are dereferenced */ - nowKnownAs (was: TFSubject, now: TFSubject): void { + nowKnownAs (was: Quad_Subject, now: Quad_Subject): void { if (this.lookedUp[was.value]) { // Transfer userCallback if (!this.lookedUp[now.value]) { @@ -1373,11 +1373,11 @@ export default class Fetcher implements CallbackifyInterface { * Writes back to the web what we have in the store for this uri */ putBack ( - uri: TFNamedNode | string, + uri: NamedNode | string, options: Options = {} ): Promise { - uri = (uri as TFNamedNode).value || uri // Accept object or string - let doc = new NamedNode(uri).doc() // strip off # + uri = (uri as NamedNode).value || uri // Accept object or string + let doc = new RDFlibNamedNode(uri).doc() // strip off # options.contentType = options.contentType || 'text/turtle' options.data = serialize(doc, this.store, doc.value, options.contentType) as string return this.webOperation('PUT', uri, options) @@ -1409,13 +1409,13 @@ export default class Fetcher implements CallbackifyInterface { * @param doc - The resource */ async createIfNotExists ( - doc: NamedNode, + doc: RDFlibNamedNode, contentType = 'text/turtle', data = '' ): Promise { const fetcher = this try { - var response = await fetcher.load(doc as TFNamedNode) + var response = await fetcher.load(doc as NamedNode) } catch (err) { if (err.response.status === 404) { console.log('createIfNotExists: doc does NOT exist, will create... ' + doc) @@ -1467,7 +1467,7 @@ export default class Fetcher implements CallbackifyInterface { return this.webOperation('POST', parentURI, options) } - invalidateCache (iri: string | TFNamedNode): void { + invalidateCache (iri: string | NamedNode): void { const uri = termValue(iri) const fetcher = this if (fetcher.fetchQueue && fetcher.fetchQueue[uri]) { @@ -1499,7 +1499,7 @@ export default class Fetcher implements CallbackifyInterface { */ webOperation ( method: HTTPMethods, - uriIn: string | TFNamedNode, + uriIn: string | NamedNode, // Not sure about this type. Maybe this Options is different? options: Options = {} ): Promise { @@ -1558,8 +1558,8 @@ export default class Fetcher implements CallbackifyInterface { * (for tracking bad links) */ lookUpThing ( - term: TFSubject, - rterm: TFSubject + term: Quad_Subject, + rterm: Quad_Subject ): Promise | Promise[] { let uris = this.store.uris(term) // Get all URIs uris = uris.map(u => Uri.docpart(u)) // Drop hash fragments @@ -1581,16 +1581,16 @@ export default class Fetcher implements CallbackifyInterface { * Looks for { [] link:requestedURI ?uri; link:response [ httph:header-name ?value ] } */ getHeader ( - doc: TFNamedNode, + doc: NamedNode, header: string ): undefined | string[] { const kb = this.store - const requests = kb.each(undefined, this.ns.link('requestedURI'), doc) as TFSubject[] + const requests = kb.each(undefined, this.ns.link('requestedURI'), doc) as Quad_Subject[] for (let r = 0; r < requests.length; r++) { let request = requests[r] if (request !== undefined) { - let response = kb.any(request, this.ns.link('response')) as TFSubject + let response = kb.any(request, this.ns.link('response')) as Quad_Subject if (response !== undefined && kb.anyValue(response, this.ns.http('status')) && (kb.anyValue(response, this.ns.http('status')) as string).startsWith('2')) { // Only look at success returns - not 401 error messagess etc let results = kb.each(response, this.ns.httph(header.toLowerCase())) @@ -1638,10 +1638,10 @@ export default class Fetcher implements CallbackifyInterface { saveResponseMetadata ( response: Response, options: { - req: TFBlankNode, - resource: TFSubject + req: BlankNode, + resource: Quad_Subject } & Options - ): TFBlankNode { + ): BlankNode { const kb = this.store let responseNode = kb.bnode() @@ -1673,7 +1673,7 @@ export default class Fetcher implements CallbackifyInterface { return responseNode } - objectRefresh (term: TFNamedNode): void { + objectRefresh (term: NamedNode): void { let uris = this.store.uris(term) // Get all URIs if (typeof uris !== 'undefined') { for (let i = 0; i < uris.length; i++) { @@ -1689,7 +1689,7 @@ export default class Fetcher implements CallbackifyInterface { ** @param userCallback - A function userCallback(ok, message, response) */ refresh ( - term: TFNamedNode, + term: NamedNode, userCallback?: UserCallback ): void { // sources_refresh this.fireCallbacks('refresh', arguments) @@ -1704,7 +1704,7 @@ export default class Fetcher implements CallbackifyInterface { ** @param userCallback - A function userCallback(ok, message, response) */ refreshIfExpired ( - term: TFNamedNode, + term: NamedNode, userCallback: UserCallback ): void { let exp = this.getHeader(term, 'Expires') @@ -1715,7 +1715,7 @@ export default class Fetcher implements CallbackifyInterface { } } - retract (term: TFGraph) { // sources_retract + retract (term: Quad_Graph) { // sources_retract this.store.removeMany(undefined, undefined, undefined, term) if (term.value) { delete this.requested[Uri.docpart(term.value)] @@ -1744,7 +1744,7 @@ export default class Fetcher implements CallbackifyInterface { return this.requested[docuri] === true } - unload (term: TFNamedNode) { + unload (term: NamedNode) { this.store.removeDocument(term) delete this.requested[term.value] // So it can be load2ed again } @@ -1829,8 +1829,8 @@ export default class Fetcher implements CallbackifyInterface { // deduce some things from the HTTP transaction addType ( - rdfType: TFNamedNode, - req: TFSubject, + rdfType: NamedNode, + req: Quad_Subject, kb: IndexedFormula, locURI: string ): void { // add type to all redirected resources too @@ -1846,11 +1846,11 @@ export default class Fetcher implements CallbackifyInterface { if (doc && doc.value) { kb.add(kb.rdfFactory.namedNode(doc.value), this.ns.rdf('type'), rdfType, this.appNode) } // convert Literal - prev = kb.any(undefined, kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#redirectedRequest'), prev) as TFSubject + prev = kb.any(undefined, kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#redirectedRequest'), prev) as Quad_Subject if (!prev) { break } var response = kb.any(prev, kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#response')) if (!response) { break } - var redirection = kb.any((response as TFNamedNode), kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/http#status')) + var redirection = kb.any((response as NamedNode), kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/http#status')) if (!redirection) { break } // @ts-ignore always true? if ((redirection !== '301') && (redirection !== '302')) { break } @@ -1909,9 +1909,9 @@ export default class Fetcher implements CallbackifyInterface { } } if (response.status === 200) { - this.addType(this.ns.link('Document') as TFNamedNode, reqNode, kb, docuri) + this.addType(this.ns.link('Document') as NamedNode, reqNode, kb, docuri) if (diffLocation) { - this.addType(this.ns.link('Document') as TFNamedNode, reqNode, kb, + this.addType(this.ns.link('Document') as NamedNode, reqNode, kb, diffLocation) } @@ -1967,7 +1967,7 @@ export default class Fetcher implements CallbackifyInterface { saveErrorResponse ( response: ExtendedResponse, - responseNode: TFSubject + responseNode: Quad_Subject ): Promise { let kb = this.store @@ -2060,8 +2060,8 @@ export default class Fetcher implements CallbackifyInterface { setRequestTimeout ( uri: string, options: { - req: TFSubject - original: TFSubject + req: Quad_Subject + original: Quad_Subject } & Options ): Promise { return new Promise((resolve) => { diff --git a/src/formula.ts b/src/formula.ts index c265a00d5..a08ea9dd7 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -2,7 +2,7 @@ import ClassOrder from './class-order' import Collection from './collection' import CanonicalDataFactory from './factories/canonical-data-factory' import log from './log' -import NamedNode from './named-node' +import RDFlibNamedNode from './named-node' import Namespace from './namespace' import Node from './node-internal' import Serializer from './serialize' @@ -19,13 +19,13 @@ import { } from './factories/factory-types' import { appliedFactoryMethods, arrayToStatements } from './utils' import { - TFBlankNode, + BlankNode, TFDataFactory, - TFGraph, - TFObject, - TFPredicate, + Quad_Graph, + Quad_Object, + Quad_Predicate, Quad, - TFSubject, + Quad_Subject, Term, } from './tf-types' @@ -123,10 +123,10 @@ export default class Formula extends Node { * @param graph - the last part of the statement */ add ( - subject: TFSubject, - predicate: TFPredicate, - object: TFObject, - graph?: TFGraph + subject: Quad_Subject, + predicate: Quad_Predicate, + object: Quad_Object, + graph?: Quad_Graph ): number { return this.statements .push(this.rdfFactory.quad(subject, predicate, object, graph)) @@ -140,7 +140,7 @@ export default class Formula extends Node { } /** @deprecated use {this.rdfFactory.blankNode} instead */ - bnode (id?: string): TFBlankNode { + bnode (id?: string): BlankNode { return this.rdfFactory.blankNode(id) } @@ -167,10 +167,10 @@ export default class Formula extends Node { * @returns A node which match the wildcard position, or null */ any( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): Term | null | undefined { const st = this.anyStatementMatching(s, p, o, g) if (st == null) { @@ -194,10 +194,10 @@ export default class Formula extends Node { * @param g The graph that contains the statement */ anyValue( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): string | void { const y = this.any(s, p, o, g) return y ? y.value : void 0 @@ -211,10 +211,10 @@ export default class Formula extends Node { * @param g The graph that contains the statement */ anyJS( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): any { const y = this.any(s, p, o, g) return y ? Node.toJS(y) : void 0 @@ -224,10 +224,10 @@ export default class Formula extends Node { * Gets the first statement that matches the specified pattern */ anyStatementMatching( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): Quad | undefined { let x = this.statementsMatching(s, p, o, g, true) if (!x || x.length === 0) { @@ -258,10 +258,10 @@ export default class Formula extends Node { * @returns {Array} - An array of nodes which match the wildcard position */ statementsMatching( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null, + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null, justOne?: boolean ): Quad[] { const sts = this.statements.filter(st => @@ -338,10 +338,10 @@ export default class Formula extends Node { * @returns {Array} - An array of nodes which match the wildcard position */ each( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): Term[] { const results: Term[] = [] let sts = this.statementsMatching(s, p, o, g, false) @@ -391,7 +391,7 @@ export default class Formula extends Node { let len4: number let m: number let members: MembersMap - let pred: TFPredicate + let pred: Quad_Predicate let ref let ref1: Quad[] let ref2: Term[] @@ -418,7 +418,7 @@ export default class Formula extends Node { this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#domain'), this.fromNT(t)) for (let l = 0, len1 = ref2.length; l < len1; l++) { - pred = ref2[l] as TFPredicate + pred = ref2[l] as Quad_Predicate ref3 = this.statementsMatching(void 0, pred) for (m = 0, len2 = ref3.length; m < len2; m++) { st = ref3[m] @@ -429,7 +429,7 @@ export default class Formula extends Node { this.rdfFactory.namedNode('http://www.w3.org/2000/01/rdf-schema#range'), this.fromNT(t)) for (let q = 0, len3 = ref4.length; q < len3; q++) { - pred = ref4[q] as TFPredicate + pred = ref4[q] as Quad_Predicate ref5 = this.statementsMatching(void 0, pred) for (u = 0, len4 = ref5.length; u < len4; u++) { st = ref5[u] @@ -473,7 +473,7 @@ export default class Formula extends Node { /** * Get all the Classes of which we can RDFS-infer the subject is a subclass - * @param {NamedNode} subject - The thing whose classes are to be found + * @param {RDFlibNamedNode} subject - The thing whose classes are to be found * @returns a hash table where key is NT of type and value is statement why we * think so. * Does NOT return terms, returns URI strings. @@ -490,7 +490,7 @@ export default class Formula extends Node { * Get all the Classes of which we can RDFS-infer the subject is a member * todo: This will loop is there is a class subclass loop (Sublass loops are * not illegal) - * @param {NamedNode} subject - The thing whose classes are to be found + * @param {RDFlibNamedNode} subject - The thing whose classes are to be found * @returns a hash table where key is NT of type and value is statement why we think so. * Does NOT return terms, returns URI strings. * We use NT representations in this version because they handle blank nodes. @@ -552,7 +552,7 @@ export default class Formula extends Node { * We use NT representations in this version because they handle blank nodes. * @param subject - A subject node */ - findTypeURIs(subject: TFSubject): UriMap { + findTypeURIs(subject: Quad_Subject): UriMap { return this.NTtoURI(this.findTypesNT(subject)) } @@ -563,8 +563,8 @@ export default class Formula extends Node { * @returns an array of statements, duplicate statements are suppresssed. */ connectedStatements( - subject: TFSubject, - doc: TFGraph, + subject: Quad_Subject, + doc: Quad_Graph, excludePredicateURIs?: ReadonlyArray ): Quad[] { excludePredicateURIs = excludePredicateURIs || [] @@ -794,10 +794,10 @@ export default class Formula extends Node { * @param g - The graph that contains the statement */ the ( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): Term | null | undefined { let x = this.any(s, p, o, g) if (x == null) { @@ -817,7 +817,7 @@ export default class Formula extends Node { */ transitiveClosure( seeds: BooleanMap, - predicate: TFPredicate, + predicate: Quad_Predicate, inverse?: boolean ): { [uri: string]: boolean; @@ -838,7 +838,7 @@ export default class Formula extends Node { } sups = inverse ? this.each(void 0, predicate, this.fromNT(t)) - : this.each(this.fromNT(t) as TFPredicate, predicate) + : this.each(this.fromNT(t) as Quad_Predicate, predicate) for (i = 0, len = sups.length; i < len; i++) { elt = sups[i] s = elt.toNT() @@ -862,9 +862,9 @@ export default class Formula extends Node { * @param types - The types */ topTypeURIs(types: { - [id: string]: string | NamedNode; + [id: string]: string | RDFlibNamedNode; }): { - [id: string]: string | NamedNode; + [id: string]: string | RDFlibNamedNode; } { let i let j @@ -926,10 +926,10 @@ export default class Formula extends Node { * @param g - The graph that contains the statement */ whether( - s?: TFSubject | null, - p?: TFPredicate | null, - o?: TFObject | null, - g?: TFGraph | null + s?: Quad_Subject | null, + p?: Quad_Predicate | null, + o?: Quad_Object | null, + g?: Quad_Graph | null ): number { return this.statementsMatching(s, p, o, g, false).length } diff --git a/src/literal.ts b/src/literal.ts index 44c61bd46..d946eab41 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -1,5 +1,5 @@ import ClassOrder from './class-order' -import NamedNode from './named-node' +import RDFlibNamedNode from './named-node' import Node from './node-internal' import { LiteralTermType, @@ -8,7 +8,7 @@ import { } from './types' import { isLiteral } from './utils/terms' import XSD from './xsd-internal' -import { TFLiteral, Term } from './tf-types' +import { Literal as TFLiteral, Term } from './tf-types' /** * An RDF literal, containing some value which isn't expressed as an IRI. @@ -23,7 +23,7 @@ export default class Literal extends Node implements TFLiteral { /** * The literal's datatype as a named node */ - datatype: NamedNode = XSD.string + datatype: RDFlibNamedNode = XSD.string isVar = 0 @@ -47,7 +47,7 @@ export default class Literal extends Node implements TFLiteral { this.language = language this.datatype = XSD.langString } else if (datatype) { - this.datatype = NamedNode.fromValue(datatype) + this.datatype = RDFlibNamedNode.fromValue(datatype) } else { this.datatype = XSD.string } @@ -153,7 +153,7 @@ export default class Literal extends Node implements TFLiteral { if (typeof value !== 'number') { throw new TypeError('Invalid argument to Literal.fromNumber()') } - let datatype: NamedNode + let datatype: RDFlibNamedNode const strValue = value.toString() if (strValue.indexOf('e') < 0 && Math.abs(value) <= Number.MAX_SAFE_INTEGER) { datatype = Number.isInteger(value) ? XSD.integer : XSD.decimal diff --git a/src/named-node.ts b/src/named-node.ts index e1cace1fb..1c801f1fb 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -3,7 +3,7 @@ import ClassOrder from './class-order' import Node from './node-internal' import { NamedNodeTermType, TermType} from './types' import { termValue } from './utils/termValue' -import { TFNamedNode } from './tf-types' +import { NamedNode as TFNamedNode } from './tf-types' /** * A named (IRI) RDF node diff --git a/src/namespace.ts b/src/namespace.ts index 68c1921ba..ba1bfe1f0 100644 --- a/src/namespace.ts +++ b/src/namespace.ts @@ -1,15 +1,15 @@ -import NamedNode from './named-node' -import { TFDataFactory, TFNamedNode } from './tf-types' +import RDFlibNamedNode from './named-node' +import { TFDataFactory, NamedNode } from './tf-types' /** * Gets a namespace for the specified namespace's URI * @param nsuri - The URI for the namespace * @param [factory] - The factory for creating named nodes with */ -export default function Namespace (nsuri: string, factory?: TFDataFactory): (ln: string) => TFNamedNode { - const dataFactory = factory || { namedNode: (value) => new NamedNode(value) as TFNamedNode } +export default function Namespace (nsuri: string, factory?: TFDataFactory): (ln: string) => NamedNode { + const dataFactory = factory || { namedNode: (value) => new RDFlibNamedNode(value) as NamedNode } - return function (ln: string): TFNamedNode { + return function (ln: string): NamedNode { return dataFactory.namedNode(nsuri + (ln || '')) } } diff --git a/src/serialize.ts b/src/serialize.ts index 1f948b773..ba1b03e79 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -3,14 +3,14 @@ import Formula from './formula' import Serializer from './serializer' import { ContentType} from './types' import IndexedFormula from './store' -import { TFBlankNode, TFNamedNode } from './tf-types' +import { BlankNode, NamedNode } from './tf-types' /** * Serialize to the appropriate format */ export default function serialize ( /** The graph or nodes that should be serialized */ - target: Formula | TFNamedNode | TFBlankNode, + target: Formula | NamedNode | BlankNode, /** The store */ kb?: IndexedFormula, base?: unknown, @@ -35,7 +35,7 @@ export default function serialize ( try { var sz = Serializer(kb) if ((opts as any).flags) sz.setFlags((opts as any).flags) - var newSts = kb!.statementsMatching(undefined, undefined, undefined, target as TFNamedNode) + var newSts = kb!.statementsMatching(undefined, undefined, undefined, target as NamedNode) var n3String: string sz.suggestNamespaces(kb!.namespaces) sz.setBase(base) diff --git a/src/statement.ts b/src/statement.ts index e3bb658bf..b9e5208de 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -8,7 +8,7 @@ import { TermType, } from './types' import { defaultGraphNode } from './utils/default-graph-uri' -import { TFGraph, TFObject, TFPredicate, Quad, TFSubject, Term } from './tf-types' +import { Quad_Graph, Quad_Object, Quad_Predicate, Quad, Quad_Subject, Term } from './tf-types' /** A Statement represents an RDF Triple or Quad. */ export default class Statement implements Quad { @@ -45,10 +45,10 @@ export default class Statement implements Quad void @@ -332,7 +334,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * * @param x The blank node to be declared, supported in N3 */ - declareExistential(x: TFBlankNode): TFBlankNode { + declareExistential(x: BlankNode): BlankNode { if (!this._existentialVariables) this._existentialVariables = [] this._existentialVariables.push(x) return x @@ -391,10 +393,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass */ // @ts-ignore differs from signature in Formula add ( - subj: TFSubject | Quad | Quad[] | Statement | Statement[], - pred?: TFPredicate, + subj: Quad_Subject | Quad | Quad[] | Statement | Statement[], + pred?: Quad_Predicate, obj?: Term, - why?: TFGraph + why?: Quad_Graph ): Quad | null | IndexedFormula { var i: number if (arguments.length === 1) { @@ -599,8 +601,8 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param flags Whether or not to do a two-directional copy and/or delete triples */ copyTo( - template: TFSubject, - target: TFSubject, + template: Quad_Subject, + target: Quad_Subject, flags?: Array<('two-direction' | 'delete')> ): void { if (!flags) flags = [] @@ -637,8 +639,8 @@ export default class IndexedFormula extends Formula { // IN future - allow pass // log.warn("Equating "+u1+" and "+u2); // @@ // @@JAMBO Must canonicalize the uris to prevent errors from a=b=c // 03-21-2010 - const u1 = this.canon(u1in) as TFSubject - const u2 = this.canon(u2in) as TFSubject + const u1 = this.canon(u1in) as Quad_Subject + const u2 = this.canon(u2in) as Quad_Subject var d = this.compareTerm(u1, u2) if (!d) { return true // No information in {a = a} @@ -686,10 +688,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param graph The graph that contains the statement */ match( - subject?: TFSubject | null, - predicate?: TFPredicate | null, - object?: TFObject | null, - graph?: TFGraph | null + subject?: Quad_Subject | null, + predicate?: Quad_Predicate | null, + object?: Quad_Object | null, + graph?: Quad_Graph | null ): Quad[] { return this.statementsMatching( Node.fromValue(subject), @@ -727,12 +729,12 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param action the function that should trigger */ newPropertyAction( - pred: TFPredicate, + pred: Quad_Predicate, action: ( store: IndexedFormula, - subject: TFSubject, - predicate: TFPredicate, - object: TFObject + subject: Quad_Subject, + predicate: Quad_Predicate, + object: Quad_Object ) => boolean ): boolean { // log.debug("newPropertyAction: "+pred) @@ -847,7 +849,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * Removes all statemnts in a doc * @param doc - The document / graph */ - removeDocument(doc: TFGraph): IndexedFormula { + removeDocument(doc: Quad_Graph): IndexedFormula { var sts: Quad[] = this.statementsMatching(undefined, undefined, undefined, doc).slice() // Take a copy as this is the actual index for (var i = 0; i < sts.length; i++) { this.removeStatement(sts[i]) @@ -864,10 +866,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param limit The number of statements to remove */ removeMany( - subj?: TFSubject | null, - pred?: TFPredicate | null, - obj?: TFObject | null, - why?: TFGraph | null, + subj?: Quad_Subject | null, + pred?: Quad_Predicate | null, + obj?: Quad_Object | null, + why?: Quad_Graph | null, limit?: number ): void { // log.debug("entering removeMany w/ subj,pred,obj,why,limit = " + subj +", "+ pred+", " + obj+", " + why+", " + limit) @@ -890,10 +892,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @param graph The graph that contains the statement */ removeMatches( - subject?: TFSubject | null, - predicate?: TFPredicate | null, - object?: TFObject | null, - graph?: TFGraph | null + subject?: Quad_Subject | null, + predicate?: Quad_Predicate | null, + object?: Quad_Object | null, + graph?: Quad_Graph | null ): IndexedFormula { this.removeStatements( this.statementsMatching(subject, predicate, object, graph) @@ -938,7 +940,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Replace big with small, obsoleted with obsoleting. */ - replaceWith (big: TFSubject, small: TFSubject): boolean { + replaceWith (big: Quad_Subject, small: Quad_Subject): boolean { // log.debug("Replacing "+big+" with "+small) // this.id(@@ var oldhash = this.id(big) var newhash = this.id(small) @@ -1036,10 +1038,10 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * @returns An array of nodes which match the wildcard position */ statementsMatching ( - subj?: TFSubject | null, - pred?: TFPredicate | null, - obj?: TFObject | null, - why?: TFGraph | null, + subj?: Quad_Subject | null, + pred?: Quad_Predicate | null, + obj?: Quad_Object | null, + why?: Quad_Graph | null, justOne?: boolean ): Quad[] { // log.debug("Matching {"+subj+" "+pred+" "+obj+"}") @@ -1118,7 +1120,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * A list of all the URIs by which this thing is known * @param term */ - uris(term: TFSubject): string[] { + uris(term: Quad_Subject): string[] { var cterm = this.canon(term) var terms = this.aliases[this.id(cterm)] if (!cterm.value) return [] diff --git a/src/tf-types.ts b/src/tf-types.ts index 5e09608bf..d46263612 100644 --- a/src/tf-types.ts +++ b/src/tf-types.ts @@ -27,7 +27,7 @@ export interface Term { * RDF/JS taskforce NamedNode * @link https://rdf.js.org/data-model-spec/#namednode-interface */ -export interface TFNamedNode extends Term { +export interface NamedNode extends Term { termType: NamedNodeTermType value: string } @@ -36,7 +36,7 @@ export interface TFNamedNode extends Term { * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ -export interface TFBlankNode extends Term { +export interface BlankNode extends Term { termType: BlankNodeTermType value: string } @@ -46,10 +46,10 @@ export interface TFBlankNode extends Term { * @link https://rdf.js.org/data-model-spec/#quad-interface */ export interface Quad< - S extends Term = TFSubject, - P extends Term = TFPredicate, - O extends Term = TFObject, - G extends Term = TFGraph + S extends Term = Quad_Subject, + P extends Term = Quad_Predicate, + O extends Term = Quad_Object, + G extends Term = Quad_Graph > { subject: S predicate: P @@ -61,7 +61,7 @@ export interface Quad< * RDF/JS taskforce Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ -export interface TFLiteral extends Term { +export interface Literal extends Term { /** Contains the constant "Literal". */ termType: LiteralTermType /** The text value, unescaped, without language or type (example: "Brad Pitt") */ @@ -72,14 +72,14 @@ export interface TFLiteral extends Term { */ language: string /** A NamedNode whose IRI represents the datatype of the literal. */ - datatype: TFNamedNode + datatype: NamedNode } /** * RDF/JS taskforce Variable * @link https://rdf.js.org/data-model-spec/#variable-interface */ -export interface TFVariable extends Term { +export interface Variable extends Term { /** Contains the constant "Variable". */ termType: VariableTermType /** The name of the variable without leading "?" (example: "a"). */ @@ -92,7 +92,7 @@ export interface TFVariable extends Term { * It's only allowed to assign a DefaultGraph to the graph property of a Quad. * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface */ -export interface TFDefaultGraph extends Term { +export interface DefaultGraph extends Term { termType: DefaultGraphTermType; /** should return and empty string'' */ value: string; @@ -107,28 +107,28 @@ export interface TFDefaultGraph extends Term { */ export interface TFDataFactory { /** Returns a new instance of NamedNode. */ - namedNode: (value: string) => TFNamedNode, + namedNode: (value: string) => NamedNode, /** * Returns a new instance of BlankNode. * If the value parameter is undefined a new identifier for the * blank node is generated for each call. */ - blankNode: (value?: string) => TFBlankNode, + blankNode: (value?: string) => BlankNode, /** * Returns a new instance of Literal. * If languageOrDatatype is a NamedNode, then it is used for the value of datatype. * Otherwise languageOrDatatype is used for the value of language. */ - literal: (value: string, languageOrDatatype: string | TFNamedNode) => TFLiteral, + literal: (value: string, languageOrDatatype: string | NamedNode) => Literal, /** Returns a new instance of Variable. This method is optional. */ - variable?: (value: string) => TFVariable, + variable?: (value: string) => Variable, /** * Returns an instance of DefaultGraph. */ - defaultGraph: () => TFDefaultGraph | TFNamedNode | TFBlankNode, + defaultGraph: () => DefaultGraph | NamedNode | BlankNode, /** * Returns a new instance of the specific Term subclass given by original.termType @@ -164,10 +164,10 @@ export interface TFDataFactory { } /** A RDF/JS taskforce Subject */ -export type TFSubject = TFNamedNode | TFBlankNode | TFVariable +export type Quad_Subject = NamedNode | BlankNode | Variable /** A RDF/JS taskforce Predicate */ -export type TFPredicate = TFNamedNode | TFVariable +export type Quad_Predicate = NamedNode | Variable /** A RDF/JS taskforce Object */ -export type TFObject = TFNamedNode | TFBlankNode | TFLiteral | TFVariable +export type Quad_Object = NamedNode | BlankNode | Literal | Variable /** A RDF/JS taskforce Graph */ -export type TFGraph = TFNamedNode | TFDefaultGraph | TFBlankNode | TFVariable +export type Quad_Graph = NamedNode | DefaultGraph | BlankNode | Variable diff --git a/src/types.ts b/src/types.ts index 7f3466137..308ab71c2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,16 +1,16 @@ import Node from './node-internal' -import Variable from './variable' -import BlankNode from './blank-node' +import RDFlibVariable from './variable' +import RDFlibBlankNode from './blank-node' import Collection from './collection' -import Literal from './literal' -import NamedNode from './named-node' -import DefaultGraph from './default-graph' +import RDFlibLiteral from './literal' +import RDFlibNamedNode from './named-node' +import RDFlibDefaultGraph from './default-graph' import { DataFactory } from './factories/factory-types' import IndexedFormula from './store' import Fetcher from './fetcher' import Statement from './statement' import Empty from './empty' -import { TFNamedNode, Term } from './tf-types' +import { NamedNode, Term } from './tf-types' /** * Types that support both Enums (for typescript) and regular strings @@ -71,13 +71,13 @@ export type ValueType = Term | Node | Date | string | number | boolean | undefin */ /** An RDF/JS Subject */ -export type SubjectType = BlankNode | NamedNode | Variable +export type SubjectType = RDFlibBlankNode | RDFlibNamedNode | RDFlibVariable /** An RDF/JS Predicate */ -export type PredicateType = NamedNode | Variable +export type PredicateType = RDFlibNamedNode | RDFlibVariable /** An RDF/JS Object */ -export type ObjectType = NamedNode | Literal | Collection | BlankNode | Variable | Empty +export type ObjectType = RDFlibNamedNode | RDFlibLiteral | Collection | RDFlibBlankNode | RDFlibVariable | Empty /** An RDF/JS Graph */ -export type GraphType = DefaultGraph | NamedNode | Variable // | Formula +export type GraphType = RDFlibDefaultGraph | RDFlibNamedNode | RDFlibVariable // | Formula export interface Bindings { [id: string]: Term; @@ -87,11 +87,11 @@ export interface Bindings { export type FromValueReturns = Term | undefined | null | Collection export interface IRDFlibDataFactory extends DataFactory< - NamedNode | BlankNode | Literal | Collection | Statement + RDFlibNamedNode | RDFlibBlankNode | RDFlibLiteral | Collection | Statement > { fetcher: (store: IndexedFormula, options: any) => Fetcher graph: (features, opts) => IndexedFormula - lit: (val: string, lang?: string, dt?: TFNamedNode) => Literal + lit: (val: string, lang?: string, dt?: NamedNode) => RDFlibLiteral st: ( subject: SubjectType, predicate: PredicateType, diff --git a/src/update-manager.ts b/src/update-manager.ts index ed69cb373..e965e017e 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -13,15 +13,17 @@ import { join as uriJoin } from './uri' import { isStore, isBlankNode } from './utils/terms' import * as Util from './util' import Statement from './statement' -import NamedNode from './named-node' +import RDFlibNamedNode from './named-node' import { termValue } from './utils/termValue' import { - TFBlankNode, - TFGraph, TFNamedNode, - TFObject, - TFPredicate, + BlankNode, + NamedNode, + Quad_Graph, + Quad_Object, + Quad_Predicate, + Quad_Subject, Quad, - TFSubject, Term + Term, } from './tf-types' interface UpdateManagerFormula extends IndexedFormula { @@ -78,7 +80,7 @@ export default class UpdateManager { this.patchControl = [] } - patchControlFor (doc: TFNamedNode) { + patchControlFor (doc: NamedNode) { if (!this.patchControl[doc.value]) { this.patchControl[doc.value] = [] } @@ -94,7 +96,7 @@ export default class UpdateManager { * @returns The method string SPARQL or DAV or * LOCALFILE or false if known, undefined if not known. */ - editable (uri: string | TFNamedNode, kb: IndexedFormula): string | boolean | undefined { + editable (uri: string | NamedNode, kb: IndexedFormula): string | boolean | undefined { if (!uri) { return false // Eg subject is bnode, no known doc to write to } @@ -142,7 +144,7 @@ export default class UpdateManager { for (var r = 0; r < requests.length; r++) { request = requests[r] if (request !== undefined) { - var response = kb.any(request, this.ns.link('response')) as TFSubject + var response = kb.any(request, this.ns.link('response')) as Quad_Subject if (request !== undefined) { var wacAllow = kb.anyValue(response, this.ns.httph('wac-allow')) if (wacAllow) { @@ -215,10 +217,10 @@ export default class UpdateManager { * Returns a list of all bnodes occurring in a statement * @private */ - statementBnodes (st: Quad): TFBlankNode[] { + statementBnodes (st: Quad): BlankNode[] { return [st.subject, st.predicate, st.object].filter(function (x) { return isBlankNode(x) - }) as TFBlankNode[] + }) as BlankNode[] } /** @@ -226,12 +228,12 @@ export default class UpdateManager { * @private */ statementArrayBnodes (sts: Quad[]) { - var bnodes: TFBlankNode[] = [] + var bnodes: BlankNode[] = [] for (let i = 0; i < sts.length; i++) { bnodes = bnodes.concat(this.statementBnodes(sts[i])) } bnodes.sort() // in place sort - result may have duplicates - var bnodes2: TFBlankNode[] = [] + var bnodes2: BlankNode[] = [] for (let j = 0; j < bnodes.length; j++) { if (j === 0 || !bnodes[j].equals(bnodes[j - 1])) { bnodes2.push(bnodes[j]) @@ -437,7 +439,7 @@ export default class UpdateManager { // @ts-ignore this.anonymize(obj) + ' ' + ' . }\n' - updater.fire((this.statement as [TFSubject, TFPredicate, TFObject, TFGraph])[3].value, query, callbackFunction) + updater.fire((this.statement as [Quad_Subject, Quad_Predicate, Quad_Object, Quad_Graph])[3].value, query, callbackFunction) } } } @@ -489,7 +491,7 @@ export default class UpdateManager { * @param doc * @param action */ - requestDownstreamAction (doc: TFNamedNode, action): void { + requestDownstreamAction (doc: NamedNode, action): void { var control = this.patchControlFor(doc) if (!control.pendingUpstream) { action(doc) @@ -508,27 +510,27 @@ export default class UpdateManager { * We want to start counting websocket notifications * to distinguish the ones from others from our own. */ - clearUpstreamCount (doc: TFNamedNode): void { + clearUpstreamCount (doc: NamedNode): void { var control = this.patchControlFor(doc) control.upstreamCount = 0 } - getUpdatesVia (doc: TFNamedNode): string | null { + getUpdatesVia (doc: NamedNode): string | null { var linkHeaders = this.store.fetcher.getHeader(doc, 'updates-via') if (!linkHeaders || !linkHeaders.length) return null return linkHeaders[0].trim() } - addDownstreamChangeListener (doc: TFNamedNode, listener): void { + addDownstreamChangeListener (doc: NamedNode, listener): void { var control = this.patchControlFor(doc) if (!control.downstreamChangeListeners) { control.downstreamChangeListeners = [] } control.downstreamChangeListeners.push(listener) - this.setRefreshHandler(doc, (doc: TFNamedNode) => { + this.setRefreshHandler(doc, (doc: NamedNode) => { this.reloadAndSync(doc) }) } - reloadAndSync (doc: TFNamedNode): void { + reloadAndSync (doc: NamedNode): void { var control = this.patchControlFor(doc) var updater = this @@ -590,7 +592,7 @@ export default class UpdateManager { * * @returns {boolean} */ - setRefreshHandler (doc: TFNamedNode, handler): boolean { + setRefreshHandler (doc: NamedNode, handler): boolean { let wssURI = this.getUpdatesVia(doc) // relative // var kb = this.store var theHandler = handler @@ -775,7 +777,7 @@ export default class UpdateManager { }) return } else if ((protocol as string).indexOf('SPARQL') >= 0) { - var bnodes: TFBlankNode[] = [] + var bnodes: BlankNode[] = [] if (ds.length) bnodes = this.statementArrayBnodes(ds) if (is.length) bnodes = bnodes.concat(this.statementArrayBnodes(is)) var context = this.bnodeContext(bnodes, doc) @@ -871,7 +873,7 @@ export default class UpdateManager { } updateDav ( - doc: TFSubject, + doc: Quad_Subject, ds, is, callbackFunction @@ -883,7 +885,7 @@ export default class UpdateManager { throw new Error('No record of our HTTP GET request for document: ' + doc) } // should not happen - var response = kb.any(request as TFNamedNode, this.ns.link('response')) as TFSubject + var response = kb.any(request as NamedNode, this.ns.link('response')) as Quad_Subject if (!response) { return null // throw "No record HTTP GET response for document: "+doc } @@ -941,7 +943,7 @@ export default class UpdateManager { * @param is * @param callbackFunction */ - updateLocalFile (doc: TFNamedNode, ds, is, callbackFunction): void { + updateLocalFile (doc: NamedNode, ds, is, callbackFunction): void { const kb = this.store console.log('Writing back to local file\n') // See http://simon-jung.blogspot.com/2007/10/firefox-extension-file-io.html @@ -1048,7 +1050,7 @@ export default class UpdateManager { * This is suitable for an initial creation of a document. */ put( - doc: NamedNode, + doc: RDFlibNamedNode, data: string | Quad[], contentType: string, callback: (uri: string, ok: boolean, errorMessage?: string, response?: unknown) => void, @@ -1093,7 +1095,7 @@ export default class UpdateManager { * document in the meantime. * * @param kb - * @param doc {NamedNode} + * @param doc {RDFlibNamedNode} * @param callbackFunction */ reload ( @@ -1138,7 +1140,7 @@ export default class UpdateManager { } } -interface docReloadType extends TFNamedNode { +interface docReloadType extends NamedNode { reloadTimeCount?: number reloadTimeTotal?: number } diff --git a/src/uri.ts b/src/uri.ts index 18945e3d5..324f13b8b 100644 --- a/src/uri.ts +++ b/src/uri.ts @@ -10,7 +10,7 @@ */ var alert = alert || console.log -import NamedNode from './named-node' +import RDFlibNamedNode from './named-node' /** * Gets the document part of an URI @@ -30,8 +30,8 @@ export function docpart(uri: string): string { * Gets the document part of an URI as a named node * @param x - The URI */ -export function document(x: string): NamedNode { - return new NamedNode(docpart(x)) +export function document(x: string): RDFlibNamedNode { + return new RDFlibNamedNode(docpart(x)) } /** diff --git a/src/utils.ts b/src/utils.ts index 81dac469d..ab8a5bd0a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import Fetcher from './fetcher' import log from './log' import { docpart } from './uri' import { string_startswith } from './util' -import { TFDataFactory, Quad, TFSubject, Term } from './tf-types' +import { TFDataFactory, Quad, Quad_Subject, Term } from './tf-types' /** RDF/JS Taskforce Typeguards */ @@ -80,12 +80,12 @@ const rdf = { */ export function arrayToStatements( rdfFactory: TFDataFactory, - subject: TFSubject, + subject: Quad_Subject, data: Term[] ): Quad[] { const statements: Quad[] = [] - data.reduce((id, _listObj, i, listData) => { + data.reduce((id, _listObj, i, listData) => { statements.push(rdfFactory.quad(id, rdfFactory.namedNode(rdf.first), listData[i])) let nextNode diff --git a/src/utils/terms.ts b/src/utils/terms.ts index b3f81a20b..25a98c93c 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -6,15 +6,16 @@ import Collection from '../collection' import IndexedFormula from '../store' import Statement from '../statement' import { - TFBlankNode, - TFGraph, - TFLiteral, TFNamedNode, - TFObject, - TFPredicate, + BlankNode, + Quad_Graph, + Literal, + NamedNode, + Quad_Object, + Quad_Predicate, Quad, - TFSubject, + Quad_Subject, Term, - TFVariable, + Variable, } from '../tf-types' /** TypeGuard for RDFLib Statements */ @@ -45,7 +46,7 @@ export function isRDFlibObject(obj: any): obj is ObjectType { } /** TypeGuard for RDFLib Variables */ -export function isVariable(obj: any): obj is TFVariable { +export function isVariable(obj: any): obj is Variable { return isTerm(obj) && (obj as Term).termType === TermType.Variable } @@ -58,7 +59,7 @@ export function isTerm(obj: any): obj is Term { } /** TypeGuard for RDF/JS TaskForce Literals */ -export function isLiteral(value: any): value is TFLiteral { +export function isLiteral(value: any): value is Literal { return (value as Term).termType === TermType.Literal } @@ -72,17 +73,17 @@ export function isQuad(obj: any): obj is Quad { } /** TypeGuard for RDF/JS TaskForce NamedNodes */ -export function isNamedNode(obj: any): obj is TFNamedNode { +export function isNamedNode(obj: any): obj is NamedNode { return isTerm(obj) && obj.termType === 'NamedNode' } /** TypeGuard for RDF/JS TaskForce BlankNodes */ -export function isBlankNode(obj: any): obj is TFBlankNode { +export function isBlankNode(obj: any): obj is BlankNode { return isTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' } /** TypeGuard for valid RDFJS Taskforce Subject types */ -export function isSubject(obj: any): obj is TFSubject { +export function isSubject(obj: any): obj is Quad_Subject { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || @@ -91,7 +92,7 @@ export function isSubject(obj: any): obj is TFSubject { } /** TypeGuard for valid RDFJS Taskforce Predicate types */ -export function isPredicate(obj: any): obj is TFPredicate { +export function isPredicate(obj: any): obj is Quad_Predicate { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable @@ -99,7 +100,7 @@ export function isPredicate(obj: any): obj is TFPredicate { } /** TypeGuard for valid RDFJS Taskforce Object types */ -export function isRDFObject(obj: any): obj is TFObject { +export function isRDFObject(obj: any): obj is Quad_Object { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || @@ -109,7 +110,7 @@ export function isRDFObject(obj: any): obj is TFObject { } /** TypeGuard for valid RDFJS Graph types */ -export function isGraph(obj: any): obj is TFGraph { +export function isGraph(obj: any): obj is Quad_Graph { return isTerm(obj) && ( obj.termType === TermType.NamedNode || obj.termType === TermType.Variable || diff --git a/src/variable.ts b/src/variable.ts index c347f543e..6bf0d740e 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -2,7 +2,7 @@ import ClassOrder from './class-order' import Node from './node-internal' import { TermType, VariableTermType } from './types' import * as Uri from './uri' -import { TFVariable } from './tf-types' +import { Variable as TFVariable } from './tf-types' /** * Variables are placeholders used in patterns to be matched. From 279a2a2fc54e14632541d50360be01795a7c8871 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 13 Dec 2019 17:43:13 +0100 Subject: [PATCH 17/29] Apply suggestions from code review Co-Authored-By: Vincent --- src/blank-node.ts | 6 +++--- src/factories/canonical-data-factory.ts | 2 +- src/factories/rdflib-data-factory.ts | 6 +++--- src/fetcher.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/blank-node.ts b/src/blank-node.ts index 3d5c369ea..e905b2c5e 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -13,7 +13,7 @@ export default class BlankNode extends Node implements TFBlankNode { * The next unique identifier for blank nodes */ static nextId: number = 0; - static NTAnonymousNodePrefix: string = '_:' + static NTAnonymousNodePrefix: '_:' = '_:' static termType: BlankNodeTermType; private static getId (id: string | unknown): string { @@ -56,7 +56,7 @@ export default class BlankNode extends Node implements TFBlankNode { /** * The identifier for the blank node - * @deprecated use {value} instead. + * @deprecated use [[value]] instead. */ public get id (): string { return this.value @@ -93,7 +93,7 @@ export default class BlankNode extends Node implements TFBlankNode { } toCanonical () { - return '_:' + this.value + return BlankNode.NTAnonymousNodePrefix + this.value } toString () { diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index d7e5af9e5..64ea8ee86 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -83,7 +83,7 @@ const CanonicalDataFactory: DataFactory = { /** * Generates a uniquely identifiably *idempotent* string for the given {term}. * - * Equivalent to {Term.hashString} + * Equivalent to [[Term.hashString]] * * @example Use this to associate data with a term in an object * { obj[id(term)] = "myData" } diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts index f64ca8252..7798d5e06 100644 --- a/src/factories/rdflib-data-factory.ts +++ b/src/factories/rdflib-data-factory.ts @@ -36,7 +36,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param val The lexical value * @param lang The language * @param dt The datatype - * @deprecated use {literal} with the second and third argument combined + * @deprecated use [[literal]] with the second and third argument combined */ lit (val: string, lang?: string, dt?: NamedNode): Literal { return this.literal('' + val, lang || dt) @@ -48,7 +48,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param predicate The predicate * @param object The object * @param graph The containing graph - * @deprecated use {quad} instead + * @deprecated use [[quad]] instead */ st ( subject: Term, @@ -64,7 +64,7 @@ const RDFlibDataFactory: IRDFlibDataFactory = { * @param subject The subject * @param predicate The predicate * @param object The object - * @deprecated use {quad} without the last argument instead + * @deprecated use [[quad]] without the last argument instead */ triple ( subject: SubjectType, diff --git a/src/fetcher.ts b/src/fetcher.ts index da6477ab2..3b7483313 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -206,7 +206,7 @@ class Handler { constructor (response: ExtendedResponse, dom?: Document) { this.response = response - // The bang operator here might need to be removed. + // The type assertion operator here might need to be removed. this.dom = dom! } } @@ -1867,7 +1867,7 @@ export default class Fetcher implements CallbackifyInterface { ): Promise | ExtendedResponse { const kb = this.store - const headers: Headers = (response as Response).headers + const headers = (response as Response).headers const reqNode = options.req From ae83701a431187567f1448a90392bb72a96348bd Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 16 Dec 2019 21:35:50 +0100 Subject: [PATCH 18/29] Rename Utils, add tests for .equals in datafactory --- src/fetcher.ts | 2 +- src/index.ts | 2 +- src/parse.ts | 2 +- src/rdfaparser.js | 2 +- src/serializer.js | 2 +- src/store.ts | 2 +- src/update-manager.ts | 2 +- src/{util.js => utils-js.js} | 0 src/utils.ts | 2 +- tests/unit/factories/canonical-data-factory-test.ts | 7 ++++++- tests/unit/indexed-formula-test.js | 2 +- 11 files changed, 15 insertions(+), 10 deletions(-) rename src/{util.js => utils-js.js} (100%) diff --git a/src/fetcher.ts b/src/fetcher.ts index 3b7483313..f330658aa 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -35,7 +35,7 @@ import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import * as Uri from './uri' import { isCollection, isNamedNode} from './utils/terms' -import * as Util from './util' +import * as Util from './utils-js' import serialize from './serialize' // @ts-ignore This is injected diff --git a/src/index.ts b/src/index.ts index f36a4b12c..5d0b7efef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,7 @@ import UpdateManager from './update-manager' import { UpdatesSocket } from './updates-via' import { UpdatesVia } from './updates-via' import * as uri from './uri' -import * as Util from './util' +import * as Util from './utils-js' import Variable from './variable' import DataFactory from './factories/rdflib-data-factory' diff --git a/src/parse.ts b/src/parse.ts index 5477e9035..11ec4c501 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -6,7 +6,7 @@ import N3Parser from './n3parser' import { parseRDFaDOM } from './rdfaparser' import RDFParser from './rdfxmlparser' import sparqlUpdateParser from './patch-parser' -import * as Util from './util' +import * as Util from './utils-js' import Formula from './formula' import { ContentType } from './types' import { Quad } from './tf-types' diff --git a/src/rdfaparser.js b/src/rdfaparser.js index 064a4e6e7..1c1989742 100644 --- a/src/rdfaparser.js +++ b/src/rdfaparser.js @@ -16,7 +16,7 @@ import BlankNode from './blank-node' import Literal from './literal' import NamedNode from './named-node' import * as Uri from './uri' -import * as Util from './util' +import * as Util from './utils-js' import rdf from './factories/canonical-data-factory' if (typeof Node === 'undefined') { // @@@@@@ Global. Interface to xmldom. diff --git a/src/serializer.js b/src/serializer.js index cf90aebc9..003ae465e 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -8,7 +8,7 @@ import NamedNode from './named-node' import BlankNode from './blank-node' import * as Uri from './uri' -import * as Util from './util' +import * as Util from './utils-js' import CanonicalDataFactory from './factories/canonical-data-factory' import { createXSD } from './xsd' diff --git a/src/store.ts b/src/store.ts index 9ecdf7c89..9828d4cad 100644 --- a/src/store.ts +++ b/src/store.ts @@ -21,7 +21,7 @@ import ClassOrder from './class-order' import { defaultGraphURI } from './factories/canonical-data-factory' import Formula, { FormulaOpts } from './formula' import { ArrayIndexOf } from './utils' -import { RDFArrayRemove } from './util' +import { RDFArrayRemove } from './utils-js' import { isRDFlibObject, isStore, diff --git a/src/update-manager.ts b/src/update-manager.ts index e965e017e..f40711a7e 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -11,7 +11,7 @@ import Namespace from './namespace' import Serializer from './serializer' import { join as uriJoin } from './uri' import { isStore, isBlankNode } from './utils/terms' -import * as Util from './util' +import * as Util from './utils-js' import Statement from './statement' import RDFlibNamedNode from './named-node' import { termValue } from './utils/termValue' diff --git a/src/util.js b/src/utils-js.js similarity index 100% rename from src/util.js rename to src/utils-js.js diff --git a/src/utils.ts b/src/utils.ts index ab8a5bd0a..0680e9a03 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import Fetcher from './fetcher' import log from './log' import { docpart } from './uri' -import { string_startswith } from './util' +import { string_startswith } from './utils-js' import { TFDataFactory, Quad, Quad_Subject, Term } from './tf-types' /** RDF/JS Taskforce Typeguards */ diff --git a/tests/unit/factories/canonical-data-factory-test.ts b/tests/unit/factories/canonical-data-factory-test.ts index b26f3e2c4..2d23fb767 100644 --- a/tests/unit/factories/canonical-data-factory-test.ts +++ b/tests/unit/factories/canonical-data-factory-test.ts @@ -18,7 +18,12 @@ describe('data-factory', () => { it('reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) }) - describe('equals', () => {}); + describe('equals', () => { + const uri = "https://w3.org/" + const otherUri = "https://h3h3.org/" + it('handles same NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(uri))).to.be.true) + it('handles different NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(otherUri))).to.be.false) + }); describe('id', () => { it('handles default graph', () => expect(Factory.id(new DefaultGraph())).to.equal('defaultGraph')) diff --git a/tests/unit/indexed-formula-test.js b/tests/unit/indexed-formula-test.js index 8df886783..d95d1cf77 100644 --- a/tests/unit/indexed-formula-test.js +++ b/tests/unit/indexed-formula-test.js @@ -5,7 +5,7 @@ import Formula from '../../src/formula' import IndexedFormula from '../../src/store' import NamedNode from '../../src/named-node' -import { RDFArrayRemove } from '../../src/util' +import { RDFArrayRemove } from '../../src/utils-js' import DataFactory from '../../src/factories/rdflib-data-factory' describe('IndexedFormula', () => { From 30fc8dbcfd6751f971401163dc788948bc064933 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 16 Dec 2019 21:44:51 +0100 Subject: [PATCH 19/29] PR Feedack: Cleanup data factory tests, docs --- src/factories/canonical-data-factory.ts | 2 +- src/update-manager.ts | 4 - .../factories/canonical-data-factory-test.ts | 88 +++++++++---------- .../factories/extended-term-factory-test.ts | 14 ++- 4 files changed, 49 insertions(+), 59 deletions(-) diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index 64ea8ee86..43cc718f9 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -29,7 +29,7 @@ export function defaultGraph(): NamedNode { return defaultGraphNode } -/** The internal RDFlib datafactory, which uses Collections */ +/** A basic internal RDFlib datafactory, which does not support Collections */ const CanonicalDataFactory: DataFactory = { supports: { diff --git a/src/update-manager.ts b/src/update-manager.ts index f40711a7e..76146bc5b 100644 --- a/src/update-manager.ts +++ b/src/update-manager.ts @@ -615,10 +615,6 @@ export default class UpdateManager { var socket if (typeof WebSocket !== 'undefined') { socket = new WebSocket(validWssURI) - //@ts-ignore Firefox Addon - } else if (typeof Services !== 'undefined') { // Firefox add on http://stackoverflow.com/questions/24244886/is-websocket-supported-in-firefox-for-android-addons - //@ts-ignore Firefox Addon - socket = (Services.wm.getMostRecentWindow('navigator:browser').WebSocket)(wssURI) } else if (typeof window !== 'undefined' && window.WebSocket) { socket = (window as any).WebSocket(validWssURI) } else { diff --git a/tests/unit/factories/canonical-data-factory-test.ts b/tests/unit/factories/canonical-data-factory-test.ts index 2d23fb767..d0cc69818 100644 --- a/tests/unit/factories/canonical-data-factory-test.ts +++ b/tests/unit/factories/canonical-data-factory-test.ts @@ -8,62 +8,58 @@ import DefaultGraph from '../../../src/default-graph' import Empty from '../../../src/empty' describe('data-factory', () => { - describe('DataFactory', () => { - describe('#supports', () => { - it('collections', () => { expect(Factory.supports[Feature.collections]).to.be.false() }) - it('defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) - it('equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) - it('identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) - it('id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) - it('reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) - }) + it('supports id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) + it('supports equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) + it('does not supports collections', () => { expect(Factory.supports[Feature.collections]).to.be.false() }) + it('does not supports defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) + it('does not supports identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) + it('does not supports reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) - describe('equals', () => { - const uri = "https://w3.org/" - const otherUri = "https://h3h3.org/" - it('handles same NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(uri))).to.be.true) - it('handles different NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(otherUri))).to.be.false) - }); + describe('equals', () => { + const uri = "https://w3.org/" + const otherUri = "https://h3h3.org/" + it('handles same NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(uri))).to.be.true) + it('handles different NamedNodes', () => expect(Factory.namedNode(uri).equals(Factory.namedNode(otherUri))).to.be.false) + }); - describe('id', () => { - it('handles default graph', () => expect(Factory.id(new DefaultGraph())).to.equal('defaultGraph')) - }); + describe('id', () => { + it('handles default graph', () => expect(Factory.id(new DefaultGraph())).to.equal('defaultGraph')) + }); - describe('isQuad', () => {}); + describe('isQuad', () => {}); - describe('literal', () => { - const plain = Factory.literal('s') - it('keeps the value', () => expect(plain.value).to.equal('s')) - it('creates a literal', () => expect(plain).to.be.instanceOf(Literal)) - it('defaults to string', () => expect(plain.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#string')) + describe('literal', () => { + const plain = Factory.literal('s') + it('keeps the value', () => expect(plain.value).to.equal('s')) + it('creates a literal', () => expect(plain).to.be.instanceOf(Literal)) + it('defaults to string', () => expect(plain.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#string')) - const integer = Factory.literal('23', Factory.namedNode('http://www.w3.org/2001/XMLSchema#integer')) - it('keeps the datatype', () => expect(integer.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#integer')) + const integer = Factory.literal('23', Factory.namedNode('http://www.w3.org/2001/XMLSchema#integer')) + it('keeps the datatype', () => expect(integer.datatype.value).to.equal('http://www.w3.org/2001/XMLSchema#integer')) - const langString = Factory.literal('s', 'en') - it('sets a language', () => expect(langString.language).to.equal('en')) - it('sets a language', () => expect(langString.datatype.value).to.equal('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')) - }); + const langString = Factory.literal('s', 'en') + it('sets a language', () => expect(langString.language).to.equal('en')) + it('sets a language', () => expect(langString.datatype.value).to.equal('http://www.w3.org/1999/02/22-rdf-syntax-ns#langString')) + }); - describe('namedNode', () => { - it('creates a named node', () => expect(Factory.namedNode('about:config')).to.be.instanceOf(NamedNode)) - it('preserves the value', () => expect(Factory.blankNode('http://example.com/').value).to.equal('http://example.com/')) - }); + describe('namedNode', () => { + it('creates a named node', () => expect(Factory.namedNode('about:config')).to.be.instanceOf(NamedNode)) + it('preserves the value', () => expect(Factory.blankNode('http://example.com/').value).to.equal('http://example.com/')) + }); - describe('quad', () => {}); + describe('quad', () => {}); - describe('quadToNQ', () => {}); + describe('quadToNQ', () => {}); - describe('termToNQ', () => { - it('handles blank nodes', () => expect(Factory.termToNQ(Factory.blankNode('g123'))).to.equal('_:g123')) - it('handles default graph', () => expect(Factory.termToNQ(new DefaultGraph())).to.equal('')) - it('handles the empty collection', () => expect(Factory.termToNQ(new Empty())).to.equal('')) - it('handles literals', () => expect(Factory.termToNQ(Factory.literal('text with "quotes"'))).to.equal('"text with \\"quotes\\""')) - it('handles named nodes', () => expect(Factory.termToNQ(Factory.namedNode('http://example.com/'))).to.equal('')) - }); + describe('termToNQ', () => { + it('handles blank nodes', () => expect(Factory.termToNQ(Factory.blankNode('g123'))).to.equal('_:g123')) + it('handles default graph', () => expect(Factory.termToNQ(new DefaultGraph())).to.equal('')) + it('handles the empty collection', () => expect(Factory.termToNQ(new Empty())).to.equal('')) + it('handles literals', () => expect(Factory.termToNQ(Factory.literal('text with "quotes"'))).to.equal('"text with \\"quotes\\""')) + it('handles named nodes', () => expect(Factory.termToNQ(Factory.namedNode('http://example.com/'))).to.equal('')) + }); - describe('toNQ', () => {}); + describe('toNQ', () => {}); - describe('variable', () => {}); - }) + describe('variable', () => {}); }) diff --git a/tests/unit/factories/extended-term-factory-test.ts b/tests/unit/factories/extended-term-factory-test.ts index 953325432..b886b2cf9 100644 --- a/tests/unit/factories/extended-term-factory-test.ts +++ b/tests/unit/factories/extended-term-factory-test.ts @@ -10,14 +10,12 @@ import Collection from '../../../src/collection' * e.g. Collection */ describe('extended-term-factory', () => { - describe('#supports', () => { - it('collections', () => { expect(Factory.supports[Feature.collections]).to.be.true() }) - it('defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) - it('equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) - it('identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) - it('id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) - it('reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) - }) + it('supports collections', () => { expect(Factory.supports[Feature.collections]).to.be.true() }) + it('supports equalsMethod', () => { expect(Factory.supports[Feature.equalsMethod]).to.be.true() }) + it('supports id', () => { expect(Factory.supports[Feature.id]).to.be.true() }) + it('does not supports identity', () => { expect(Factory.supports[Feature.identity]).to.be.false() }) + it('does not supports defaultGraphType', () => { expect(Factory.supports[Feature.defaultGraphType]).to.be.false() }) + it('does not supports reversibleId', () => { expect(Factory.supports[Feature.reversibleId]).to.be.false() }) describe('id', () => { it('handles collections', () => { From 79684f40e221b31d3ae15fedb24615fca46c8d1e Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Mon, 16 Dec 2019 22:27:46 +0100 Subject: [PATCH 20/29] TermTypes as consts, not enums --- src/blank-node.ts | 6 ++-- src/collection.ts | 5 ++- src/default-graph.ts | 8 ++--- src/empty.ts | 7 ++-- src/factories/canonical-data-factory.ts | 21 +++++++----- src/factories/extended-term-factory.ts | 4 +-- src/fetcher.ts | 6 ++-- src/formula.ts | 7 ++-- src/literal.ts | 6 ++-- src/named-node.ts | 30 ++++++++--------- src/node-internal.ts | 4 +-- src/statement.ts | 4 +-- src/tf-types.ts | 10 +++--- src/types.ts | 41 +++++++++------------- src/utils/terms.ts | 45 ++++++++++++------------- src/variable.ts | 6 ++-- 16 files changed, 97 insertions(+), 113 deletions(-) diff --git a/src/blank-node.ts b/src/blank-node.ts index e905b2c5e..3dace082d 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -1,7 +1,7 @@ import ClassOrder from './class-order' import Node from './node-internal' import IndexedFormula from './store' -import { BlankNodeTermType, TermType} from './types' +import { BlankNodeTermType } from './types' import { BlankNode as TFBlankNode } from './tf-types' /** @@ -9,12 +9,13 @@ import { BlankNode as TFBlankNode } from './tf-types' * @link https://rdf.js.org/data-model-spec/#blanknode-interface */ export default class BlankNode extends Node implements TFBlankNode { + static termType: typeof BlankNodeTermType = BlankNodeTermType; + termType: typeof BlankNodeTermType = BlankNodeTermType; /** * The next unique identifier for blank nodes */ static nextId: number = 0; static NTAnonymousNodePrefix: '_:' = '_:' - static termType: BlankNodeTermType; private static getId (id: string | unknown): string { if (id) { @@ -44,7 +45,6 @@ export default class BlankNode extends Node implements TFBlankNode { * Note that the existence of this property already indicates that it is a variable. */ isVar = 1 - termType: BlankNodeTermType = TermType.BlankNode; /** * Initializes this node diff --git a/src/collection.ts b/src/collection.ts index 3181063e6..58278db90 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -6,7 +6,6 @@ import { Bindings, CollectionTermType, FromValueReturns, - TermType, ValueType } from './types' import Variable from './variable' @@ -45,7 +44,8 @@ export function fromValue = any, C extends Node = export default class Collection< T extends Node = Node | RdflibBlankNode | Collection | Literal | Variable > extends Node implements Term { - static termType = TermType.Collection + static termType: typeof CollectionTermType = CollectionTermType + termType: typeof CollectionTermType = CollectionTermType classOrder = ClassOrder.Collection closed: boolean = false @@ -55,7 +55,6 @@ export default class Collection< */ elements: T[] = [] isVar = 0 - termType: CollectionTermType = TermType.Collection constructor (initial?: ReadonlyArray) { super((RdflibBlankNode.nextId++).toString()) diff --git a/src/default-graph.ts b/src/default-graph.ts index 65b4fe439..e17fe3ec2 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -1,13 +1,11 @@ -'use strict' import Node from './node-internal' -import { TermType, DefaultGraphTermType } from './types' +import { DefaultGraphTermType } from './types' import { DefaultGraph as TFDefaultGraph } from './tf-types' /** The RDF default graph */ export default class DefaultGraph extends Node implements TFDefaultGraph { - static termType = TermType.DefaultGraph; - - termType: DefaultGraphTermType = TermType.DefaultGraph; + static termType: typeof DefaultGraphTermType = DefaultGraphTermType; + termType: typeof DefaultGraphTermType = DefaultGraphTermType; constructor () { super('') diff --git a/src/empty.ts b/src/empty.ts index fdd23664b..2246cade1 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -1,14 +1,13 @@ import Node from './node-internal' -import { TermType } from './types' +import { EmptyTermType } from './types' import { Term } from './tf-types' /** * An empty node */ export default class Empty extends Node implements Term { - static termType = TermType.Empty - - termType = TermType.Empty + static termType: typeof EmptyTermType = EmptyTermType + termType: typeof EmptyTermType = EmptyTermType constructor () { super('') diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts index 43cc718f9..de90b8f1b 100644 --- a/src/factories/canonical-data-factory.ts +++ b/src/factories/canonical-data-factory.ts @@ -8,7 +8,12 @@ import { PredicateType, ObjectType, GraphType, - TermType, + EmptyTermType, + DefaultGraphTermType, + VariableTermType, + BlankNodeTermType, + LiteralTermType, + NamedNodeTermType, } from '../types' import { defaultGraphNode } from '../utils/default-graph-uri' import { @@ -98,9 +103,9 @@ const CanonicalDataFactory: DataFactory = { } switch (term.termType) { - case TermType.DefaultGraph: + case DefaultGraphTermType: return 'defaultGraph' - case TermType.Variable: + case VariableTermType: return Variable.toString(term) default: const nq = this.termToNQ(term) @@ -173,15 +178,15 @@ const CanonicalDataFactory: DataFactory = { /** Stringify a {term} to n-quads serialization. */ termToNQ(term: Term): string { switch (term.termType) { - case TermType.BlankNode: + case BlankNodeTermType: return '_:' + term.value - case TermType.DefaultGraph: + case DefaultGraphTermType: return '' - case TermType.Empty: + case EmptyTermType: return '' - case TermType.Literal: + case LiteralTermType: return Literal.toNT(term as Literal) - case TermType.NamedNode: + case NamedNodeTermType: return '<' + term.value + '>' default: throw new Error(`Can't serialize nonstandard term type (was '${term.termType}')`) diff --git a/src/factories/extended-term-factory.ts b/src/factories/extended-term-factory.ts index f89f2e269..b99234a36 100644 --- a/src/factories/extended-term-factory.ts +++ b/src/factories/extended-term-factory.ts @@ -1,6 +1,6 @@ import Collection from '../collection' import CanonicalDataFactory from './canonical-data-factory' -import { TermType, ValueType } from '../types' +import { ValueType, CollectionTermType } from '../types' import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from './factory-types' import { isCollection, isVariable } from '../utils/terms' import Variable from '../variable' @@ -50,7 +50,7 @@ const ExtendedTermFactory: CollectionFactory = { }, termToNQ (term: Term): string { - if (term.termType === TermType.Collection) { + if (term.termType === CollectionTermType) { return Collection.toNT(term) } diff --git a/src/fetcher.ts b/src/fetcher.ts index f330658aa..af9cd4998 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -1376,11 +1376,11 @@ export default class Fetcher implements CallbackifyInterface { uri: NamedNode | string, options: Options = {} ): Promise { - uri = (uri as NamedNode).value || uri // Accept object or string - let doc = new RDFlibNamedNode(uri).doc() // strip off # + const uriSting = termValue(uri) + let doc = new RDFlibNamedNode(uriSting).doc() // strip off # options.contentType = options.contentType || 'text/turtle' options.data = serialize(doc, this.store, doc.value, options.contentType) as string - return this.webOperation('PUT', uri, options) + return this.webOperation('PUT', uriSting, options) } webCopy (here: string, there: string, contentType): Promise { diff --git a/src/formula.ts b/src/formula.ts index a08ea9dd7..b12086ecb 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -9,7 +9,7 @@ import Serializer from './serialize' import Statement from './statement' import { Bindings, - TermType, + GraphTermType, } from './types' import { isStatement } from './utils/terms' import Variable from './variable' @@ -51,7 +51,7 @@ interface UriMap { * A formula, or store of RDF statements */ export default class Formula extends Node { - static termType = TermType.Graph + static termType: typeof GraphTermType = GraphTermType classOrder = ClassOrder.Graph @@ -83,8 +83,6 @@ export default class Formula extends Node { /** The stored statements */ statements: Quad[]; - termType = TermType.Graph - /** * Initializes this formula * @constructor @@ -103,7 +101,6 @@ export default class Formula extends Node { opts: FormulaOpts = {} ) { super('') - this.termType = Formula.termType this.statements = statements || [] this.constraints = constraints || [] this.initBindings = initBindings || [] diff --git a/src/literal.ts b/src/literal.ts index d946eab41..c0a58f880 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -3,7 +3,6 @@ import RDFlibNamedNode from './named-node' import Node from './node-internal' import { LiteralTermType, - TermType, ValueType } from './types' import { isLiteral } from './utils/terms' @@ -16,7 +15,8 @@ import { Literal as TFLiteral, Term } from './tf-types' */ // @ts-ignore Incorrectly extends due to fromValue() export default class Literal extends Node implements TFLiteral { - static termType = TermType.Literal + static termType: typeof LiteralTermType = LiteralTermType + termType: typeof LiteralTermType = LiteralTermType classOrder = ClassOrder.Literal @@ -32,8 +32,6 @@ export default class Literal extends Node implements TFLiteral { */ language: string = '' - termType: LiteralTermType = TermType.Literal - /** * Initializes a literal * @param value - The literal's lexical value diff --git a/src/named-node.ts b/src/named-node.ts index 1c801f1fb..f30c9c237 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -1,25 +1,24 @@ -'use strict' import ClassOrder from './class-order' import Node from './node-internal' -import { NamedNodeTermType, TermType} from './types' +import { NamedNodeTermType } from './types' import { termValue } from './utils/termValue' import { NamedNode as TFNamedNode } from './tf-types' +import { isTerm } from './utils/terms' /** * A named (IRI) RDF node */ export default class NamedNode extends Node implements TFNamedNode { - static termType = TermType.NamedNode - + static termType: typeof NamedNodeTermType = NamedNodeTermType + termType: typeof NamedNodeTermType = NamedNodeTermType classOrder = ClassOrder.NamedNode - termType: NamedNodeTermType = TermType.NamedNode /** * Create a named (IRI) RDF Node * @constructor - * @param iri {String} - The IRI for this node + * @param iri - The IRI for this node */ - constructor (iri) { + constructor (iri: string) { super(termValue(iri)) if (!this.value) { @@ -39,7 +38,7 @@ export default class NamedNode extends Node implements TFNamedNode { /** * Returns an $rdf node for the containing directory, ending in slash. */ - dir () { + dir (): NamedNode | null { var str = this.value.split('#')[0] var p = str.slice(0, -1).lastIndexOf('/') var q = str.indexOf('//') @@ -51,7 +50,7 @@ export default class NamedNode extends Node implements TFNamedNode { * Returns an NN for the whole web site, ending in slash. * Contrast with the "origin" which does NOT have a trailing slash */ - site () { + site (): NamedNode { var str = this.value.split('#')[0] var p = str.indexOf('//') if (p < 0) throw new Error('This URI does not have a web site part (origin)') @@ -67,7 +66,7 @@ export default class NamedNode extends Node implements TFNamedNode { * Creates the fetchable named node for the document. * Removes everything from the # anchor tag. */ - doc () { + doc (): NamedNode { if (this.value.indexOf('#') < 0) { return this } else { @@ -78,12 +77,12 @@ export default class NamedNode extends Node implements TFNamedNode { /** * Returns the URI including */ - toString () { + toString (): string { return '<' + this.value + '>' } /** The local identifier with the document */ - id () { + id (): string { return this.value.split('#')[1] } @@ -91,11 +90,11 @@ export default class NamedNode extends Node implements TFNamedNode { * Legacy getter and setter alias, node.uri * @deprecated use {value} */ - get uri () { + get uri (): string { return this.value } - set uri (uri) { + set uri (uri: string) { this.value = uri } @@ -107,8 +106,7 @@ export default class NamedNode extends Node implements TFNamedNode { if (typeof value === 'undefined' || value === null) { return value } - const isNode = value && value.termType - if (isNode) { + if (isTerm(value)) { return value } return new NamedNode(value) diff --git a/src/node-internal.ts b/src/node-internal.ts index a3e728bb6..2e6883cba 100644 --- a/src/node-internal.ts +++ b/src/node-internal.ts @@ -1,4 +1,4 @@ -import { ValueType, Bindings, FromValueReturns } from './types' +import { ValueType, Bindings, FromValueReturns, TermType } from './types' import { Term } from './tf-types' /** @@ -16,7 +16,7 @@ export default abstract class Node { static toJS: (term: any) => Date | Number | string | boolean | object | Array; /** The type of node */ - termType!: string; + termType!: TermType; /** The class order for this node */ classOrder!: number; diff --git a/src/statement.ts b/src/statement.ts index b9e5208de..fea65cd25 100644 --- a/src/statement.ts +++ b/src/statement.ts @@ -5,7 +5,7 @@ import { ObjectType, PredicateType, SubjectType, - TermType, + DefaultGraphTermType, } from './types' import { defaultGraphNode } from './utils/default-graph-uri' import { Quad_Graph, Quad_Object, Quad_Predicate, Quad, Quad_Subject, Term } from './tf-types' @@ -100,7 +100,7 @@ export default class Statement implements Quad { return isTerm(obj) - && (obj as Term).termType === TermType.Collection + && (obj as Term).termType === CollectionTermType } /** TypeGuard for valid RDFlib Object types, also allows Collections */ export function isRDFlibObject(obj: any): obj is ObjectType { return obj && Object.prototype.hasOwnProperty.call(obj, 'termType') && ( - obj.termType === TermType.NamedNode || - obj.termType === TermType.Variable || - obj.termType === TermType.BlankNode || - obj.termType === TermType.Collection || - obj.termType === TermType.Literal + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType || + obj.termType === CollectionTermType || + obj.termType === LiteralTermType ) } /** TypeGuard for RDFLib Variables */ export function isVariable(obj: any): obj is Variable { return isTerm(obj) - && (obj as Term).termType === TermType.Variable + && (obj as Term).termType === VariableTermType } /** TypeGuard for RDF/JS TaskForce Terms */ @@ -60,7 +59,7 @@ export function isTerm(obj: any): obj is Term { /** TypeGuard for RDF/JS TaskForce Literals */ export function isLiteral(value: any): value is Literal { - return (value as Term).termType === TermType.Literal + return (value as Term).termType === LiteralTermType } /** TypeGuard for RDF/JS TaskForce Quads */ @@ -85,36 +84,36 @@ export function isBlankNode(obj: any): obj is BlankNode { /** TypeGuard for valid RDFJS Taskforce Subject types */ export function isSubject(obj: any): obj is Quad_Subject { return isTerm(obj) && ( - obj.termType === TermType.NamedNode || - obj.termType === TermType.Variable || - obj.termType === TermType.BlankNode + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType ) } /** TypeGuard for valid RDFJS Taskforce Predicate types */ export function isPredicate(obj: any): obj is Quad_Predicate { return isTerm(obj) && ( - obj.termType === TermType.NamedNode || - obj.termType === TermType.Variable + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType ) } /** TypeGuard for valid RDFJS Taskforce Object types */ export function isRDFObject(obj: any): obj is Quad_Object { return isTerm(obj) && ( - obj.termType === TermType.NamedNode || - obj.termType === TermType.Variable || - obj.termType === TermType.BlankNode || - obj.termType === TermType.Literal + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType || + obj.termType === LiteralTermType ) } /** TypeGuard for valid RDFJS Graph types */ export function isGraph(obj: any): obj is Quad_Graph { return isTerm(obj) && ( - obj.termType === TermType.NamedNode || - obj.termType === TermType.Variable || - obj.termType === TermType.BlankNode || - obj.termType === TermType.DefaultGraph + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType || + obj.termType === DefaultGraphTermType ) } diff --git a/src/variable.ts b/src/variable.ts index 6bf0d740e..512e3f80e 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -1,6 +1,6 @@ import ClassOrder from './class-order' import Node from './node-internal' -import { TermType, VariableTermType } from './types' +import { VariableTermType } from './types' import * as Uri from './uri' import { Variable as TFVariable } from './tf-types' @@ -12,13 +12,13 @@ import { Variable as TFVariable } from './tf-types' * but the ? notation has an implicit base uri of 'varid:' */ export default class Variable extends Node implements TFVariable { - static termType = TermType.Variable + static termType: typeof VariableTermType = VariableTermType + termType: typeof VariableTermType = VariableTermType /** The base string for a variable's name */ base = 'varid:' classOrder = ClassOrder.Variable isVar = 1 - termType: VariableTermType = TermType.Variable /** The unique identifier of this variable */ uri: string From dfc669ef8c9b3407b21a32c2485e1e335137652a Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 09:33:55 +0100 Subject: [PATCH 21/29] Remove static termtypes --- src/blank-node.ts | 1 - src/default-graph.ts | 1 - src/empty.ts | 1 - src/formula.ts | 2 +- src/literal.ts | 1 - src/named-node.ts | 1 - src/types.ts | 1 + src/variable.ts | 1 - 8 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/blank-node.ts b/src/blank-node.ts index 3dace082d..5c409d95a 100644 --- a/src/blank-node.ts +++ b/src/blank-node.ts @@ -9,7 +9,6 @@ import { BlankNode as TFBlankNode } from './tf-types' * @link https://rdf.js.org/data-model-spec/#blanknode-interface */ export default class BlankNode extends Node implements TFBlankNode { - static termType: typeof BlankNodeTermType = BlankNodeTermType; termType: typeof BlankNodeTermType = BlankNodeTermType; /** * The next unique identifier for blank nodes diff --git a/src/default-graph.ts b/src/default-graph.ts index e17fe3ec2..c4fbd7ea7 100644 --- a/src/default-graph.ts +++ b/src/default-graph.ts @@ -4,7 +4,6 @@ import { DefaultGraph as TFDefaultGraph } from './tf-types' /** The RDF default graph */ export default class DefaultGraph extends Node implements TFDefaultGraph { - static termType: typeof DefaultGraphTermType = DefaultGraphTermType; termType: typeof DefaultGraphTermType = DefaultGraphTermType; constructor () { diff --git a/src/empty.ts b/src/empty.ts index 2246cade1..3a3e3c8d3 100644 --- a/src/empty.ts +++ b/src/empty.ts @@ -6,7 +6,6 @@ import { Term } from './tf-types' * An empty node */ export default class Empty extends Node implements Term { - static termType: typeof EmptyTermType = EmptyTermType termType: typeof EmptyTermType = EmptyTermType constructor () { diff --git a/src/formula.ts b/src/formula.ts index b12086ecb..807d38a81 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -51,7 +51,7 @@ interface UriMap { * A formula, or store of RDF statements */ export default class Formula extends Node { - static termType: typeof GraphTermType = GraphTermType + termType: typeof GraphTermType = GraphTermType classOrder = ClassOrder.Graph diff --git a/src/literal.ts b/src/literal.ts index c0a58f880..4e5255f66 100644 --- a/src/literal.ts +++ b/src/literal.ts @@ -15,7 +15,6 @@ import { Literal as TFLiteral, Term } from './tf-types' */ // @ts-ignore Incorrectly extends due to fromValue() export default class Literal extends Node implements TFLiteral { - static termType: typeof LiteralTermType = LiteralTermType termType: typeof LiteralTermType = LiteralTermType classOrder = ClassOrder.Literal diff --git a/src/named-node.ts b/src/named-node.ts index f30c9c237..e2c4bcb78 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -9,7 +9,6 @@ import { isTerm } from './utils/terms' * A named (IRI) RDF node */ export default class NamedNode extends Node implements TFNamedNode { - static termType: typeof NamedNodeTermType = NamedNodeTermType termType: typeof NamedNodeTermType = NamedNodeTermType classOrder = ClassOrder.NamedNode diff --git a/src/types.ts b/src/types.ts index 7702e382c..fa12098f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,6 +29,7 @@ export type TermType = typeof NamedNodeTermType | typeof DefaultGraphTermType | typeof CollectionTermType | typeof EmptyTermType + | typeof GraphTermType /** * A valid mime type header diff --git a/src/variable.ts b/src/variable.ts index 512e3f80e..03de55c54 100644 --- a/src/variable.ts +++ b/src/variable.ts @@ -12,7 +12,6 @@ import { Variable as TFVariable } from './tf-types' * but the ? notation has an implicit base uri of 'varid:' */ export default class Variable extends Node implements TFVariable { - static termType: typeof VariableTermType = VariableTermType termType: typeof VariableTermType = VariableTermType /** The base string for a variable's name */ From 637eba0fc1e82c403315935a463e8561ceee9f2c Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 10 Dec 2019 18:03:15 +0100 Subject: [PATCH 22/29] More tests for utils, stricter types in store.ts --- src/store.ts | 4 ++-- tests/unit/util-test.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/store.ts b/src/store.ts index 9828d4cad..a4491722c 100644 --- a/src/store.ts +++ b/src/store.ts @@ -248,13 +248,13 @@ export default class IndexedFormula extends Formula { // IN future - allow pass where?: any }, target: TFNamedNode, - patchCallback: (errorString: string) => void + patchCallback: (errorString?: string) => void ): void { var targetKB = this var ds var binding: Bindings | null = null - function doPatch (onDonePatch: Function) { + function doPatch (onDonePatch: (errorString?: string) => void) { if (patch['delete']) { ds = patch['delete'] // console.log(bindingDebug(binding)) diff --git a/tests/unit/util-test.js b/tests/unit/util-test.js index 12b0b463b..b51bc73e5 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -168,6 +168,8 @@ describe('util', () => { it('handles other objects', () => { const t = new NamedNode('http://example.org') expect(isVariable(t)).to.be.false() + expect(isVariable(undefined)).to.be.false() + expect(isVariable(2)).to.be.false() }) }) From faeddd2abf79508aee081653559714ce55b06018 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 13 Dec 2019 17:26:57 +0100 Subject: [PATCH 23/29] Match bolean simplification, use helper for URI --- src/fetcher.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fetcher.ts b/src/fetcher.ts index af9cd4998..569a3e952 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -595,12 +595,12 @@ function isXHTML (responseText) { function isXML (responseText: string): boolean { const match = responseText.match(/\s*<\?xml\s+version\s*=[^<>]+\?>/) - return match ? true : false + return !!match } function isXMLNS (responseText: string): boolean { const match = responseText.match(/[^(/) - return match ? true : false + return !!match } type StatusValues = From d0ec4bced7798ae9fc78f77d32c1bb934d82a1b2 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Fri, 13 Dec 2019 17:43:51 +0100 Subject: [PATCH 24/29] Implement feedback Vinnl and Megoth --- src/fetcher.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/fetcher.ts b/src/fetcher.ts index 569a3e952..910b92449 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -240,7 +240,7 @@ class RDFXMLHandler extends Handler { if (root.nodeName === 'parsererror') { // Mozilla only See issue/issue110 // have to fail the request return fetcher.failFetch(options, 'Badly formed XML in ' + - (options as any).resource.value, 'parse_error') + options.resource!.value, 'parse_error') } let parser = new RDFParser(kb) try { @@ -932,7 +932,9 @@ export default class Fetcher implements CallbackifyInterface { options = this.initFetchOptions(docuri, options) - return this.pendingFetchPromise(docuri, (options as AutoInitOptions).baseURI, (options as AutoInitOptions)) + const initialisedOptions = this.initFetchOptions(docuri, options) + + return this.pendingFetchPromise(docuri, initialisedOptions.baseURI, initialisedOptions) } pendingFetchPromise ( @@ -954,7 +956,7 @@ export default class Fetcher implements CallbackifyInterface { this.fetchQueue[originalUri] = pendingPromise // Clean up the queued promise after a time, if it's resolved - this.cleanupFetchRequest(originalUri, this.timeout) + this.cleanupFetchRequest(originalUri, null, this.timeout) } return pendingPromise.then(x => { @@ -966,7 +968,17 @@ export default class Fetcher implements CallbackifyInterface { }) } - cleanupFetchRequest (originalUri: string, timeout: number) { + /** + * @param _options - DEPRECATED + */ + cleanupFetchRequest ( + originalUri: string, + _options, + timeout: number + ) { + if (_options !== undefined) { + console.warn("_options is deprecated") + } this.timeouts[originalUri] = (this.timeouts[originalUri] || []).concat(setTimeout(() => { if (!this.isPending(originalUri)) { delete this.fetchQueue[originalUri] From bdfbcbedcc61b2952691a072458af76d83fd077e Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 17:03:54 +0100 Subject: [PATCH 25/29] Use union type and consts for mime types --- src/fetcher.ts | 14 +++++++------- src/named-node.ts | 2 +- src/serialize.ts | 33 ++++++++++++++++++++++----------- src/tf-types.ts | 2 +- src/types.ts | 39 ++++++++++++++++++++++++--------------- 5 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/fetcher.ts b/src/fetcher.ts index 910b92449..5c829cc01 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -43,7 +43,7 @@ import { fetch as solidAuthCli } from 'solid-auth-cli' // @ts-ignore This is injected import { fetch as solidAuthClient } from 'solid-auth-client' import { - ContentType + ContentType, TurtleContentType, RDFXMLContentType, XHTMLContentType } from './types' import { termValue } from './utils/termValue' import { @@ -69,8 +69,8 @@ const Parsable = { // This is a minimal set to allow the use of damaged servers if necessary const CONTENT_TYPE_BY_EXT = { - 'rdf': 'application/rdf+xml', - 'owl': 'application/rdf+xml', + 'rdf': RDFXMLContentType, + 'owl': RDFXMLContentType, 'n3': 'text/n3', 'ttl': 'text/turtle', 'nt': 'text/n3', @@ -217,7 +217,7 @@ class RDFXMLHandler extends Handler { } static register (fetcher: Fetcher) { - fetcher.mediatypes['application/rdf+xml'] = { + fetcher.mediatypes[RDFXMLContentType] = { 'q': 0.9 } } @@ -264,7 +264,7 @@ class XHTMLHandler extends Handler { } static register (fetcher: Fetcher) { - fetcher.mediatypes['application/xhtml+xml'] = {} + fetcher.mediatypes[XHTMLContentType] = {} } parse ( @@ -1390,7 +1390,7 @@ export default class Fetcher implements CallbackifyInterface { ): Promise { const uriSting = termValue(uri) let doc = new RDFlibNamedNode(uriSting).doc() // strip off # - options.contentType = options.contentType || 'text/turtle' + options.contentType = options.contentType || TurtleContentType options.data = serialize(doc, this.store, doc.value, options.contentType) as string return this.webOperation('PUT', uriSting, options) } @@ -1422,7 +1422,7 @@ export default class Fetcher implements CallbackifyInterface { */ async createIfNotExists ( doc: RDFlibNamedNode, - contentType = 'text/turtle', + contentType = TurtleContentType, data = '' ): Promise { const fetcher = this diff --git a/src/named-node.ts b/src/named-node.ts index e2c4bcb78..944047ba0 100644 --- a/src/named-node.ts +++ b/src/named-node.ts @@ -87,7 +87,7 @@ export default class NamedNode extends Node implements TFNamedNode { /** * Legacy getter and setter alias, node.uri - * @deprecated use {value} + * @deprecated use [[value]] */ get uri (): string { return this.value diff --git a/src/serialize.ts b/src/serialize.ts index ba1b03e79..768474ca0 100644 --- a/src/serialize.ts +++ b/src/serialize.ts @@ -1,7 +1,18 @@ import * as convert from './convert' import Formula from './formula' import Serializer from './serializer' -import { ContentType} from './types' +import { + ContentType, + JSONLDContentType, + N3ContentType, + N3LegacyContentType, + NQuadsAltContentType, + NQuadsContentType, + NTriplesContentType, + RDFXMLContentType, + TurtleContentType, + TurtleLegacyContentType, +} from './types' import IndexedFormula from './store' import { BlankNode, NamedNode } from './tf-types' @@ -30,7 +41,7 @@ export default function serialize ( ): string | undefined { base = base || target.value const opts = options || {} - contentType = contentType || ContentType.turtle // text/n3 if complex? + contentType = contentType || TurtleContentType // text/n3 if complex? var documentString: string | null = null try { var sz = Serializer(kb) @@ -40,30 +51,30 @@ export default function serialize ( sz.suggestNamespaces(kb!.namespaces) sz.setBase(base) switch (contentType) { - case ContentType.rdfxml: + case RDFXMLContentType: documentString = sz.statementsToXML(newSts) return executeCallback(null, documentString) - case ContentType.n3: - case ContentType.n3Legacy: + case N3ContentType: + case N3LegacyContentType: documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case ContentType.turtle: - case ContentType.turtleLegacy: + case TurtleContentType: + case TurtleLegacyContentType: sz.setFlags('si') // Suppress = for sameAs and => for implies documentString = sz.statementsToN3(newSts) return executeCallback(null, documentString) - case ContentType.nTriples: + case NTriplesContentType: sz.setFlags('deinprstux') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) return executeCallback(null, documentString) - case ContentType.jsonld: + case JSONLDContentType: sz.setFlags('deinprstux') // Use adapters to connect to incmpatible parser n3String = sz.statementsToNTriples(newSts) // n3String = sz.statementsToN3(newSts) convert.convertToJson(n3String, callback) break - case ContentType.nQuads: - case ContentType.nQuadsAlt: // @@@ just outpout the quads? Does not work for collections + case NQuadsContentType: + case NQuadsAltContentType: // @@@ just outpout the quads? Does not work for collections sz.setFlags('deinprstux q') // Suppress nice parts of N3 to make ntriples documentString = sz.statementsToNTriples(newSts) // q in flag means actually quads return executeCallback(null, documentString) diff --git a/src/tf-types.ts b/src/tf-types.ts index c87b3322c..119858ad4 100644 --- a/src/tf-types.ts +++ b/src/tf-types.ts @@ -20,7 +20,7 @@ export interface Term { * * Note that the task force spec only allows comparison with other terms */ - equals (other: any): boolean + equals (other: Term): boolean } /** diff --git a/src/types.ts b/src/types.ts index fa12098f0..a10de6135 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,24 +31,33 @@ export type TermType = typeof NamedNodeTermType | typeof EmptyTermType | typeof GraphTermType +export const HTMLContentType = "text/html" as const +export const JSONLDContentType = "application/ld+json" as const +export const N3ContentType = "text/n3" as const +export const N3LegacyContentType = "application/n3" as const +export const NQuadsAltContentType = "application/nquads" as const +export const NQuadsContentType = "application/n-quads" as const +export const NTriplesContentType = "application/n-triples" as const +export const RDFXMLContentType = "application/rdf+xml" as const +export const SPARQLUpdateContentType = "application/sparql-update" as const +export const TurtleContentType = "text/turtle" as const +export const TurtleLegacyContentType = "application/x-turtle" as const +export const XHTMLContentType = "application/xhtml+xml" as const + /** * A valid mime type header - * @todo Convert these to const enums when it's supported https://github.com/babel/babel/issues/8741 */ -export enum ContentType { - rdfxml = "application/rdf+xml", - turtle = "text/turtle", - turtleLegacy = "application/x-turtle", - n3 = "text/n3", - n3Legacy = "application/n3", - nTriples = "application/n-triples", - nQuads = "application/n-quads", - nQuadsAlt = "application/nquads", - jsonld = "application/ld+json", - xhtml = "application/xhtml+xml", - html = "text/html", - sparqlupdate = "application/sparql-update", -} +export type ContentType = typeof RDFXMLContentType + | typeof HTMLContentType + | typeof JSONLDContentType + | typeof N3ContentType + | typeof N3LegacyContentType + | typeof NQuadsAltContentType + | typeof NQuadsContentType + | typeof SPARQLUpdateContentType + | typeof TurtleContentType + | typeof TurtleLegacyContentType + | typeof XHTMLContentType /** A type for values that serves as inputs */ export type ValueType = Term | Node | Date | string | number | boolean | undefined | null | Collection From f4c460420f212458703214e089cd0e68ab34dd88 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 17:06:41 +0100 Subject: [PATCH 26/29] Removed taskforce references --- src/store.ts | 2 +- src/tf-types.ts | 24 ++++++++++++------------ src/types.ts | 2 +- src/utils.ts | 2 +- src/utils/terms.ts | 18 +++++++++--------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/store.ts b/src/store.ts index a4491722c..d3c57247a 100644 --- a/src/store.ts +++ b/src/store.ts @@ -680,7 +680,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Returns any quads matching the given arguments. - * Standard RDFJS Taskforce method for Source objects, implemented as an + * Standard RDFJS spec method for Source objects, implemented as an * alias to `statementsMatching()` * @param subject The subject * @param predicate The predicate diff --git a/src/tf-types.ts b/src/tf-types.ts index 119858ad4..cebf78839 100644 --- a/src/tf-types.ts +++ b/src/tf-types.ts @@ -8,7 +8,7 @@ import { } from './types' /** - * RDF/JS taskforce Term + * RDF/JS spec Term * @link https://rdf.js.org/data-model-spec/#term-interface */ export interface Term { @@ -24,7 +24,7 @@ export interface Term { } /** - * RDF/JS taskforce NamedNode + * RDF/JS spec NamedNode * @link https://rdf.js.org/data-model-spec/#namednode-interface */ export interface NamedNode extends Term { @@ -33,7 +33,7 @@ export interface NamedNode extends Term { } /** - * RDF/JS taskforce Literal + * RDF/JS spec Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ export interface BlankNode extends Term { @@ -42,7 +42,7 @@ export interface BlankNode extends Term { } /** - * RDF/JS taskforce Quad + * RDF/JS spec Quad * @link https://rdf.js.org/data-model-spec/#quad-interface */ export interface Quad< @@ -58,7 +58,7 @@ export interface Quad< } /** - * RDF/JS taskforce Literal + * RDF/JS spec Literal * @link https://rdf.js.org/data-model-spec/#literal-interface */ export interface Literal extends Term { @@ -76,7 +76,7 @@ export interface Literal extends Term { } /** - * RDF/JS taskforce Variable + * RDF/JS spec Variable * @link https://rdf.js.org/data-model-spec/#variable-interface */ export interface Variable extends Term { @@ -87,7 +87,7 @@ export interface Variable extends Term { } /** - * RDF/JS taskforce DefaultGraph + * RDF/JS spec DefaultGraph * An instance of DefaultGraph represents the default graph. * It's only allowed to assign a DefaultGraph to the graph property of a Quad. * @link https://rdf.js.org/data-model-spec/#defaultgraph-interface @@ -99,7 +99,7 @@ export interface DefaultGraph extends Term { } /** - * RDF/JS taskforce DataFactory + * RDF/JS spec DataFactory * * Not 100% compliant due to to practicality problems. * @@ -163,11 +163,11 @@ export interface TFDataFactory { supports: SupportTable } -/** A RDF/JS taskforce Subject */ +/** A RDF/JS spec Subject */ export type Quad_Subject = NamedNode | BlankNode | Variable -/** A RDF/JS taskforce Predicate */ +/** A RDF/JS spec Predicate */ export type Quad_Predicate = NamedNode | Variable -/** A RDF/JS taskforce Object */ +/** A RDF/JS spec Object */ export type Quad_Object = NamedNode | BlankNode | Literal | Variable -/** A RDF/JS taskforce Graph */ +/** A RDF/JS spec Graph */ export type Quad_Graph = NamedNode | DefaultGraph | BlankNode | Variable diff --git a/src/types.ts b/src/types.ts index a10de6135..813f9e484 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,7 +64,7 @@ export type ValueType = Term | Node | Date | string | number | boolean | undefin /** * In this project, there exist two types for the same kind of RDF concept. - * We have RDF/JS Taskforce types (standardized, generic), and RDFlib types (internal, specific). + * We have RDF/JS spec types (standardized, generic), and RDFlib types (internal, specific). * When deciding which type to use in a function, it is preferable to accept generic inputs, * whenever possible, and provide strict outputs. * In some ways, the TF types in here are a bit more strict. diff --git a/src/utils.ts b/src/utils.ts index 0680e9a03..8c0a89dc7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ import { docpart } from './uri' import { string_startswith } from './utils-js' import { TFDataFactory, Quad, Quad_Subject, Term } from './tf-types' -/** RDF/JS Taskforce Typeguards */ +/** RDF/JS spec Typeguards */ /** * Loads ontologies of the data we load (this is the callback from the kb to diff --git a/src/utils/terms.ts b/src/utils/terms.ts index a704669e3..b51871747 100644 --- a/src/utils/terms.ts +++ b/src/utils/terms.ts @@ -50,19 +50,19 @@ export function isVariable(obj: any): obj is Variable { && (obj as Term).termType === VariableTermType } -/** TypeGuard for RDF/JS TaskForce Terms */ +/** TypeGuard for RDF/JS spec Terms */ export function isTerm(obj: any): obj is Term { return typeof obj === 'object' && obj !== null && 'termType' in obj } -/** TypeGuard for RDF/JS TaskForce Literals */ +/** TypeGuard for RDF/JS spec Literals */ export function isLiteral(value: any): value is Literal { return (value as Term).termType === LiteralTermType } -/** TypeGuard for RDF/JS TaskForce Quads */ +/** TypeGuard for RDF/JS spec Quads */ export function isQuad(obj: any): obj is Quad { return typeof obj === "object" && obj !== null && ( 'subject' in obj @@ -71,17 +71,17 @@ export function isQuad(obj: any): obj is Quad { ) } -/** TypeGuard for RDF/JS TaskForce NamedNodes */ +/** TypeGuard for RDF/JS spec NamedNodes */ export function isNamedNode(obj: any): obj is NamedNode { return isTerm(obj) && obj.termType === 'NamedNode' } -/** TypeGuard for RDF/JS TaskForce BlankNodes */ +/** TypeGuard for RDF/JS spec BlankNodes */ export function isBlankNode(obj: any): obj is BlankNode { return isTerm(obj) && 'termType' in obj && obj.termType === 'BlankNode' } -/** TypeGuard for valid RDFJS Taskforce Subject types */ +/** TypeGuard for valid RDF/JS spec Subject types */ export function isSubject(obj: any): obj is Quad_Subject { return isTerm(obj) && ( obj.termType === NamedNodeTermType || @@ -90,7 +90,7 @@ export function isSubject(obj: any): obj is Quad_Subject { ) } -/** TypeGuard for valid RDFJS Taskforce Predicate types */ +/** TypeGuard for valid RDF/JS spec Predicate types */ export function isPredicate(obj: any): obj is Quad_Predicate { return isTerm(obj) && ( obj.termType === NamedNodeTermType || @@ -98,7 +98,7 @@ export function isPredicate(obj: any): obj is Quad_Predicate { ) } -/** TypeGuard for valid RDFJS Taskforce Object types */ +/** TypeGuard for valid RDF/JS spec Object types */ export function isRDFObject(obj: any): obj is Quad_Object { return isTerm(obj) && ( obj.termType === NamedNodeTermType || @@ -108,7 +108,7 @@ export function isRDFObject(obj: any): obj is Quad_Object { ) } -/** TypeGuard for valid RDFJS Graph types */ +/** TypeGuard for valid RDF/JS Graph types */ export function isGraph(obj: any): obj is Quad_Graph { return isTerm(obj) && ( obj.termType === NamedNodeTermType || From b6448daac583cd8c18657930e21f880f97a52e8b Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 17:19:47 +0100 Subject: [PATCH 27/29] Fix ContentType imports --- src/fetcher.ts | 2 +- src/parse.ts | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/fetcher.ts b/src/fetcher.ts index 5c829cc01..7f21a28fc 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -1461,7 +1461,7 @@ export default class Fetcher implements CallbackifyInterface { ): Promise { let headers = { // Force the right mime type for containers - 'content-type': ContentType.turtle, + 'content-type': TurtleContentType, 'link': this.ns.ldp('BasicContainer') + '; rel="type"' } diff --git a/src/parse.ts b/src/parse.ts index 11ec4c501..51ea41702 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -8,7 +8,7 @@ import RDFParser from './rdfxmlparser' import sparqlUpdateParser from './patch-parser' import * as Util from './utils-js' import Formula from './formula' -import { ContentType } from './types' +import { ContentType, TurtleContentType, N3ContentType, RDFXMLContentType, XHTMLContentType, HTMLContentType, SPARQLUpdateContentType, JSONLDContentType, NQuadsContentType, NQuadsAltContentType } from './types' import { Quad } from './tf-types' type CallbackFunc = (error: any, kb: Formula | null) => void @@ -31,30 +31,30 @@ export default function parse ( contentType: string | ContentType, callback?: CallbackFunc ) { - contentType = contentType || ContentType.turtle + contentType = contentType || TurtleContentType contentType = contentType.split(';')[0] as ContentType try { - if (contentType === ContentType.n3 || contentType === ContentType.turtle) { + if (contentType === N3ContentType || contentType === TurtleContentType) { var p = N3Parser(kb, kb, base, base, null, null, '', null) p.loadBuf(str) executeCallback() - } else if (contentType === ContentType.rdfxml) { + } else if (contentType === RDFXMLContentType) { var parser = new RDFParser(kb) parser.parse(Util.parseXML(str), base, kb.sym(base)) executeCallback() - } else if (contentType === ContentType.xhtml) { - parseRDFaDOM(Util.parseXML(str, {contentType: ContentType.xhtml}), kb, base) + } else if (contentType === XHTMLContentType) { + parseRDFaDOM(Util.parseXML(str, {contentType: XHTMLContentType}), kb, base) executeCallback() - } else if (contentType === ContentType.html) { - parseRDFaDOM(Util.parseXML(str, {contentType: ContentType.html}), kb, base) + } else if (contentType === HTMLContentType) { + parseRDFaDOM(Util.parseXML(str, {contentType: HTMLContentType}), kb, base) executeCallback() - } else if (contentType === ContentType.sparqlupdate) { // @@ we handle a subset + } else if (contentType === SPARQLUpdateContentType) { // @@ we handle a subset sparqlUpdateParser(str, kb, base) executeCallback() - } else if (contentType === ContentType.jsonld) { + } else if (contentType === JSONLDContentType) { jsonldParser(str, kb, base, executeCallback) - } else if (contentType === ContentType.nQuads || - contentType === ContentType.nQuadsAlt) { + } else if (contentType === NQuadsContentType || + contentType === NQuadsAltContentType) { var n3Parser = new N3jsParser({ factory: DataFactory }) nquadCallback(null, str) } else if (contentType === undefined) { @@ -89,11 +89,11 @@ export default function parse ( function executeErrorCallback (e: Error): void { if ( // TODO: Always true, what is the right behavior - contentType !== ContentType.jsonld || + contentType !== JSONLDContentType || // @ts-ignore always true? - contentType !== ContentType.nQuads || + contentType !== NQuadsContentType || // @ts-ignore always true? - contentType !== ContentType.nQuadsAlt + contentType !== NQuadsAltContentType ) { if (callback) { callback(e, kb) From 0ea38a7452ea77c60d4dc07cbb6320a45f091947 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 17:40:18 +0100 Subject: [PATCH 28/29] Fix import, rename TFDataFactory to RdfJs --- reference/fetcher-classes.js | 4 ++-- src/factories/factory-types.ts | 4 ++-- src/fetcher.ts | 4 ++-- src/formula.ts | 4 ++-- src/namespace.ts | 4 ++-- src/tf-types.ts | 2 +- src/utils.ts | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/reference/fetcher-classes.js b/reference/fetcher-classes.js index b907ceb23..91aebab57 100644 --- a/reference/fetcher-classes.js +++ b/reference/fetcher-classes.js @@ -1,4 +1,4 @@ -import { isTFNamedNode} from './utils/terms' +import { isNamedNode } from './utils/terms' const log = require('./log') const N3Parser = require('./n3parser') @@ -667,7 +667,7 @@ class Fetcher { userCallback = p2 } else if (typeof p2 === 'undefined') { // original calling signature // referingTerm = undefined - } else if (isTFNamedNode(p2)) { + } else if (isNamedNode(p2)) { // referingTerm = p2 options = {referingTerm: p2} } else { diff --git a/src/factories/factory-types.ts b/src/factories/factory-types.ts index 1d5cc43ff..1b663efc1 100644 --- a/src/factories/factory-types.ts +++ b/src/factories/factory-types.ts @@ -5,7 +5,7 @@ import BlankNode from '../blank-node' import Variable from '../variable' import { BlankNode as TFBlankNode, - TFDataFactory, + RdfJsDataFactory, Literal as TFLiteral, NamedNode as TFNamedNode, Quad, @@ -55,7 +55,7 @@ export enum Feature { export interface DataFactory< FactoryTypes = DefaultFactoryTypes, IndexType = Indexable -> extends TFDataFactory { +> extends RdfJsDataFactory { /** * BlankNode index * @private diff --git a/src/fetcher.ts b/src/fetcher.ts index 7f21a28fc..c1c227c9f 100644 --- a/src/fetcher.ts +++ b/src/fetcher.ts @@ -48,7 +48,7 @@ import { import { termValue } from './utils/termValue' import { BlankNode, - TFDataFactory, + RdfJsDataFactory, Quad_Graph, NamedNode, Quad_Predicate, @@ -84,7 +84,7 @@ const CONTENT_TYPE_BY_EXT = { // make its own list and not rely on the prefixes used here, // and not be tempted to add to them, and them clash with those of another // application. -const getNS = (factory?: TFDataFactory) => { +const getNS = (factory?: RdfJsDataFactory) => { return { link: Namespace('http://www.w3.org/2007/ont/link#', factory), http: Namespace('http://www.w3.org/2007/ont/http#', factory), diff --git a/src/formula.ts b/src/formula.ts index 807d38a81..b42a0b2cc 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -20,7 +20,7 @@ import { import { appliedFactoryMethods, arrayToStatements } from './utils' import { BlankNode, - TFDataFactory, + RdfJsDataFactory, Quad_Graph, Quad_Object, Quad_Predicate, @@ -32,7 +32,7 @@ import { export interface FormulaOpts { dataCallback?: (q: Quad) => void rdfArrayRemove?: (arr: Quad[], q: Quad) => void - rdfFactory?: TFDataFactory + rdfFactory?: RdfJsDataFactory } interface BooleanMap { diff --git a/src/namespace.ts b/src/namespace.ts index ba1bfe1f0..190e32ee6 100644 --- a/src/namespace.ts +++ b/src/namespace.ts @@ -1,12 +1,12 @@ import RDFlibNamedNode from './named-node' -import { TFDataFactory, NamedNode } from './tf-types' +import { RdfJsDataFactory, NamedNode } from './tf-types' /** * Gets a namespace for the specified namespace's URI * @param nsuri - The URI for the namespace * @param [factory] - The factory for creating named nodes with */ -export default function Namespace (nsuri: string, factory?: TFDataFactory): (ln: string) => NamedNode { +export default function Namespace (nsuri: string, factory?: RdfJsDataFactory): (ln: string) => NamedNode { const dataFactory = factory || { namedNode: (value) => new RDFlibNamedNode(value) as NamedNode } return function (ln: string): NamedNode { diff --git a/src/tf-types.ts b/src/tf-types.ts index cebf78839..51ce2d661 100644 --- a/src/tf-types.ts +++ b/src/tf-types.ts @@ -105,7 +105,7 @@ export interface DefaultGraph extends Term { * * @link https://rdf.js.org/data-model-spec/#datafactory-interface */ -export interface TFDataFactory { +export interface RdfJsDataFactory { /** Returns a new instance of NamedNode. */ namedNode: (value: string) => NamedNode, diff --git a/src/utils.ts b/src/utils.ts index 8c0a89dc7..c5eac100e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import Fetcher from './fetcher' import log from './log' import { docpart } from './uri' import { string_startswith } from './utils-js' -import { TFDataFactory, Quad, Quad_Subject, Term } from './tf-types' +import { RdfJsDataFactory, Quad, Quad_Subject, Term } from './tf-types' /** RDF/JS spec Typeguards */ @@ -79,7 +79,7 @@ const rdf = { * @return The {data} as a set of statements. */ export function arrayToStatements( - rdfFactory: TFDataFactory, + rdfFactory: RdfJsDataFactory, subject: Quad_Subject, data: Term[] ): Quad[] { From d41ef3717091de2a6ffd0ebc1103fa9fc16e5ac6 Mon Sep 17 00:00:00 2001 From: Joep Meindertsma Date: Tue, 17 Dec 2019 17:44:04 +0100 Subject: [PATCH 29/29] Stricter type Formula.fetcher --- src/formula.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/formula.ts b/src/formula.ts index b42a0b2cc..26ce0e691 100644 --- a/src/formula.ts +++ b/src/formula.ts @@ -28,6 +28,7 @@ import { Quad_Subject, Term, } from './tf-types' +import Fetcher from './fetcher' export interface FormulaOpts { dataCallback?: (q: Quad) => void @@ -63,7 +64,7 @@ export default class Formula extends Node { * * Is set by the fetcher when initialized. */ - fetcher?: any + fetcher?: Fetcher initBindings: ReadonlyArray