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/.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 bb27e00c3..7cf2e03dd 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", @@ -1040,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", @@ -1054,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", @@ -1092,6 +1268,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", @@ -1634,6 +1816,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", @@ -1841,7 +2032,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": { @@ -1878,7 +2069,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": { @@ -1923,7 +2114,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": { @@ -2043,15 +2234,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", @@ -2427,7 +2609,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 +2622,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 +2855,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": { @@ -2844,12 +3026,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", @@ -2860,23 +3036,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", @@ -3294,9 +3482,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 } } @@ -4241,6 +4429,26 @@ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, + "handlebars": { + "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", + "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", @@ -4333,6 +4541,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "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": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -4779,12 +4993,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": { @@ -4847,6 +5069,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", @@ -4869,59 +5097,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", @@ -5028,15 +5208,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", @@ -5046,15 +5217,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", @@ -5148,6 +5310,12 @@ "yallist": "^3.0.2" } }, + "lunr": { + "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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -5188,25 +5356,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", @@ -5224,12 +5373,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", @@ -5378,7 +5521,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 }, @@ -5466,9 +5609,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", @@ -5491,9 +5634,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": { @@ -5540,12 +5683,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", @@ -6010,6 +6147,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", @@ -6095,6 +6238,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", @@ -6468,6 +6621,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", @@ -6683,6 +6842,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", @@ -6828,15 +6996,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", @@ -7119,7 +7278,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": { @@ -7142,6 +7301,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", @@ -7732,6 +7902,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", @@ -7756,9 +7946,9 @@ "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==", + "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": { @@ -7770,12 +7960,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", @@ -8024,17 +8208,69 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typedoc": { + "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.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.1", + "typescript": "3.7.x" + } + }, + "typedoc-default-themes": { + "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.8", + "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.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.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", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } }, "underscore": { "version": "1.9.1", @@ -8680,6 +8916,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", @@ -8691,7 +8933,7 @@ }, "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": { @@ -8760,12 +9002,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", @@ -8790,22 +9026,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": { @@ -8814,6 +9049,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", @@ -8833,13 +9079,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", @@ -8847,58 +9104,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 82b1fa6be..93f37fdef 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", @@ -43,20 +55,22 @@ "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", "@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", "babel-loader": "^8.0.6", "chai": "^4.2.0", "diff": "^4.0.1", "dirty-chai": "^2.0.1", "fs-grep": "^0.0.5", - "jsdoc": "^3.6.3", - "mocha": "^6.2.0", + "mocha": "^6.2.2", "nock": "^10.0.6", "node-fetch": "^2.6.0", "node-mkdirp": "0.0.1", @@ -64,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", @@ -74,8 +89,8 @@ "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", - "prepare": "npm run build && npm run build:browser", + "doc": "rm -r doc ; typedoc", + "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/,*", @@ -94,7 +109,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/reference/fetcher-classes.js b/reference/fetcher-classes.js index 9f11f47b6..91aebab57 100644 --- a/reference/fetcher-classes.js +++ b/reference/fetcher-classes.js @@ -1,8 +1,7 @@ -import { isNamedNode } from '../src/util' +import { isNamedNode } from './utils/terms' 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 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..5c409d95a --- /dev/null +++ b/src/blank-node.ts @@ -0,0 +1,101 @@ +import ClassOrder from './class-order' +import Node from './node-internal' +import IndexedFormula from './store' +import { BlankNodeTermType } from './types' +import { BlankNode as TFBlankNode } from './tf-types' + +/** + * 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 { + termType: typeof BlankNodeTermType = BlankNodeTermType; + /** + * The next unique identifier for blank nodes + */ + static nextId: number = 0; + static NTAnonymousNodePrefix: '_:' = '_:' + + 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: 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 + + /** + * 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: BlankNode): number { + 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 + } + + /** + * 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 + } + + toCanonical () { + return BlankNode.NTAnonymousNodePrefix + this.value + } + + toString () { + return BlankNode.NTAnonymousNodePrefix + this.id + } +} diff --git a/src/class-order.js b/src/class-order.ts similarity index 50% rename from src/class-order.js rename to src/class-order.ts index f2529983e..d614b56e5 100644 --- a/src/class-order.js +++ b/src/class-order.ts @@ -1,4 +1,9 @@ -export default { +/** +* Class orders +*/ +const ClassOrder: { + [id: string]: number; +} = { 'Literal': 1, 'Collection': 3, 'Graph': 4, @@ -6,3 +11,5 @@ export default { 'BlankNode': 6, 'Variable': 7 } + +export default ClassOrder 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..58278db90 --- /dev/null +++ b/src/collection.ts @@ -0,0 +1,133 @@ +import RdflibBlankNode from './blank-node' +import ClassOrder from './class-order' +import Literal from './literal' +import Node from './node-internal' +import { + Bindings, + CollectionTermType, + FromValueReturns, + ValueType +} from './types' +import Variable from './variable' +import { isTerm } from './utils/terms' +import { Term } from './tf-types' + +/** + * 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 = any, C extends Node = any>(value: ValueType): T { + if (typeof value === 'undefined' || value === null) { + return value as T + } + + if (isTerm(value)) { // a Node subclass or a Collection + return value as T + } + + if (Array.isArray(value)) { + return new Collection(value) as T + } + + return Literal.fromValue(value) +} + +/** + * A collection of other RDF nodes + * + * Use generic T to control the contents of the array. + */ +export default class Collection< + T extends Node = Node | RdflibBlankNode | Collection | Literal | Variable +> extends Node implements Term { + static termType: typeof CollectionTermType = CollectionTermType + termType: typeof CollectionTermType = CollectionTermType + + classOrder = ClassOrder.Collection + closed: boolean = false + compareTerm = RdflibBlankNode.prototype.compareTerm + /** + * The nodes in this collection + */ + elements: T[] = [] + isVar = 0 + + constructor (initial?: ReadonlyArray) { + super((RdflibBlankNode.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: T): 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 (): T | undefined { + return this.elements.shift() + } + + /** + * Creates a new Collection with the substituting bindings applied + * @param bindings - The bindings to substitute + */ + substitute (bindings: Bindings) { + const elementsCopy = this.elements.map((ea) => ea.substitute(bindings)) + + return new Collection(elementsCopy) + } + + toNT () { + return Collection.toNT(this) + } + + static toNT (collection) { + return RdflibBlankNode.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: T): number { + return this.elements.unshift(element) + } +} diff --git a/src/data-factory-internal.js b/src/data-factory-internal.js deleted file mode 100644 index 59171e970..000000000 --- a/src/data-factory-internal.js +++ /dev/null @@ -1,91 +0,0 @@ -import BlankNode from './blank-node' -import Literal from './literal' -import NamedNode from './named-node' -import Statement from './statement' -import Variable from './variable' - -export const defaultGraphURI = 'chrome:theSession' -const defaultGraphNode = new NamedNode(defaultGraphURI) - -function blankNode (value) { - return new BlankNode(value) -} - -function defaultGraph () { - return defaultGraphNode -} - -/** - * Generates a unique identifier for the object. - * - * Equivalent to {Term.hashString} - */ -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() - } - - switch (term.termType) { - case "NamedNode": - return '<' + term.value + '>' - 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.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/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..c4fbd7ea7 --- /dev/null +++ b/src/default-graph.ts @@ -0,0 +1,16 @@ +import Node from './node-internal' +import { DefaultGraphTermType } from './types' +import { DefaultGraph as TFDefaultGraph } from './tf-types' + +/** The RDF default graph */ +export default class DefaultGraph extends Node implements TFDefaultGraph { + termType: typeof DefaultGraphTermType = DefaultGraphTermType; + + constructor () { + super('') + } + + toCanonical () { + return this.value + } +} diff --git a/src/empty.js b/src/empty.js deleted file mode 100644 index a24ee039d..000000000 --- a/src/empty.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict' -import Node from './node' - -/** - * Singleton subclass of an empty Collection. - */ -export default class Empty extends Node { - constructor () { - super() - this.termType = Empty.termType - } - toString () { - return '()' - } -} -Empty.termType = 'empty' diff --git a/src/empty.ts b/src/empty.ts new file mode 100644 index 000000000..3a3e3c8d3 --- /dev/null +++ b/src/empty.ts @@ -0,0 +1,18 @@ +import Node from './node-internal' +import { EmptyTermType } from './types' +import { Term } from './tf-types' + +/** +* An empty node +*/ +export default class Empty extends Node implements Term { + termType: typeof EmptyTermType = EmptyTermType + + constructor () { + super('') + } + + toString () { + return '()' + } +} diff --git a/src/factories/canonical-data-factory.ts b/src/factories/canonical-data-factory.ts new file mode 100644 index 000000000..de90b8f1b --- /dev/null +++ b/src/factories/canonical-data-factory.ts @@ -0,0 +1,215 @@ +import BlankNode from '../blank-node' +import Literal from '../literal' +import NamedNode from '../named-node' +import Statement from '../statement' +import Variable from '../variable' +import { + SubjectType, + PredicateType, + ObjectType, + GraphType, + EmptyTermType, + DefaultGraphTermType, + VariableTermType, + BlankNodeTermType, + LiteralTermType, + NamedNodeTermType, +} from '../types' +import { defaultGraphNode } from '../utils/default-graph-uri' +import { + Comparable, + DataFactory, + DefaultFactoryTypes, + Feature, +} from './factory-types' +import { isQuad, isTerm } from '../utils/terms' +import { NamedNode as TFNamedNode, Quad, Term } from '../tf-types' + +export { defaultGraphURI } from '../utils/default-graph-uri' + +/** + * Gets the default graph + */ +export function defaultGraph(): NamedNode { + return defaultGraphNode +} + +/** A basic internal RDFlib datafactory, which does not support 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 (isQuad(a) || isQuad(b)) { + if (isQuad(a) && isQuad(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 (isTerm(a) && isTerm(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: Term | Statement | undefined): string { + if (!term) { + return 'undefined' + } + + if (isQuad(term)) { + return this.quadToNQ(term) + } + + switch (term.termType) { + case DefaultGraphTermType: + return 'defaultGraph' + case VariableTermType: + 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) + } + + 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: 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 | 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: Term): string { + switch (term.termType) { + case BlankNodeTermType: + return '_:' + term.value + case DefaultGraphTermType: + return '' + case EmptyTermType: + return '' + case LiteralTermType: + return Literal.toNT(term as Literal) + case NamedNodeTermType: + 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: Term | (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..b99234a36 --- /dev/null +++ b/src/factories/extended-term-factory.ts @@ -0,0 +1,61 @@ +import Collection from '../collection' +import CanonicalDataFactory from './canonical-data-factory' +import { ValueType, CollectionTermType } from '../types' +import { DataFactory, DefaultFactoryTypes, Feature, Indexable } from './factory-types' +import { isCollection, isVariable } from '../utils/terms' +import Variable from '../variable' +import { Term } from '../tf-types' + +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: Term | 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: Term): string { + if (term.termType === CollectionTermType) { + return Collection.toNT(term) + } + + return CanonicalDataFactory.termToNQ(term) + } +} + +export default ExtendedTermFactory diff --git a/src/factories/factory-types.ts b/src/factories/factory-types.ts new file mode 100644 index 000000000..1b663efc1 --- /dev/null +++ b/src/factories/factory-types.ts @@ -0,0 +1,95 @@ +import Literal from '../literal' +import Statement from '../statement' +import NamedNode from '../named-node' +import BlankNode from '../blank-node' +import Variable from '../variable' +import { + BlankNode as TFBlankNode, + RdfJsDataFactory, + Literal as TFLiteral, + NamedNode as TFNamedNode, + Quad, + Term, + Variable as TFVariable, +} from '../tf-types' + +export type Comparable = Term | TFNamedNode | TFBlankNode | TFLiteral | Quad | 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 | Quad | TFVariable | Term + +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 + * + * bnIndex is optional but useful. + */ +export interface DataFactory< + FactoryTypes = DefaultFactoryTypes, + IndexType = Indexable +> extends RdfJsDataFactory { + /** + * BlankNode index + * @private + */ + bnIndex?: number + + supports: SupportTable + + literal(value: string, languageOrDatatype?: string | TFNamedNode): Literal + + isQuad(obj: any): obj is Statement + + equals(a: Comparable, b: Comparable): boolean + + toNQ(term: Term | FactoryTypes): string + + quad( + subject: Term, + predicate: Term, + object: Term, + graph?: Term, + ): Statement; + + quadToNQ(term: Statement | Quad): string + + termToNQ(term: Term): string + + /** + * 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: Term | FactoryTypes): IndexType +} diff --git a/src/factories/rdflib-data-factory.ts b/src/factories/rdflib-data-factory.ts new file mode 100644 index 000000000..7798d5e06 --- /dev/null +++ b/src/factories/rdflib-data-factory.ts @@ -0,0 +1,78 @@ +import { + IRDFlibDataFactory, + ObjectType, + PredicateType, + SubjectType, +} 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 { NamedNode, Term } from '../tf-types' + +/** Full RDFLib.js Data Factory */ +const RDFlibDataFactory: IRDFlibDataFactory = { + ...ExtendedTermFactory, + + /** + * 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) + */ + 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 + * @deprecated use [[literal]] with the second and third argument combined + */ + lit (val: string, lang?: string, dt?: NamedNode): Literal { + return this.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 + * @deprecated use [[quad]] instead + */ + st ( + subject: Term, + predicate: Term, + object: Term, + graph?: Term + ): Statement { + return this.quad(subject, predicate, object, graph) + }, + + /** + * Creates a new statement + * @param subject The subject + * @param predicate The predicate + * @param object The object + * @deprecated use [[quad]] without the last argument instead + */ + triple ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType + ): Statement { + return this.quad(subject, predicate, object) + }, +} + +export default RDFlibDataFactory diff --git a/src/fetcher.js b/src/fetcher.ts similarity index 63% rename from src/fetcher.js rename to src/fetcher.ts index 362908db4..c1c227c9f 100644 --- a/src/fetcher.js +++ b/src/fetcher.ts @@ -28,18 +28,32 @@ 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' import RDFParser from './rdfxmlparser' import * as Uri from './uri' -import { isNamedNode } from './util' -import * as Util from './util' +import { isCollection, isNamedNode} from './utils/terms' +import * as Util from './utils-js' 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 { + ContentType, TurtleContentType, RDFXMLContentType, XHTMLContentType +} from './types' +import { termValue } from './utils/termValue' +import { + BlankNode, + RdfJsDataFactory, + Quad_Graph, + NamedNode, + Quad_Predicate, + Quad_Subject +} from './tf-types' // This is a special fetch which does OIDC auth, catching 401 errors const fetch = typeof window === 'undefined' ? solidAuthCli : solidAuthClient @@ -55,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', @@ -70,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) => { +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), @@ -83,10 +97,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?: Quad_Subject + 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?: 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?: 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: Quad_Subject + /** The serialized resource in the body*/ + // Used for storing metadata of requests + original: NamedNode + // Like requeststatus? Can contain text with error. + data?: string + // Probably an identifier for request?s + req: BlankNode + // 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 type assertion operator here might need to be removed. + this.dom = dom! } } @@ -95,13 +216,22 @@ class RDFXMLHandler extends Handler { return 'RDFXMLHandler' } - static register (fetcher) { - fetcher.mediatypes['application/rdf+xml'] = { + static register (fetcher: Fetcher) { + fetcher.mediatypes[RDFXMLContentType] = { 'q': 0.9 } } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + /** An XML String */ + responseText: String, + /** Requires .original */ + options: { + original: Quad_Subject + req: Quad_Subject + } & Options, + ) { let kb = fetcher.store if (!this.dom) { this.dom = Util.parseXML(responseText) @@ -110,11 +240,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.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 +263,19 @@ class XHTMLHandler extends Handler { return 'XHTMLHandler' } - static register (fetcher) { - fetcher.mediatypes['application/xhtml+xml'] = {} + static register (fetcher: Fetcher) { + fetcher.mediatypes[XHTMLContentType] = {} } - parse (fetcher, responseText, options, response) { - let relation, reverse + parse ( + fetcher: Fetcher, + responseText: string, + options: { + resource: Quad_Subject + original: Quad_Subject + } & Options, + ): Promise | ExtendedResponse { + let relation, reverse: boolean if (!this.dom) { this.dom = Util.parseXML(responseText) } @@ -147,7 +284,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 +300,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 +308,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 +322,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 +340,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: Quad_Subject + req: BlankNode + resource: Quad_Subject + } & Options, + ): ExtendedResponse | Promise { let dom = Util.parseXML(responseText) // XML Semantics defined by root element namespace @@ -223,7 +370,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 +388,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 +401,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 +422,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: BlankNode, + resource: Quad_Subject, + original: Quad_Subject, + } & Options + ): Promise | ExtendedResponse { let kb = fetcher.store // We only handle XHTML so we have to figure out if this is XML @@ -291,7 +446,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 +455,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 +464,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 +487,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: Quad_Subject + original: Quad_Subject + resource: Quad_Subject + } & Options + ): ExtendedResponse | Promise { // We only speak dialects of XML right now. Is this XML? // Look for an XML declaration @@ -348,7 +511,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 +520,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 +536,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 +550,18 @@ class N3Handler extends Handler { } // post 2008 } - parse (fetcher, responseText, options, response) { + parse ( + fetcher: Fetcher, + responseText: string, + options: { + original: NamedNode + req: Quad_Subject + } & 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 +580,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 +593,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 +} + +function isXMLNS (responseText: string): boolean { + const match = responseText.match(/[^(/) + return !!match +} + +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[] } -function isXMLNS (responseText) { - return responseText.match(/[^(/) +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 +675,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: BlankNode /** - * @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) => Quad_Predicate } + 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 +729,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 +749,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 +761,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 +787,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 +840,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,17 +876,17 @@ 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. * - * @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) @@ -657,29 +915,33 @@ export default class Fetcher { * * @returns {Promise} */ - load (uri, options = {}) { + load ( + uri: NamedNode | 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 RDFlibNamedNode) docuri = docuri.split('#')[0] options = this.initFetchOptions(docuri, options) - return this.pendingFetchPromise(docuri, options.baseURI, options) + const initialisedOptions = this.initFetchOptions(docuri, options) + + return this.pendingFetchPromise(docuri, initialisedOptions.baseURI, initialisedOptions) } - /** - * @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 +956,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, null, this.timeout) } return pendingPromise.then(x => { @@ -706,21 +968,28 @@ export default class Fetcher { }) } - cleanupFetchRequest (originalUri, options, timeout) { + /** + * @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] } - }, 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 +997,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 +1025,7 @@ export default class Fetcher { } options.actualProxyURI = actualProxyURI - return options + return options as AutoInitOptions } /** @@ -767,7 +1036,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 +1050,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 +1092,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,8 +1148,13 @@ 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 | NamedNode, + p2?: UserCallback | Options, + userCallback?: UserCallback, + options: Options = {} + ): void { + const uri = termValue(uriIn) if (typeof p2 === 'function') { // nowOrWhenFetched (uri, userCallback) @@ -887,11 +1169,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 +1190,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 +1205,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: BlankNode, statusMessage: string) { // let now = new Date() statusMessage = '[' + now.getHours() + ':' + now.getMinutes() + ':' + @@ -934,9 +1214,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 +1229,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: BlankNode + original: Quad_Subject + } & 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 +1274,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: NamedNode, + rel: string, + uri: string, + why: Quad_Graph, + 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 +1309,11 @@ export default class Fetcher { } } - parseLinkHeader (linkHeader, originalUri, reqNode) { + parseLinkHeader ( + linkHeader: string, + originalUri: NamedNode, + reqNode: Quad_Graph + ): void { if (!linkHeader) { return } // const linkexp = /<[^>]*>\s*(\s*;\s*[^()<>@,;:"/[\]?={} \t]+=(([^()<>@,;:"/[]?={} \t]+)|("[^"]*")))*(,|$)/g @@ -1033,11 +1328,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 +1346,17 @@ export default class Fetcher { } } - doneFetch (options, response) { + doneFetch ( + options: { + req: Quad_Subject, + original: Quad_Subject + } & 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 +1368,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: Quad_Subject, now: Quad_Subject): 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 +1383,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 - let doc = new NamedNode(uri).doc() // strip off # - options.contentType = options.contentType || 'text/turtle' - options.data = serialize(doc, this.store, doc.uri, options.contentType) - return this.webOperation('PUT', uri, options) - } - - webCopy (here, there, contentType) { + putBack ( + uri: NamedNode | string, + options: Options = {} + ): Promise { + const uriSting = termValue(uri) + let doc = new RDFlibNamedNode(uriSting).doc() // strip off # + options.contentType = options.contentType || TurtleContentType + options.data = serialize(doc, this.store, doc.value, options.contentType) as string + return this.webOperation('PUT', uriSting, options) + } + + webCopy (here: string, there: string, contentType): Promise { return this.webOperation('GET', here) .then((result) => { return this.webOperation( @@ -1102,18 +1404,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 +1418,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: RDFlibNamedNode, + contentType = TurtleContentType, + data = '' + ): Promise { const fetcher = this try { - var response = await fetcher.load(doc) + 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) 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 +1446,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': TurtleContentType, 'link': this.ns.ldp('BasicContainer') + '; rel="type"' } @@ -1168,7 +1469,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 +1479,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 | NamedNode): 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 +1501,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 | NamedNode, + // 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 +1525,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 +1549,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 +1564,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: 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 @@ -1277,30 +1580,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: NamedNode, + 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 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')) - - 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 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())) @@ -1315,24 +1618,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 (isNamedNode(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 +1642,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: BlankNode, + resource: Quad_Subject + } & Options + ): BlankNode { 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 +1685,11 @@ export default class Fetcher { return responseNode } - objectRefresh (term) { + 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++) { - this.refresh(this.store.sym(Uri.docpart(uris[i]))) + this.refresh(this.store.rdfFactory.namedNode(Uri.docpart(uris[i]))) // what about rterm? } } @@ -1390,10 +1697,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: NamedNode, + userCallback?: UserCallback + ): void { // sources_refresh this.fireCallbacks('refresh', arguments) this.nowOrWhenFetched(term, { force: true, clearPreviousData: true }, @@ -1402,27 +1712,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: NamedNode, + 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: Quad_Graph) { // 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 +1749,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: NamedNode) { 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 +1782,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 +1797,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 +1826,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 +1836,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: NamedNode, + req: Quad_Subject, + 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 Quad_Subject 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 NamedNode), 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 = (response as Response).headers const reqNode = options.req @@ -1583,7 +1900,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 +1912,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 +1921,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 NamedNode, reqNode, kb, docuri) if (diffLocation) { - this.addType(this.ns.link('Document'), reqNode, kb, + this.addType(this.ns.link('Document') as NamedNode, reqNode, kb, diffLocation) } @@ -1619,10 +1936,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 +1947,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 +1955,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 +1968,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: Quad_Subject + ): 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 +2000,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 +2036,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 +2049,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 +2069,13 @@ export default class Fetcher { }) } - setRequestTimeout (uri, options) { + setRequestTimeout ( + uri: string, + options: { + req: Quad_Subject + original: Quad_Subject + } & Options + ): Promise { return new Promise((resolve) => { this.timeouts[uri] = (this.timeouts[uri] || []).concat(setTimeout(() => { if (this.isPending(uri) && @@ -1769,11 +2083,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 +2120,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.js b/src/formula.js deleted file mode 100644 index 34a361155..000000000 --- a/src/formula.js +++ /dev/null @@ -1,679 +0,0 @@ -'use strict' -import BlankNode from './blank-node' -import ClassOrder from './class-order' -import Collection from './collection' -import CanonicalDataFactory from './data-factory-internal' -import log from './log' -import NamedNode from './named-node' -import Namespace from './namespace' -import Node from './node' -import Serializer from './serialize' -import Statement from './statement' -import { appliedFactoryMethods, arrayToStatements, isStatement } from './util' -import Variable from './variable' - -/** @module formula */ - -export default class Formula extends Node { - /** - * @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 = {}) { - super() - this.termType = Formula.termType - this.statements = statements || [] - this.constraints = constraints || [] - this.initBindings = initBindings || [] - this.optional = optional || [] - - this.rdfFactory = (opts && opts.rdfFactory) || CanonicalDataFactory - // Enable default factory methods on this while preserving factory context. - for(const factoryMethod of appliedFactoryMethods) { - this[factoryMethod] = (...args) => this.rdfFactory[factoryMethod](...args) - } - } - /** 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 - */ - 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) - } - bnode (id) { - return this.rdfFactory.blankNode(id) - } - - addAll (statements) { - statements.forEach(quad => { - this.add(quad.subject, quad.predicate, quad.object, quad.graph) - }) - } - - /** Follow link from one node, using one wildcard, looking for one - * - * For example, any(me, knows, null, profile) - a person I know accoring to my profile . - * 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 - * @returns {Node} - A node which match the wildcard position, or null - */ - any (s, p, o, g) { - var st = this.anyStatementMatching(s, p, o, g) - if (st == null) { - return void 0 - } else if (s == null) { - return st.subject - } else if (p == null) { - return st.predicate - } else if (o == null) { - return st.object - } - return void 0 - } - - anyValue (s, p, o, g) { - var y = this.any(s, p, o, g) - return y ? y.value : void 0 - } - - anyJS (s, p, o, g) { - var y = this.any(s, p, o, g) - return y ? Node.toJS(y) : void 0 - } - - anyStatementMatching (subj, pred, obj, why) { - var x = this.statementsMatching(subj, pred, obj, why, true) - if (!x || x.length === 0) { - return undefined - } - return x[0] - } - - /** - * Returns a unique index-safe identifier for the given term. - * - * Falls back to the rdflib hashString implementation if the given factory doesn't support id. - */ - id (term) { - return this.rdfFactory.id(term) - } - - /** 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} 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 => - (!subj || subj.equals(st.subject)) && - (!pred || pred.equals(st.predicate)) && - (!obj || subj.equals(st.object)) && - (!why || why.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. - */ - bottomTypeURIs (types) { - var bots - var bottom - var elt - var i - var k - var len - var ref - var subs - var v - bots = [] - for (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)) - bottom = true - i = 0 - for (len = subs.length; i < len; i++) { - elt = subs[i] - ref = elt.uri - if (ref in types) { // the subclass is one we know - bottom = false - break - } - } - if (bottom) { - bots[k] = v - } - } - return bots - } - collection () { - return new Collection() - } - - /** 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 - * @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) - if (s == null) { - for (i = 0, len = sts.length; i < len; i++) { - elt = sts[i] - results.push(elt.subject) - } - } else if (p == null) { - for (l = 0, len1 = sts.length; l < len1; l++) { - elt = sts[l] - results.push(elt.predicate) - } - } else if (o == null) { - for (m = 0, len2 = sts.length; m < len2; m++) { - elt = sts[m] - results.push(elt.object) - } - } else if (g == null) { - for (q = 0, len3 = sts.length; q < len3; q++) { - elt = sts[q] - results.push(elt.why) - } - } - return results - } - equals (other) { - 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 - * 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 - * @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 - 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) { - if (!ref.hasOwnProperty(t)) continue - ref1 = this.statementsMatching(void 0, - this.sym('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), - this.fromNT(t)) - for (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.fromNT(t)) - for (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++) { - st = ref3[m] - members[st.subject.toNT()] = st - } - } - ref4 = this.each(void 0, - this.sym('http://www.w3.org/2000/01/rdf-schema#range'), - this.fromNT(t)) - for (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++) { - st = ref5[u] - members[st.object.toNT()] = st - } - } - } - return members - } - 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 - * think so. - * Does NOT return terms, returns URI strings. - * We use NT representations in this version because they handle blank nodes. - */ - findSubClassesNT (subject) { - var types = {} - types[subject.toNT()] = true - return this.transitiveClosure(types, - this.sym('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 - * @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. - */ - findSuperClassesNT (subject) { - var types = {} - types[subject.toNT()] = true - return this.transitiveClosure(types, - this.sym('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 - * not illegal) - * @param {NamedNode} 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. - */ - 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 - 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++) { - 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++) { - range = ref1[l] - types[range.toNT()] = st - } - } - } - ref2 = this.statementsMatching(void 0, void 0, subject) - for (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++) { - domain = ref3[q] - types[domain.toNT()] = st - } - } - return this.transitiveClosure(types, this.sym('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) { - excludePredicateURIs = excludePredicateURIs || [] - var todo = [subject] - var done = [] - var doneArcs = [] - var result = [] - var self = this - var follow = function (x) { - var 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) - .concat(self.statementsMatching(x, null, null, doc)) - sts = sts.filter(function (st) { - if (excludePredicateURIs[st.predicate.uri]) return false - var hash = st.toNT() - if (doneArcs[hash]) return false - doneArcs[hash] = true - return true - } - ) - sts.forEach(function (st, i) { - queue(st.subject) - queue(st.object) - }) - result = result.concat(sts) - } - while (todo.length) { - follow(todo.shift()) - } - // console.log('' + result.length + ' statements about ' + subject) - return result - } - - 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. - */ - fromNT (str) { - var dt, k, lang - switch (str[0]) { - case '<': - return this.sym(str.slice(1, -1)) - case '"': - lang = void 0 - dt = void 0 - k = str.lastIndexOf('"') - if (k < str.length - 1) { - if (str[k + 1] === '@') { - lang = str.slice(k + 2) - } else if (str.slice(k + 1, k + 3) === '^^') { - dt = this.fromNT(str.slice(k + 3)) - } else { - throw new Error("Can't convert string from NT: " + str) - } - } - str = str.slice(1, k) - str = str.replace(/\\"/g, '"') - str = str.replace(/\\n/g, '\n') - str = str.replace(/\\\\/g, '\\') - return this.literal(str, lang || dt) - case '_': - return this.rdfFactory.blankNode(str.slice(2)) - case '?': - return new Variable(str.slice(1)) - } - throw new Error("Can't convert from NT: " + str) - } - - holds (s, p, o, g) { - var i - if (arguments.length === 1) { - if (!s) { - return true - } - if (s instanceof Array) { - for (i = 0; i < s.length; i++) { - if (!this.holds(s[i])) { - return false - } - } - return true - } else if (isStatement(s)) { - return this.holds(s.subject, s.predicate, s.object, s.why) - } else if (s.statements) { - return this.holds(s.statements) - } - } - - var st = this.anyStatementMatching(s, p, o, g) - return st != null - } - holdsStatement (st) { - return this.holds(st.subject, st.predicate, st.object, st.why) - } - - /** - * Used by the n3parser to generate list elements - * @param values - The values of the collection - * @param context - The store - * @return {BlankNode|Collection} - The term for the statement - */ - list (values, context) { - if (context.rdfFactory.supports["COLLECTIONS"]) { - const collection = context.rdfFactory.collection() - values.forEach(function (val) { - collection.append(val) - }) - return collection - } else { - const node = context.rdfFactory.blankNode() - const statements = arrayToStatements(context.rdfFactory, node, values) - context.addAll(statements) - 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 - * todo: explain why it is important to go through NT - */ - NTtoURI (t) { - var k, v - var uris = {} - for (k in t) { - if (!t.hasOwnProperty(k)) continue - v = t[k] - if (k[0] === '<') { - uris[k.slice(1, -1)] = v - } - } - return uris - } - serialize (base, contentType, provenance) { - var documentString - var sts - var sz - sz = Serializer(this) - sz.suggestNamespaces(this.namespaces) - sz.setBase(base) - if (provenance) { - sts = this.statementsMatching(void 0, void 0, void 0, provenance) - } else { - sts = this.statements - } - switch ( - contentType != null ? contentType : 'text/n3') { - case 'application/rdf+xml': - documentString = sz.statementsToXML(sts) - break - case 'text/n3': - case 'text/turtle': - documentString = sz.statementsToN3(sts) - break - default: - throw new Error('serialize: Content-type ' + contentType + - ' not supported.') - } - return documentString - } - substitute (bindings) { - var statementsCopy = this.statements.map(function (ea) { - return ea.substitute(bindings) - }) - console.log('Formula subs statmnts:' + statementsCopy) - var y = new Formula() - y.add(statementsCopy) - console.log('indexed-form subs formula:' + y) - return y - } - 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) - } - the (s, p, o, g) { - var 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 - */ - transitiveClosure (seeds, predicate, inverse) { - var elt, i, len, s, sups, t - var agenda = {} - Object.assign(agenda, seeds) // make a copy - var done = {} // classes we have looked up - while (true) { - t = (function () { - for (var p in agenda) { - if (!agenda.hasOwnProperty(p)) continue - return p - } - })() - if (t == null) { - return done - } - sups = inverse ? this.each(void 0, predicate, this.fromNT(t)) : this.each(this.fromNT(t), predicate) - for (i = 0, len = sups.length; i < len; i++) { - elt = sups[i] - s = elt.toNT() - if (s in done) { - continue - } - if (s in agenda) { - continue - } - agenda[s] = agenda[t] - } - done[t] = agenda[t] - 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 - 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')) - for (i = 0, len = ref.length; i < len; i++) { - j = ref[i] - if (j.uri !== 'http://www.w3.org/2000/01/rdf-schema#Resource') { - n++ - break - } - } - if (!n) { - tops[k] = v - } - } - if (tops['http://www.w3.org/2000/01/rdf-schema#Resource']) { - delete tops['http://www.w3.org/2000/01/rdf-schema#Resource'] - } - if (tops['http://www.w3.org/2002/07/owl#Thing']) { - delete tops['http://www.w3.org/2002/07/owl#Thing'] - } - return tops - } - toString () { - return '{' + this.statements.join('\n') + '}' - } - whether (s, p, o, g) { - 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/formula.ts b/src/formula.ts new file mode 100644 index 000000000..26ce0e691 --- /dev/null +++ b/src/formula.ts @@ -0,0 +1,934 @@ +import ClassOrder from './class-order' +import Collection from './collection' +import CanonicalDataFactory from './factories/canonical-data-factory' +import log from './log' +import RDFlibNamedNode from './named-node' +import Namespace from './namespace' +import Node from './node-internal' +import Serializer from './serialize' +import Statement from './statement' +import { + Bindings, + GraphTermType, +} from './types' +import { isStatement } from './utils/terms' +import Variable from './variable' +import { + Indexable, + TFIDFactoryTypes, +} from './factories/factory-types' +import { appliedFactoryMethods, arrayToStatements } from './utils' +import { + BlankNode, + RdfJsDataFactory, + Quad_Graph, + Quad_Object, + Quad_Predicate, + Quad, + Quad_Subject, + Term, +} from './tf-types' +import Fetcher from './fetcher' + +export interface FormulaOpts { + dataCallback?: (q: Quad) => void + rdfArrayRemove?: (arr: Quad[], q: Quad) => void + rdfFactory?: RdfJsDataFactory +} + +interface BooleanMap { + [uri: string]: boolean; +} + +interface MembersMap { + [uri: string]: Quad; +} + +interface UriMap { + [uri: string]: string; +} + +/** + * A formula, or store of RDF statements + */ +export default class Formula extends Node { + termType: typeof GraphTermType = GraphTermType + + classOrder = ClassOrder.Graph + + /** The additional constraints */ + constraints: ReadonlyArray; + + /** + * The accompanying fetcher instance. + * + * Is set by the fetcher when initialized. + */ + fetcher?: Fetcher + + initBindings: ReadonlyArray + + isVar = 0 + + /** + * A namespace for the specified namespace's URI + * @param nsuri The URI for the namespace + */ + ns = Namespace + + optional: ReadonlyArray + + /** The factory used to generate statements and terms */ + rdfFactory: any + + /** The stored statements */ + statements: Quad[]; + + /** + * 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 opts.rdfFactory - The rdf factory that should be used by the store +*/ + constructor ( + statements?: Quad[], + constraints?: ReadonlyArray, + initBindings?: ReadonlyArray, + optional?: ReadonlyArray, + opts: FormulaOpts = {} + ) { + super('') + this.statements = statements || [] + this.constraints = constraints || [] + this.initBindings = initBindings || [] + this.optional = optional || [] + + this.rdfFactory = (opts && opts.rdfFactory) || CanonicalDataFactory + // Enable default factory methods on this while preserving factory context. + for(const factoryMethod of appliedFactoryMethods) { + this[factoryMethod] = (...args) => this.rdfFactory[factoryMethod](...args) + } + } + + /** Add a statement from its parts + * @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: Quad_Subject, + predicate: Quad_Predicate, + object: Quad_Object, + graph?: Quad_Graph + ): 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: Quad): number { + return this.statements.push(statement) + } + + /** @deprecated use {this.rdfFactory.blankNode} instead */ + bnode (id?: string): BlankNode { + return this.rdfFactory.blankNode(id) + } + + /** + * Adds all the statements to this formula + * @param statements - A collection of statements + */ + addAll (statements: Quad[]): void { + statements.forEach(quad => { + this.add(quad.subject, quad.predicate, quad.object, quad.graph) + }) + } + + /** Follow link from one node, using one wildcard, looking for one + * + * For example, any(me, knows, null, profile) - a person I know accoring to my profile . + * 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 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?: 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) { + return void 0 + } else if (s == null) { + return st.subject + } else if (p == null) { + return st.predicate + } else if (o == null) { + return st.object + } + + return void 0 + } + + /** + * 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?: 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 + } + + /** + * 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?: 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 + } + + /** + * Gets the first statement that matches the specified pattern + */ + anyStatementMatching( + 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) { + return undefined + } + + return x[0] + } + + /** + * Returns a unique index-safe identifier for the given term. + * + * Falls back to the rdflib hashString implementation if the given factory doesn't support id. + */ + id (term: TFIDFactoryTypes): Indexable { + return this.rdfFactory.id(term) + } + + /** + * Search the Store + * This is really a teaching method as to do this properly you would use IndexedFormula + * + * @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( + 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 => + (!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 + } + + /** + * 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) { + let bots + let bottom + let elt + let i + let len + let ref + let subs + let v + bots = [] + for (let k in types) { + if (!types.hasOwnProperty(k)) continue + v = types[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++) { + elt = subs[i] + ref = elt.uri + if (ref in types) { // the subclass is one we know + bottom = false + break + } + } + if (bottom) { + bots[k] = v + } + } + return bots + } + + /** Creates a new collection */ + collection (): Collection { + return new Collection() + } + + /** 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 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?: 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) + if (s == null) { + for (let i = 0, len = sts.length; i < len; i++) { + results.push(sts[i].subject) + } + } else if (p == null) { + for (let l = 0, len1 = sts.length; l < len1; l++) { + results.push(sts[l].predicate) + } + } else if (o == null) { + for (let m = 0, len2 = sts.length; m < len2; m++) { + results.push(sts[m].object) + } + } else if (g == null) { + for (let q = 0, len3 = sts.length; q < len3; q++) { + results.push(sts[q].graph) + } + } + + return results + } + + /** + * 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 + * @return a hash of URIs + */ + findMembersNT (thisClass) { + let len2: number + let len4: number + let m: number + let members: MembersMap + let pred: Quad_Predicate + let ref + let ref1: Quad[] + let ref2: Term[] + let ref3: Quad[] + let ref4: Term[] + let ref5: Quad[] + let seeds + let st + let u: number + seeds = {} + seeds[thisClass.toNT()] = true + members = {} + 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.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), + this.fromNT(t)) + for (let i = 0, len = ref1.length; i < len; i++) { + st = ref1[i] + members[st.subject.toNT()] = st + } + ref2 = this.each(void 0, + 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 Quad_Predicate + ref3 = this.statementsMatching(void 0, pred) + for (m = 0, len2 = ref3.length; m < len2; m++) { + st = ref3[m] + members[st.subject.toNT()] = st + } + } + ref4 = this.each(void 0, + 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 Quad_Predicate + ref5 = this.statementsMatching(void 0, pred) + for (u = 0, len4 = ref5.length; u < len4; u++) { + st = ref5[u] + members[st.object.toNT()] = st + } + } + } + + 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: Node): UriMap { + 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 + * think so. + * Does NOT return terms, returns URI strings. + * We use NT representations in this version because they handle blank nodes. + */ + findSubClassesNT(subject: Node): { [uri: string]: boolean } { + let types = {} + types[subject.toNT()] = 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 {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. + */ + findSuperClassesNT(subject: Node): { [uri: string]: boolean } { + let types = {} + types[subject.toNT()] = true + return this.transitiveClosure(types, + 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 + * not illegal) + * @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. + */ + findTypesNT (subject) { + 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 (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.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 (let m = 0, len2 = ref2.length; m < len2; m++) { + st = ref2[m] + 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.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 + * 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: Quad_Subject): UriMap { + return this.NTtoURI(this.findTypesNT(subject)) + } + + /** Trace statements which connect directly, or through bnodes + * + * @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: Quad_Subject, + doc: Quad_Graph, + excludePredicateURIs?: ReadonlyArray + ): Quad[] { + excludePredicateURIs = excludePredicateURIs || [] + let todo = [subject] + let done: { [k: string]: boolean } = {} + let doneArcs: { [k: string]: boolean } = {} + let result: Quad[] = [] + 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) + } + } + let sts = self.statementsMatching(null, null, x, doc) + .concat(self.statementsMatching(x, null, null, doc)) + 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) { + queue(st.subject) + queue(st.object) + }) + result = result.concat(sts) + } + while (todo.length) { + follow(todo.shift()) + } + // console.log('' + result.length + ' statements about ' + subject) + return result + } + + /** + * Creates a new empty formula + * + * @param _features - Not applicable, but necessary for typing to pass + */ + formula(_features?: ReadonlyArray): Formula { + return new Formula() + } + + /** + * Transforms an NTriples string format into a Node. + * 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) { + let dt, k, lang + switch (str[0]) { + case '<': + return this.sym(str.slice(1, -1)) + case '"': + lang = void 0 + dt = void 0 + k = str.lastIndexOf('"') + if (k < str.length - 1) { + if (str[k + 1] === '@') { + lang = str.slice(k + 2) + } else if (str.slice(k + 1, k + 3) === '^^') { + dt = this.fromNT(str.slice(k + 3)) + } else { + throw new Error("Can't convert string from NT: " + str) + } + } + str = str.slice(1, k) + str = str.replace(/\\"/g, '"') + str = str.replace(/\\n/g, '\n') + str = str.replace(/\\\\/g, '\\') + return this.rdfFactory.literal(str, lang || dt) + case '_': + return this.rdfFactory.blankNode(str.slice(2)) + case '?': + return new Variable(str.slice(1)) + } + throw new Error("Can't convert from NT: " + str) + } + + /** 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 + } + if (s instanceof Array) { + for (i = 0; i < s.length; i++) { + if (!this.holds(s[i])) { + return false + } + } + return true + } else if (isStatement(s)) { + return this.holds(s.subject, s.predicate, s.object, s.graph) + } else if (s.statements) { + return this.holds(s.statements) + } + } + + let st = this.anyStatementMatching(s, p, o, g) + return st != null + } + + /** + * Returns true if this formula holds the specified {statement} + */ + holdsStatement (statement) { + return this.holds( + statement.subject, + statement.predicate, + statement.object, + statement.graph, + ) + } + + /** + * Used by the n3parser to generate list elements + * @param values - The values of the collection + * @param context - The store + * @return {BlankNode|Collection} - The term for the statement + */ + list (values, context) { + if (context.rdfFactory.supports["COLLECTIONS"]) { + const collection = context.rdfFactory.collection() + values.forEach(function (val) { + collection.append(val) + }) + return collection + } else { + const node = context.rdfFactory.blankNode() + const statements = arrayToStatements(context.rdfFactory, node, values) + context.addAll(statements) + return node + } + } + + /** + * 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) { + let k, v + let uris = {} + for (k in t) { + if (!t.hasOwnProperty(k)) continue + v = t[k] + if (k[0] === '<') { + uris[k.slice(1, -1)] = v + } + } + 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) { + let documentString + let sts + let sz + sz = Serializer(this) + sz.suggestNamespaces(this.ns) + sz.setBase(base) + if (provenance) { + sts = this.statementsMatching(void 0, void 0, void 0, provenance) + } else { + sts = this.statements + } + switch ( + contentType != null ? contentType : 'text/n3') { + case 'application/rdf+xml': + documentString = sz.statementsToXML(sts) + break + case 'text/n3': + case 'text/turtle': + documentString = sz.statementsToN3(sts) + break + default: + throw new Error('serialize: Content-type ' + contentType + + ' not supported.') + } + return documentString + } + + /** + * Creates a new formula with the substituting bindings applied + * @param bindings - The bindings to substitute + */ + //@ts-ignore signature not compatible with Node + substitute(bindings: Bindings): Formula { + let statementsCopy = this.statements.map(function (ea) { + return (ea as Statement).substitute(bindings) + }) + console.log('Formula subs statmnts:' + statementsCopy) + const y = new Formula() + y.addAll(statementsCopy as Quad[]) + console.log('indexed-form subs formula:' + y) + return y + } + + /** + * @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. + * @param s - The subject + * @param p - The predicate + * @param o - The object + * @param g - The graph that contains the statement + */ + the ( + 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) { + 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 - A hash of NTs of classes to start with + * @param predicate - The property to trace though + * @param inverse - Trace inverse direction + */ + transitiveClosure( + seeds: BooleanMap, + predicate: Quad_Predicate, + inverse?: boolean + ): { + [uri: string]: boolean; + } { + let elt, i, len, s, sups, t + let agenda = {} + Object.assign(agenda, seeds) // make a copy + let done = {} // classes we have looked up + while (true) { + t = (function () { + for (let p in agenda) { + if (!agenda.hasOwnProperty(p)) continue + return p + } + })() + if (t == null) { + return done + } + sups = inverse ? + this.each(void 0, predicate, this.fromNT(t)) + : this.each(this.fromNT(t) as Quad_Predicate, predicate) + for (i = 0, len = sups.length; i < len; i++) { + elt = sups[i] + s = elt.toNT() + if (s in done) { + continue + } + if (s in agenda) { + continue + } + agenda[s] = agenda[t] + } + done[t] = agenda[t] + 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. + * @param types - The types + */ + topTypeURIs(types: { + [id: string]: string | RDFlibNamedNode; + }): { + [id: string]: string | RDFlibNamedNode; + } { + 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.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') { + n++ + break + } + } + if (!n) { + tops[k] = v + } + } + if (tops['http://www.w3.org/2000/01/rdf-schema#Resource']) { + delete tops['http://www.w3.org/2000/01/rdf-schema#Resource'] + } + if (tops['http://www.w3.org/2002/07/owl#Thing']) { + delete tops['http://www.w3.org/2002/07/owl#Thing'] + } + return tops + } + + /** + * Serializes this formula to a string + */ + toString(): string { + return '{' + this.statements.join('\n') + '}' + } + + /** + * 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?: 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/index.ts b/src/index.ts index f49c6ceeb..5d0b7efef 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' @@ -27,8 +26,11 @@ 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' + +export * from './utils/terms' const NextId = BlankNode.nextId @@ -104,3 +106,4 @@ export { triple, variable, } +export { termValue } from './utils/termValue' 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.js b/src/literal.js deleted file mode 100644 index 55ba43ffd..000000000 --- a/src/literal.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict' -import ClassOrder from './class-order' -import NamedNode from './named-node' -import Node from './node-internal' -import XSD from './xsd-internal' - -export default class Literal extends Node { - constructor (value, language, datatype) { - super() - this.termType = Literal.termType - this.value = value - if (language) { - this.lang = language - datatype = XSD.langString - } - // If not specified, a literal has the implied XSD.string default datatype - if (datatype) { - this.datatype = NamedNode.fromValue(datatype) - } - } - copy () { - return new Literal(this.value, this.lang, this.datatype) - } - equals (other) { - 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 - } - set language (language) { - this.lang = language || '' - } - toNT() { - return Literal.toNT(this) - } - static toNT (literal) { - if (typeof literal.value === 'number') { - return '' + literal.value - } else if (typeof literal.value !== 'string') { - throw new Error('Value of RDF literal is not string or number: ' + - literal.value) - } - var str = literal.value - str = str.replace(/\\/g, '\\\\') - str = str.replace(/\"/g, '\\"') - str = str.replace(/\n/g, '\\n') - str = '"' + str + '"' - - if (literal.language) { - str += '@' + literal.language - } else if (!literal.datatype.equals(XSD.string)) { - // Only add datatype if it's not a string - str += '^^' + literal.datatype.toCanonical() - } - return str - } - toString () { - return '' + this.value - } - /** - * @method fromBoolean - * @static - * @param value {Boolean} - * @return {Literal} - */ - static fromBoolean (value) { - let strValue = value ? '1' : '0' - return new Literal(strValue, null, XSD.boolean) - } - /** - * @method fromDate - * @static - * @param value {Date} - * @return {Literal} - */ - static fromDate (value) { - if (!(value instanceof Date)) { - throw new TypeError('Invalid argument to Literal.fromDate()') - } - let d2 = function (x) { - return ('' + (100 + x)).slice(1, 3) - } - let date = '' + value.getUTCFullYear() + '-' + d2(value.getUTCMonth() + 1) + - '-' + d2(value.getUTCDate()) + 'T' + d2(value.getUTCHours()) + ':' + - d2(value.getUTCMinutes()) + ':' + d2(value.getUTCSeconds()) + 'Z' - return new Literal(date, null, XSD.dateTime) - } - /** - * @method fromNumber - * @static - * @param value {Number} - * @return {Literal} - */ - static fromNumber (value) { - if (typeof value !== 'number') { - throw new TypeError('Invalid argument to Literal.fromNumber()') - } - let datatype - const strValue = value.toString() - if (strValue.indexOf('e') < 0 && Math.abs(value) <= Number.MAX_SAFE_INTEGER) { - datatype = Number.isInteger(value) ? XSD.integer : XSD.decimal - } else { - datatype = XSD.double - } - return new Literal(strValue, null, datatype) - } - /** - * @method fromValue - * @param value - * @return {Literal} - */ - static fromValue (value) { - if (typeof value === 'undefined' || value === null) { - return value - } - if (typeof value === 'object' && value.termType) { // this is a Node instance - return value - } - switch (typeof value) { - case 'object': - if (value instanceof Date) { - return Literal.fromDate(value) - } - case 'boolean': - return Literal.fromBoolean(value) - case 'number': - return Literal.fromNumber(value) - case 'string': - return new Literal(value) - } - throw new Error("Can't make literal from " + value + ' of type ' + - typeof value) - - } -} -Literal.termType = 'Literal' -Literal.prototype.classOrder = ClassOrder['Literal'] -Literal.prototype.datatype = XSD.string -Literal.prototype.lang = '' -Literal.prototype.isVar = 0 diff --git a/src/literal.ts b/src/literal.ts new file mode 100644 index 000000000..4e5255f66 --- /dev/null +++ b/src/literal.ts @@ -0,0 +1,187 @@ +import ClassOrder from './class-order' +import RDFlibNamedNode from './named-node' +import Node from './node-internal' +import { + LiteralTermType, + ValueType +} from './types' +import { isLiteral } from './utils/terms' +import XSD from './xsd-internal' +import { Literal as TFLiteral, Term } from './tf-types' + +/** + * An RDF literal, containing some value which isn't expressed as an IRI. + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +// @ts-ignore Incorrectly extends due to fromValue() +export default class Literal extends Node implements TFLiteral { + termType: typeof LiteralTermType = LiteralTermType + + classOrder = ClassOrder.Literal + + /** + * The literal's datatype as a named node + */ + datatype: RDFlibNamedNode = XSD.string + + isVar = 0 + + /** + * The language for the literal + */ + language: string = '' + + /** + * 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) + + if (language) { + this.language = language + this.datatype = XSD.langString + } else if (datatype) { + this.datatype = RDFlibNamedNode.fromValue(datatype) + } else { + this.datatype = XSD.string + } + } + + /** + * Gets a copy of this literal + */ + copy (): Literal { + return new Literal(this.value, this.lang, this.datatype) + } + + /** + * Gets whether two literals are the same + * @param other The other statement + */ + equals (other: Term): boolean { + if (!other) { + return false + } + + return (this.termType === other.termType) && + (this.value === other.value) && + (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 (): string { + return this.language + } + + set lang (language: string) { + this.language = language || '' + } + + toNT(): string { + return Literal.toNT(this) + } + + /** 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') { + throw new Error('Value of RDF literal is not string or number: ' + + literal.value) + } + var str = literal.value + str = str.replace(/\\/g, '\\\\') + str = str.replace(/\"/g, '\\"') + str = str.replace(/\n/g, '\\n') + str = '"' + str + '"' + + if (literal.language) { + str += '@' + literal.language + } else if (!literal.datatype.equals(XSD.string)) { + // Only add datatype if it's not a string + str += '^^' + literal.datatype.toCanonical() + } + return str + } + + toString () { + return '' + this.value + } + + /** + * Builds a literal node from a boolean value + * @param value - The value + */ + static fromBoolean (value: boolean): Literal { + let strValue = value ? '1' : '0' + return new Literal(strValue, null, XSD.boolean) + } + + /** + * Builds a literal node from a date value + * @param value The value + */ + static fromDate(value: Date): Literal { + if (!(value instanceof Date)) { + throw new TypeError('Invalid argument to Literal.fromDate()') + } + let d2 = function (x) { + return ('' + (100 + x)).slice(1, 3) + } + let date = '' + value.getUTCFullYear() + '-' + d2(value.getUTCMonth() + 1) + + '-' + d2(value.getUTCDate()) + 'T' + d2(value.getUTCHours()) + ':' + + d2(value.getUTCMinutes()) + ':' + d2(value.getUTCSeconds()) + 'Z' + return new Literal(date, null, XSD.dateTime) + } + + /** + * Builds a literal node from a number value + * @param value - The value + */ + static fromNumber(value: number): Literal { + if (typeof value !== 'number') { + throw new TypeError('Invalid argument to Literal.fromNumber()') + } + 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 + } else { + datatype = XSD.double + } + return new Literal(strValue, null, datatype) + } + + /** + * Builds a literal node from an input value + * @param value - The input value + */ + static fromValue(value: ValueType): T { + if (isLiteral(value)) { + return value as T + } + switch (typeof value) { + case 'object': + if (value instanceof Date) { + return Literal.fromDate(value) as T + } + case 'boolean': + return Literal.fromBoolean(value as boolean) as T + case 'number': + return Literal.fromNumber(value as number) as T + case 'string': + 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.js b/src/named-node.js deleted file mode 100644 index b806386ca..000000000 --- a/src/named-node.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict' -import ClassOrder from './class-order' -import Node from './node-internal' - -/** - * @class NamedNode - * @extends Node - */ -export default class NamedNode extends Node { - /** - * @constructor - * @param iri {String} - */ - constructor (iri) { - super() - this.termType = NamedNode.termType - - if (iri && iri.termType === NamedNode.termType) { // param is a named node - iri = iri.value - } - - if (!iri) { - throw new Error('Missing IRI for NamedNode') - } - - if (!iri.includes(':')) { - throw new Error('NamedNode IRI "' + iri + '" must be absolute.') - } - - if (iri.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. - */ - dir () { - var str = this.uri.split('#')[0] - var p = str.slice(0, -1).lastIndexOf('/') - var q = str.indexOf('//') - 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 - */ - site () { - var str = this.uri.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) - if (q < 0) { - return new NamedNode(str.slice(0) + '/') // Add slash to a bare origin - } else { - return new NamedNode(str.slice(0, q + 1)) - } - } - doc () { - if (this.uri.indexOf('#') < 0) { - return this - } else { - return new NamedNode(this.uri.split('#')[0]) - } - } - toString () { - return '<' + this.uri + '>' - } - - /* The local identifier with the document - */ - id () { - return this.uri.split('#')[1] - } - - /** - * Legacy getter and setter alias, node.uri - */ - get uri () { - return this.value - } - set uri (uri) { - this.value = uri - } - static fromValue (value) { - if (typeof value === 'undefined' || value === null) { - return value - } - const isNode = value && value.termType - if (isNode) { - return value - } - return new NamedNode(value) - } -} -NamedNode.termType = 'NamedNode' -NamedNode.prototype.classOrder = ClassOrder['NamedNode'] -NamedNode.prototype.isVar = 0 diff --git a/src/named-node.ts b/src/named-node.ts new file mode 100644 index 000000000..944047ba0 --- /dev/null +++ b/src/named-node.ts @@ -0,0 +1,113 @@ +import ClassOrder from './class-order' +import Node from './node-internal' +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 { + termType: typeof NamedNodeTermType = NamedNodeTermType + classOrder = ClassOrder.NamedNode + + /** + * Create a named (IRI) RDF Node + * @constructor + * @param iri - The IRI for this node + */ + constructor (iri: string) { + super(termValue(iri)) + + if (!this.value) { + throw new Error('Missing IRI for NamedNode') + } + + if (!this.value.includes(':')) { + throw new Error('NamedNode IRI "' + iri + '" must be absolute.') + } + + if (this.value.includes(' ')) { + var message = 'Error: NamedNode IRI "' + iri + '" must not contain unencoded spaces.' + throw new Error(message) + } + } + + /** + * Returns an $rdf node for the containing directory, ending in slash. + */ + dir (): NamedNode | null { + 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 + 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 + */ + 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)') + var q = str.indexOf('/', p+2) + if (q < 0) { + return new NamedNode(str.slice(0) + '/') // Add slash to a bare origin + } else { + return new NamedNode(str.slice(0, q + 1)) + } + } + + /** + * Creates the fetchable named node for the document. + * Removes everything from the # anchor tag. + */ + doc (): NamedNode { + if (this.value.indexOf('#') < 0) { + return this + } else { + return new NamedNode(this.value.split('#')[0]) + } + } + + /** + * Returns the URI including + */ + toString (): string { + return '<' + this.value + '>' + } + + /** The local identifier with the document */ + id (): string { + return this.value.split('#')[1] + } + + /** + * Legacy getter and setter alias, node.uri + * @deprecated use [[value]] + */ + get uri (): string { + return this.value + } + + set uri (uri: string) { + 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 + } + if (isTerm(value)) { + return value + } + return new NamedNode(value) + } +} 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..190e32ee6 --- /dev/null +++ b/src/namespace.ts @@ -0,0 +1,15 @@ +import RDFlibNamedNode from './named-node' +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?: RdfJsDataFactory): (ln: string) => NamedNode { + const dataFactory = factory || { namedNode: (value) => new RDFlibNamedNode(value) as NamedNode } + + return function (ln: string): NamedNode { + 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..2e6883cba --- /dev/null +++ b/src/node-internal.ts @@ -0,0 +1,117 @@ +import { ValueType, Bindings, FromValueReturns, TermType } from './types' +import { Term } from './tf-types' + +/** + * 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 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 */ + termType!: TermType; + + /** The class order for this node */ + classOrder!: number; + + /** The node's value */ + value: string; + + protected 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: Bindings): T { + console.log('@@@ node substitute' + this) + return this as unknown as T + } + + /** + * Compares this node with another + * @see {equals} to check if two nodes are equal + * @param other - The other node + */ + compareTerm (other: Node): 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: Term): 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: Node): 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 61% rename from src/node.js rename to src/node.ts index 1e16803b7..baeed84f6 100644 --- a/src/node.js +++ b/src/node.ts @@ -1,12 +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 from './collection' -import Literal from './literal' - -export default Node +import Namespace from './namespace' +import { isCollection, isLiteral } from './utils/terms' +import { Term } from './tf-types' /** * Creates an RDF Node from a native javascript value. @@ -16,30 +14,23 @@ 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; + +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: Term): Term | 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 (!isLiteral(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'))) { @@ -50,7 +41,6 @@ Node.toJS = function toJS (term) { 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/parse.js b/src/parse.js deleted file mode 100644 index ae6c47012..000000000 --- a/src/parse.js +++ /dev/null @@ -1,112 +0,0 @@ -import DataFactory from './data-factory' -import jsonldParser from './jsonldparser' -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' - -/** - * 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. - */ -export default function parse (str, kb, base, contentType, callback) { - contentType = contentType || 'text/turtle' - contentType = contentType.split(';')[0] - try { - if (contentType === 'text/n3' || contentType === 'text/turtle') { - var p = N3Parser(kb, kb, base, base, null, null, '', null) - p.loadBuf(str) - executeCallback() - } else if (contentType === 'application/rdf+xml') { - 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) - executeCallback() - } else if (contentType === 'text/html') { - parseRDFaDOM(Util.parseXML(str, {contentType: 'text/html'}), kb, base) - executeCallback() - } else if (contentType === 'application/sparql-update') { // @@ we handle a subset - sparqlUpdateParser(str, kb, base) - executeCallback() - } else if (contentType === 'application/ld+json') { - jsonldParser(str, kb, base, executeCallback) - } else if (contentType === 'application/nquads' || - contentType === 'application/n-quads') { - var n3Parser = new N3jsParser({ factory: DataFactory }) - nquadCallback(null, str) - } else { - throw new Error("Don't know how to parse " + contentType + ' yet') - } - } catch (e) { - executeErrorCallback(e) - } - - parse.handled = { - 'text/n3': true, - 'text/turtle': true, - 'application/rdf+xml': true, - 'application/xhtml+xml': true, - 'text/html': true, - 'application/sparql-update': true, - 'application/ld+json': true, - 'application/nquads' : true, - 'application/n-quads' : true - } - - function executeCallback () { - if (callback) { - callback(null, kb) - } else { - return - } - } - - function executeErrorCallback (e) { - if (contentType !== 'application/ld+json' || - contentType !== 'application/nquads' || - contentType !== 'application/n-quads') { - if (callback) { - callback(e, kb) - } else { - let e2 = new Error('' + e + ' while trying to parse <' + base + '> as ' + contentType) - e2.cause = e - throw e2 - } - } - } -/* - function setJsonLdBase (doc, base) { - if (doc instanceof Array) { - return - } - if (!('@context' in doc)) { - doc['@context'] = {} - } - doc['@context']['@base'] = base - } -*/ - function nquadCallback (err, nquads) { - if (err) { - callback(err, kb) - } - try { - n3Parser.parse(nquads, tripleCallback) - } catch (err) { - callback(err, kb) - } - } - - function tripleCallback (err, triple, prefixes) { - if (triple) { - kb.add(triple) - } else { - callback(err, kb) - } - } -} diff --git a/src/parse.ts b/src/parse.ts new file mode 100644 index 000000000..51ea41702 --- /dev/null +++ b/src/parse.ts @@ -0,0 +1,137 @@ +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 +import N3Parser from './n3parser' +import { parseRDFaDOM } from './rdfaparser' +import RDFParser from './rdfxmlparser' +import sparqlUpdateParser from './patch-parser' +import * as Util from './utils-js' +import Formula from './formula' +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 + +/** + * 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: string, + kb: Formula, + base: string, + contentType: string | ContentType, + callback?: CallbackFunc +) { + contentType = contentType || TurtleContentType + contentType = contentType.split(';')[0] as ContentType + try { + if (contentType === N3ContentType || contentType === TurtleContentType) { + var p = N3Parser(kb, kb, base, base, null, null, '', null) + p.loadBuf(str) + executeCallback() + } else if (contentType === RDFXMLContentType) { + var parser = new RDFParser(kb) + parser.parse(Util.parseXML(str), base, kb.sym(base)) + executeCallback() + } else if (contentType === XHTMLContentType) { + parseRDFaDOM(Util.parseXML(str, {contentType: XHTMLContentType}), kb, base) + executeCallback() + } else if (contentType === HTMLContentType) { + parseRDFaDOM(Util.parseXML(str, {contentType: HTMLContentType}), kb, base) + executeCallback() + } else if (contentType === SPARQLUpdateContentType) { // @@ we handle a subset + sparqlUpdateParser(str, kb, base) + executeCallback() + } else if (contentType === JSONLDContentType) { + jsonldParser(str, kb, base, executeCallback) + } else if (contentType === NQuadsContentType || + contentType === NQuadsAltContentType) { + 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') + } + } catch (e) { + executeErrorCallback(e) + } + + (parse as any).handled= { + 'text/n3': true, + 'text/turtle': true, + 'application/rdf+xml': true, + 'application/xhtml+xml': true, + 'text/html': true, + 'application/sparql-update': true, + 'application/ld+json': true, + 'application/nquads' : true, + 'application/n-quads' : true + } + + function executeCallback () { + if (callback) { + callback(null, kb) + } else { + return + } + } + + function executeErrorCallback (e: Error): void { + if ( + // TODO: Always true, what is the right behavior + contentType !== JSONLDContentType || + // @ts-ignore always true? + contentType !== NQuadsContentType || + // @ts-ignore always true? + contentType !== NQuadsAltContentType + ) { + 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 + } + } + } +/* + function setJsonLdBase (doc, base) { + if (doc instanceof Array) { + return + } + if (!('@context' in doc)) { + doc['@context'] = {} + } + doc['@context']['@base'] = base + } +*/ + function nquadCallback (err?: Error | null, nquads?: string): void { + if (err) { + (callback as CallbackFunc)(err, kb) + } + try { + n3Parser.parse(nquads, tripleCallback) + } catch (err) { + (callback as CallbackFunc)(err, kb) + } + } + + function tripleCallback (err: Error, triple: Quad) { + if (triple) { + kb.add(triple.subject, triple.predicate, triple.object, triple.graph) + } else { + (callback as CallbackFunc)(err, kb) + } + } +} 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/rdfaparser.js b/src/rdfaparser.js index ef3e4763b..1c1989742 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 * as Util from './utils-js' +import rdf from './factories/canonical-data-factory' if (typeof Node === 'undefined') { // @@@@@@ Global. Interface to xmldom. var Node = { diff --git a/src/serialize.js b/src/serialize.js deleted file mode 100644 index ea48d8879..000000000 --- a/src/serialize.js +++ /dev/null @@ -1,70 +0,0 @@ -import * as convert from './convert' -import Serializer from './serializer' - -/** - * 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 - 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) - sz.setBase(base) - switch (contentType) { - case 'application/rdf+xml': - documentString = sz.statementsToXML(newSts) - return executeCallback(null, documentString) - case 'text/n3': - case 'application/n3': // Legacy - documentString = sz.statementsToN3(newSts) - return executeCallback(null, documentString) - case 'text/turtle': - case 'application/x-turtle': // Legacy - sz.setFlags('si') // Suppress = for sameAs and => for implies - documentString = sz.statementsToN3(newSts) - return executeCallback(null, documentString) - case 'application/n-triples': - sz.setFlags('deinprstux') // Suppress nice parts of N3 to make ntriples - documentString = sz.statementsToNTriples(newSts) - return executeCallback(null, documentString) - case 'application/ld+json': - 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 - 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 - default: - throw new Error('Serialize: Content-type ' + contentType + ' not supported for data write.') - } - } catch (err) { - if (callback) { - return callback(err) - } - throw err // Don't hide problems from caller in sync mode - } - - function executeCallback (err, result) { - if (callback) { - callback(err, result) - return - } else { - return result - } - } -} diff --git a/src/serialize.ts b/src/serialize.ts new file mode 100644 index 000000000..768474ca0 --- /dev/null +++ b/src/serialize.ts @@ -0,0 +1,102 @@ +import * as convert from './convert' +import Formula from './formula' +import Serializer from './serializer' +import { + ContentType, + JSONLDContentType, + N3ContentType, + N3LegacyContentType, + NQuadsAltContentType, + NQuadsContentType, + NTriplesContentType, + RDFXMLContentType, + TurtleContentType, + TurtleLegacyContentType, +} from './types' +import IndexedFormula from './store' +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 | NamedNode | BlankNode, + /** The store */ + kb?: IndexedFormula, + base?: unknown, + /** + * The mime type. + * Defaults to Turtle. + */ + contentType?: string | ContentType, + callback?: (err: Error | undefined | null, result?: string | null) => 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 || TurtleContentType // text/n3 if complex? + var documentString: string | null = null + 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 NamedNode) + var n3String: string + sz.suggestNamespaces(kb!.namespaces) + sz.setBase(base) + switch (contentType) { + case RDFXMLContentType: + documentString = sz.statementsToXML(newSts) + return executeCallback(null, documentString) + case N3ContentType: + case N3LegacyContentType: + documentString = sz.statementsToN3(newSts) + return executeCallback(null, documentString) + case TurtleContentType: + case TurtleLegacyContentType: + sz.setFlags('si') // Suppress = for sameAs and => for implies + documentString = sz.statementsToN3(newSts) + return executeCallback(null, documentString) + case NTriplesContentType: + sz.setFlags('deinprstux') // Suppress nice parts of N3 to make ntriples + documentString = sz.statementsToNTriples(newSts) + return executeCallback(null, documentString) + 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 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) + // n3String = sz.statementsToN3(newSts) + // documentString = convert.convertToNQuads(n3String, callback) + // break + default: + throw new Error('Serialize: Content-type ' + contentType + ' not supported for data write.') + } + } catch (err) { + if (callback) { + return callback(err, undefined) + } + throw err // Don't hide problems from caller in sync mode + } + + function executeCallback (err: Error | null | undefined, result: string | null | undefined): string | undefined { + if (callback) { + callback(err, result) + return + } else { + return result as string + } + } +} diff --git a/src/serializer.js b/src/serializer.js index 4356fb73b..003ae465e 100644 --- a/src/serializer.js +++ b/src/serializer.js @@ -8,8 +8,8 @@ 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 * as Util from './utils-js' +import CanonicalDataFactory from './factories/canonical-data-factory' import { createXSD } from './xsd' export default (function () { 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..fea65cd25 --- /dev/null +++ b/src/statement.ts @@ -0,0 +1,132 @@ +import Node from './node-internal' +import { + Bindings, + GraphType, + ObjectType, + PredicateType, + SubjectType, + DefaultGraphTermType, +} from './types' +import { defaultGraphNode } from './utils/default-graph-uri' +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 { + /** The subject of the triple. What the Statement is about. */ + subject: SubjectType + + /** The relationship which is asserted between the subject and object */ + predicate: PredicateType + + /** The thing or data value which is asserted to be related to the subject */ + object: ObjectType + + /** + * The graph param is a named node of the document in which the triple when + * it is stored on the web. + */ + graph: GraphType + + /** + * 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: Quad_Subject | Term, + predicate: Quad_Predicate | Term, + object: Quad_Object | Term, + graph?: Quad_Graph | Term, + ) { + this.subject = Node.fromValue(subject) + this.predicate = Node.fromValue(predicate) + this.object = Node.fromValue(object) + this.graph = graph == undefined ? defaultGraphNode : Node.fromValue(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: Quad): 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: 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 !== DefaultGraphTermType) { + 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() + } +} diff --git a/src/store.js b/src/store.ts similarity index 58% rename from src/store.js rename to src/store.ts index acbc2a6e5..d3c57247a 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 * * */ @@ -17,22 +18,54 @@ /** @module store */ 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 Statement from './statement' +import { defaultGraphURI } from './factories/canonical-data-factory' +import Formula, { FormulaOpts } from './formula' +import { ArrayIndexOf } from './utils' +import { RDFArrayRemove } from './utils-js' +import { + isRDFlibObject, + isStore, + isGraph, + isPredicate, + isQuad, + isSubject +} from './utils/terms' import Node from './node' import Variable from './variable' import { Query, indexedFormulaQuery } from './query' +import UpdateManager from './update-manager' +import { + Bindings, +} from './types' +import Statement from './statement' +import { Indexable } from './factories/factory-types' +import NamedNode from './named-node' +import Fetcher from './fetcher' +import { + BlankNode, + Quad_Graph, + NamedNode as TFNamedNode, + Quad_Object, + Quad_Predicate, + Quad, + Quad_Subject, + Term +} from './tf-types' 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: Quad_Subject, + pred: Quad_Predicate, + obj: Quad_Object +): boolean { var o1 = formula.any(subj, pred, undefined) if (!o1) { return false // First time with this value @@ -43,7 +76,12 @@ function handleFP (formula, subj, pred, obj) { } // handleFP // Handle Inverse Functional Property -function handleIFP (formula, subj, pred, obj) { +function handleIFP ( + formula: IndexedFormula, + subj: Quad_Subject, + pred: Quad_Predicate, + obj: Quad_Object +): boolean { var s1 = formula.any(undefined, pred, obj) if (!s1) { return false // First time with this value @@ -53,9 +91,16 @@ function handleIFP (formula, subj, pred, obj) { return true } // handleIFP -function handleRDFType (formula, subj, pred, obj, why) { +function handleRDFType ( + formula: IndexedFormula, + subj: Quad_Subject, + pred: Quad_Predicate, + obj: Quad_Object, + why: Quad_Graph +) { + //@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)] @@ -67,32 +112,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: Quad[] + /** Array of statements with this X as subject */ + subjectIndex: Quad[] + /** Array of statements with this X as predicate */ + predicateIndex: Quad[] + /** Array of statements with this X as object */ + objectIndex: Quad[] + /** Array of statements with X as provenance */ + whyIndex: Quad[] + index: [ + Quad[], + Quad[], + Quad[], + Quad[] + ] + features: FeaturesType + static handleRDFType: Function + _universalVariables?: TFNamedNode[] + _existentialVariables?: BlankNode[] + + /** Function to remove quads from the store arrays with */ + private rdfArrayRemove: (arr: Quad[], q: Quad) => void + /** Callbacks which are triggered after a statement has been added to the store */ + private dataCallbacks?: Array<(q: Quad) => 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, @@ -113,41 +202,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: Quad) { + 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: Quad) => 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: (errorString?: string) => void) { 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: 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) @@ -163,7 +279,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: Quad) { targetKB.remove(st) }) } @@ -172,9 +288,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: Quad) { + st.graph = target + targetKB.add(st.subject, st.predicate, st.object, st.graph) }) } onDonePatch() @@ -184,13 +300,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)) }, @@ -210,13 +329,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: BlankNode): BlankNode { 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 ] @@ -249,48 +376,72 @@ 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: Quad): 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: Quad_Subject | Quad | Quad[] | Statement | Statement[], + pred?: Quad_Predicate, + obj?: Term, + why?: Quad_Graph + ): 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 (isStatement(subj)) { - this.add(subj.subject, subj.predicate, subj.object, subj.why) + } else if (isQuad(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: Quad 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 (!isSubject(subj)) { + throw new Error('Subject is not a subject type') + } + if (!isPredicate(pred)) { + throw new Error(`Predicate ${pred} is not a predicate type`) + } + if (!isRDFlibObject(obj)) { + throw new Error(`Object ${obj} is not an object type`) + } + if (!isGraph(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 - var predHash = this.id(this.canon(pred)) + var predHash = this.id(this.canon(pred!)) actions = this.propertyActions[predHash] // Predicate hash var done = false if (actions) { @@ -313,6 +464,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass this.id(this.canon(obj)), this.id(this.canon(why)) ] + // @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] @@ -337,8 +489,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: Term): Term { if (!term) { return term } @@ -349,12 +502,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) } } @@ -362,22 +520,29 @@ 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: Quad 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: Quad) { 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 } } @@ -394,21 +559,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: Term, u2: Term): 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 } @@ -424,13 +592,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: Quad_Subject, + target: Quad_Subject, + flags?: Array<('two-direction' | 'delete')> + ): void { if (!flags) flags = [] var statList = this.statementsMatching(template) if (ArrayIndexOf(flags, 'two-direction') !== -1) { @@ -444,7 +618,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) { @@ -454,15 +630,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: 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 - u1 = this.canon(u1) - u2 = this.canon(u2) + 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} @@ -476,7 +654,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) } @@ -490,21 +674,25 @@ export default class IndexedFormula extends Formula { // IN future - allow pass * ``` * @returns {Number} */ - get length () { + get length (): number { return this.statements.length } /** * 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()` - * @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?: Quad_Subject | null, + predicate?: Quad_Predicate | null, + object?: Quad_Object | null, + graph?: Quad_Graph | null + ): Quad[] { return this.statementsMatching( Node.fromValue(subject), Node.fromValue(predicate), @@ -515,22 +703,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): Term { 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: Quad_Predicate, + action: ( + store: IndexedFormula, + subject: Quad_Subject, + predicate: Quad_Predicate, + object: Quad_Object + ) => boolean + ): boolean { // log.debug("newPropertyAction: "+pred) var hash = this.id(pred) if (!this.propertyActions[hash]) { @@ -546,8 +752,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) @@ -555,39 +765,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) @@ -598,9 +824,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: Quad | Quad[]): IndexedFormula { if (st instanceof Array) { for (var i = 0; i < st.length; i++) { this.remove(st[i]) @@ -610,8 +837,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) } @@ -621,9 +847,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: 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]) } @@ -631,37 +858,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?: 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) 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: 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]) } - 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?: Quad_Subject | null, + predicate?: Quad_Predicate | null, + object?: Quad_Object | null, + graph?: Quad_Graph | 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: Quad): 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) @@ -675,7 +926,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]) } @@ -685,7 +940,7 @@ export default class IndexedFormula extends Formula { // IN future - allow pass /** * Replace big with small, obsoleted with obsoleting. */ - replaceWith (big, small) { + 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) @@ -707,7 +962,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] = [] @@ -719,7 +974,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) @@ -733,8 +988,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 @@ -742,8 +998,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 } @@ -753,10 +1011,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 @@ -772,21 +1030,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?: Quad_Subject | null, + pred?: Quad_Predicate | null, + obj?: Quad_Object | null, + why?: Quad_Graph | null, + justOne?: boolean + ): Quad[] { // 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: Term[] = [] + 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])) @@ -830,12 +1094,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: Quad[] = this.index[pBest][hash[pBest]] var check = given.slice(0, iBest).concat(given.slice(iBest + 1)) // remove iBest - var results = [] + var results: Quad[] = [] var parts = [ 'subject', 'predicate', 'object', 'why' ] for (var j = 0; j < possibles.length; j++) { - var st = possibles[j] + var st: Quad | null = possibles[j] for (i = 0; i < check.length; i++) { // for each position to be checked p = check[i] @@ -853,13 +1117,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: Quad_Subject): 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/tf-types.ts b/src/tf-types.ts new file mode 100644 index 000000000..51ce2d661 --- /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 spec Term + * @link https://rdf.js.org/data-model-spec/#term-interface + */ +export interface Term { + 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: Term): boolean +} + +/** + * RDF/JS spec NamedNode + * @link https://rdf.js.org/data-model-spec/#namednode-interface + */ +export interface NamedNode extends Term { + termType: typeof NamedNodeTermType + value: string +} + +/** + * RDF/JS spec Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface BlankNode extends Term { + termType: typeof BlankNodeTermType + value: string +} + +/** + * RDF/JS spec Quad + * @link https://rdf.js.org/data-model-spec/#quad-interface + */ +export interface Quad< + S extends Term = Quad_Subject, + P extends Term = Quad_Predicate, + O extends Term = Quad_Object, + G extends Term = Quad_Graph +> { + subject: S + predicate: P + object: O + graph: G +} + +/** + * RDF/JS spec Literal + * @link https://rdf.js.org/data-model-spec/#literal-interface + */ +export interface Literal extends Term { + /** Contains the constant "Literal". */ + termType: typeof 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: NamedNode +} + +/** + * RDF/JS spec Variable + * @link https://rdf.js.org/data-model-spec/#variable-interface + */ +export interface Variable extends Term { + /** Contains the constant "Variable". */ + termType: typeof VariableTermType + /** The name of the variable without leading "?" (example: "a"). */ + value: string +} + +/** + * 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 + */ +export interface DefaultGraph extends Term { + termType: typeof DefaultGraphTermType; + /** should return and empty string'' */ + value: string; +} + +/** + * RDF/JS spec DataFactory + * + * Not 100% compliant due to to practicality problems. + * + * @link https://rdf.js.org/data-model-spec/#datafactory-interface + */ +export interface RdfJsDataFactory { + /** Returns a new instance of NamedNode. */ + 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) => 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 | NamedNode) => Literal, + + /** Returns a new instance of Variable. This method is optional. */ + variable?: (value: string) => Variable, + + /** + * Returns an instance of DefaultGraph. + */ + defaultGraph: () => DefaultGraph | NamedNode | BlankNode, + + /** + * 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: Term) => Term + + /** + * Returns a new instance of Quad, such that newObject.equals(original) returns true. + * Not implemented in RDFJS, so optional. + */ + 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: Term, + predicate: Term, + object: Term, + graph?: Term, + ) => Quad + + /** + * Check for specific features/behaviour on the factory. + * + * This does not exist on the original RDF/JS spec + */ + supports: SupportTable +} + +/** A RDF/JS spec Subject */ +export type Quad_Subject = NamedNode | BlankNode | Variable +/** A RDF/JS spec Predicate */ +export type Quad_Predicate = NamedNode | Variable +/** A RDF/JS spec Object */ +export type Quad_Object = NamedNode | BlankNode | Literal | Variable +/** A RDF/JS spec Graph */ +export type Quad_Graph = NamedNode | DefaultGraph | BlankNode | Variable diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..813f9e484 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,107 @@ +import Node from './node-internal' +import RDFlibVariable from './variable' +import RDFlibBlankNode from './blank-node' +import Collection from './collection' +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 { NamedNode, Term } from './tf-types' + +export const NamedNodeTermType = "NamedNode" as const +export const BlankNodeTermType = "BlankNode" as const +export const LiteralTermType = "Literal" as const +export const VariableTermType = "Variable" as const +export const DefaultGraphTermType = "DefaultGraph" as const +// Non-RDF/JS types: +export const CollectionTermType = "Collection" as const +export const EmptyTermType = "Empty" as const +export const GraphTermType = "Graph" as const + +export type TermType = typeof NamedNodeTermType + | typeof BlankNodeTermType + | typeof LiteralTermType + | typeof VariableTermType + | typeof DefaultGraphTermType + | typeof CollectionTermType + | 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 + */ +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 + +/** + * In this project, there exist two types for the same kind of RDF concept. + * 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. + * Variables are missing, and the statement requires specific types of terms (e.g. NamedNode instead of Term). + */ + +/** An RDF/JS Subject */ +export type SubjectType = RDFlibBlankNode | RDFlibNamedNode | RDFlibVariable +/** An RDF/JS Predicate */ +export type PredicateType = RDFlibNamedNode | RDFlibVariable +/** An RDF/JS Object */ +export type ObjectType = RDFlibNamedNode | RDFlibLiteral | Collection | RDFlibBlankNode | RDFlibVariable | Empty +/** An RDF/JS Graph */ +export type GraphType = RDFlibDefaultGraph | RDFlibNamedNode | RDFlibVariable // | Formula + +export interface Bindings { + [id: string]: Term; +} + +/** All the types that a .fromValue() method might return */ +export type FromValueReturns = Term | undefined | null | Collection + +export interface IRDFlibDataFactory extends DataFactory< + RDFlibNamedNode | RDFlibBlankNode | RDFlibLiteral | Collection | Statement +> { + fetcher: (store: IndexedFormula, options: any) => Fetcher + graph: (features, opts) => IndexedFormula + lit: (val: string, lang?: string, dt?: NamedNode) => RDFlibLiteral + st: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType, + graph?: GraphType + ) => Statement + triple: ( + subject: SubjectType, + predicate: PredicateType, + object: ObjectType + ) => Statement +} 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 294c19057..76146bc5b 100644 --- a/src/update-manager.js +++ b/src/update-manager.ts @@ -7,34 +7,63 @@ 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 './util' -import * as Util from './util' +import { isStore, isBlankNode } from './utils/terms' +import * as Util from './utils-js' +import Statement from './statement' +import RDFlibNamedNode from './named-node' +import { termValue } from './utils/termValue' +import { + BlankNode, + NamedNode, + Quad_Graph, + Quad_Object, + Quad_Predicate, + Quad_Subject, + Quad, + Term, +} from './tf-types' + +interface UpdateManagerFormula extends IndexedFormula { + fetcher: Fetcher +} + +type CallBackFunction = (uri: string, ok: boolean, message: string, response: Error | Response) => {} | void -/** Update Manager -* -* The update manager is a helper object for a store. +/** +* 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 +77,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: NamedNode) { + if (!this.patchControl[doc.value]) { + this.patchControl[doc.value] = [] } - return this.patchControl[doc.uri] + return this.patchControl[doc.value] } /** @@ -64,26 +93,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 | NamedNode, 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 +117,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 +125,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 +137,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 Quad_Subject if (request !== undefined) { var wacAllow = kb.anyValue(response, this.ns.httph('wac-allow')) if (wacAllow) { @@ -151,6 +178,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 +207,7 @@ export default class UpdateManager { : obj.toNT() } - anonymizeNT (stmt) { + anonymizeNT (stmt: Quad) { return this.anonymize(stmt.subject) + ' ' + this.anonymize(stmt.predicate) + ' ' + this.anonymize(stmt.object) + ' .' @@ -189,23 +217,23 @@ export default class UpdateManager { * Returns a list of all bnodes occurring in a statement * @private */ - statementBnodes (st) { + statementBnodes (st: Quad): BlankNode[] { return [st.subject, st.predicate, st.object].filter(function (x) { - return x.isBlank - }) + return isBlankNode(x) + }) as BlankNode[] } /** * Returns a list of all bnodes occurring in a list of statements * @private */ - statementArrayBnodes (sts) { - var bnodes = [] + statementArrayBnodes (sts: Quad[]) { + 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 = [] + var bnodes2: BlankNode[] = [] for (let j = 0; j < bnodes.length; j++) { if (j === 0 || !bnodes[j].equals(bnodes[j - 1])) { bnodes2.push(bnodes[j]) @@ -223,12 +251,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 +272,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 +288,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 +324,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 +349,9 @@ export default class UpdateManager { * Returns the best context for a single statement * @private */ - statementContext (st) { + statementContext (st: Quad) { var bnodes = this.statementBnodes(st) - return this.bnodeContext(bnodes, st.why) + return this.bnodeContext(bnodes, st.graph) } /** @@ -342,7 +370,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 +401,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 +416,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: Quad) { + 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 +432,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 [Quad_Subject, Quad_Predicate, Quad_Object, Quad_Graph])[3].value, query, callbackFunction) } } } - insert_statement (st, callbackFunction) { + insert_statement (st: Quad, callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -424,10 +459,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: Quad | Quad[], callbackFunction: CallBackFunction): void { var st0 = st instanceof Array ? st[0] : st var query = this.contextWhere(this.statementContext(st0)) @@ -442,7 +477,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 +491,7 @@ export default class UpdateManager { * @param doc * @param action */ - requestDownstreamAction (doc, action) { + requestDownstreamAction (doc: NamedNode, action): void { var control = this.patchControlFor(doc) if (!control.pendingUpstream) { action(doc) @@ -475,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) { + clearUpstreamCount (doc: NamedNode): void { var control = this.patchControlFor(doc) control.upstreamCount = 0 } - getUpdatesVia (doc) { + 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, listener) { + addDownstreamChangeListener (doc: NamedNode, listener): void { var control = this.patchControlFor(doc) if (!control.downstreamChangeListeners) { control.downstreamChangeListeners = [] } control.downstreamChangeListeners.push(listener) - this.setRefreshHandler(doc, (doc) => { + this.setRefreshHandler(doc, (doc: NamedNode) => { this.reloadAndSync(doc) }) } - reloadAndSync (doc) { + reloadAndSync (doc: NamedNode): void { var control = this.patchControlFor(doc) var updater = this @@ -524,14 +559,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 +592,8 @@ export default class UpdateManager { * * @returns {boolean} */ - setRefreshHandler (doc, handler) { - var wssURI = this.getUpdatesVia(doc) // relative + setRefreshHandler (doc: NamedNode, handler): boolean { + let wssURI = this.getUpdatesVia(doc) // relative // var kb = this.store var theHandler = handler var self = this @@ -571,19 +606,17 @@ 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) - } else if (typeof Services !== 'undefined') { // Firefox add on http://stackoverflow.com/questions/24244886/is-websocket-supported-in-firefox-for-android-addons - socket = (Services.wm.getMostRecentWindow('navigator:browser').WebSocket)(wssURI) + socket = new WebSocket(validWssURI) } 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 +624,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 +633,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 +650,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 +661,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 +681,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 +729,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 +744,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: Quad) { + 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 +757,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 +765,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: BlankNode[] = [] if (ds.length) bnodes = this.statementArrayBnodes(ds) if (is.length) bnodes = bnodes.concat(this.statementArrayBnodes(is)) var context = this.bnodeContext(bnodes, doc) @@ -784,11 +819,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 +836,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 +846,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 +863,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: Quad_Subject, + 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 +881,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 NamedNode, this.ns.link('response')) as Quad_Subject 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 Term).value // prepare contents of revised document let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy! @@ -856,7 +896,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 +924,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 +939,7 @@ export default class UpdateManager { * @param is * @param callbackFunction */ - updateLocalFile (doc, ds, is, callbackFunction) { + 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 @@ -913,29 +953,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 +985,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 +1003,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 | Quad[], contentType: string): string { const kb = this.store let documentString @@ -1003,35 +1043,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: RDFlibNamedNode, + data: string | Quad[], + 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 +1075,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) }) } @@ -1055,20 +1091,30 @@ export default class UpdateManager { * document in the meantime. * * @param kb - * @param doc {NamedNode} + * @param doc {RDFlibNamedNode} * @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 +1135,8 @@ export default class UpdateManager { }) } } + +interface docReloadType extends NamedNode { + reloadTimeCount?: number + reloadTimeTotal?: number +} diff --git a/src/updates-via.js b/src/updates-via.js index 981f54f1f..f56da7b1c 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 './factories/rdflib-data-factory' export class UpdatesSocket { constructor (parent, via) { diff --git a/src/uri.js b/src/uri.ts similarity index 74% rename from src/uri.js rename to src/uri.ts index 40fe7923b..324f13b8b 100644 --- a/src/uri.js +++ b/src/uri.ts @@ -10,10 +10,14 @@ */ var alert = alert || console.log -import NamedNode from './named-node' +import RDFlibNamedNode 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): RDFlibNamedNode { + return new RDFlibNamedNode(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/utils-js.js similarity index 73% rename from src/util.js rename to src/utils-js.js index 0550963ec..eb9b543cb 100644 --- a/src/util.js +++ b/src/utils-js.js @@ -20,136 +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', -] - -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). - */ -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..c5eac100e --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,114 @@ +import Fetcher from './fetcher' +import log from './log' +import { docpart } from './uri' +import { string_startswith } from './utils-js' +import { RdfJsDataFactory, Quad, Quad_Subject, Term } from './tf-types' + +/** RDF/JS spec 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: RdfJsDataFactory, + subject: Quad_Subject, + data: Term[] +): Quad[] { + const statements: Quad[] = [] + + 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: 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/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..43d5630cf --- /dev/null +++ b/src/utils/termValue.ts @@ -0,0 +1,10 @@ +import { Term } from '../tf-types' + +/** Retrieve the value of a term, or self if already a string. */ +export function termValue (node: Term | string): string { + if (typeof node === 'string') { + return node + } + + return node.value +} diff --git a/src/utils/terms.ts b/src/utils/terms.ts new file mode 100644 index 000000000..b51871747 --- /dev/null +++ b/src/utils/terms.ts @@ -0,0 +1,119 @@ +import { + ObjectType, CollectionTermType, NamedNodeTermType, VariableTermType, BlankNodeTermType, LiteralTermType, DefaultGraphTermType, +} from '../types' +import Collection from '../collection' +import IndexedFormula from '../store' +import Statement from '../statement' +import { + BlankNode, + Quad_Graph, + Literal, + NamedNode, + Quad_Object, + Quad_Predicate, + Quad, + Quad_Subject, + Term, + Variable, +} 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 RDFlib Stores */ +export function isStore(obj): obj is IndexedFormula { + return typeof obj === 'object' && obj !== null && 'statements' in obj +} + +/** TypeGuard for RDFLib Collections */ +export function isCollection(obj: any): obj is Collection { + return isTerm(obj) + && (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 === 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 === VariableTermType +} + +/** 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 spec Literals */ +export function isLiteral(value: any): value is Literal { + return (value as Term).termType === LiteralTermType +} + +/** TypeGuard for RDF/JS spec Quads */ +export function isQuad(obj: any): obj is Quad { + return typeof obj === "object" && obj !== null && ( + 'subject' in obj + && 'predicate' in obj + && 'object' in obj + ) +} + +/** TypeGuard for RDF/JS spec NamedNodes */ +export function isNamedNode(obj: any): obj is NamedNode { + return isTerm(obj) && obj.termType === 'NamedNode' +} + +/** 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 RDF/JS spec Subject types */ +export function isSubject(obj: any): obj is Quad_Subject { + return isTerm(obj) && ( + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType + ) +} + +/** TypeGuard for valid RDF/JS spec Predicate types */ +export function isPredicate(obj: any): obj is Quad_Predicate { + return isTerm(obj) && ( + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType + ) +} + +/** TypeGuard for valid RDF/JS spec Object types */ +export function isRDFObject(obj: any): obj is Quad_Object { + return isTerm(obj) && ( + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType || + obj.termType === LiteralTermType + ) +} + +/** TypeGuard for valid RDF/JS Graph types */ +export function isGraph(obj: any): obj is Quad_Graph { + return isTerm(obj) && ( + obj.termType === NamedNodeTermType || + obj.termType === VariableTermType || + obj.termType === BlankNodeTermType || + obj.termType === DefaultGraphTermType + ) +} diff --git a/src/variable.js b/src/variable.js deleted file mode 100644 index 65d199e1f..000000000 --- a/src/variable.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict' -import ClassOrder from './class-order' -import Node from './node' -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 { - constructor (name = '') { - super() - this.termType = Variable.termType - this.value = name - this.base = 'varid:' - 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 - } -} - -Variable.termType = 'Variable' -Variable.prototype.classOrder = ClassOrder['Variable'] -Variable.prototype.isVar = 1 diff --git a/src/variable.ts b/src/variable.ts new file mode 100644 index 000000000..03de55c54 --- /dev/null +++ b/src/variable.ts @@ -0,0 +1,62 @@ +import ClassOrder from './class-order' +import Node from './node-internal' +import { VariableTermType } from './types' +import * as Uri from './uri' +import { Variable as TFVariable } from './tf-types' + +/** +* 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 { + termType: typeof VariableTermType = VariableTermType + + /** The base string for a variable's name */ + base = 'varid:' + classOrder = ClassOrder.Variable + isVar = 1 + /** The unique identifier of this variable */ + uri: string + + /** + * Initializes this variable + * @param name The variable's name + */ + constructor (name = '') { + super(name) + this.base = 'varid:' + 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}` + } +} 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..d0cc69818 --- /dev/null +++ b/tests/unit/factories/canonical-data-factory-test.ts @@ -0,0 +1,65 @@ +import { expect } from 'chai' + +import Factory from '../../../src/factories/canonical-data-factory'; +import { Feature } from '../../../src/factories/factory-types' +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', () => { + 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('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..b886b2cf9 --- /dev/null +++ b/tests/unit/factories/extended-term-factory-test.ts @@ -0,0 +1,35 @@ +import { expect } from 'chai' + +import Factory from '../../../src/factories/extended-term-factory'; +import { Feature } from '../../../src/factories/factory-types' +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', () => { + 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', () => { + 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-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 182c0942b..f9b66dfe9 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) @@ -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() }) }) @@ -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/indexed-formula-test.js b/tests/unit/indexed-formula-test.js index 0bc02ca39..d95d1cf77 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 DataFactory from '../../src/data-factory' -import { RDFArrayRemove } from '../../src/util' +import { RDFArrayRemove } from '../../src/utils-js' +import DataFactory from '../../src/factories/rdflib-data-factory' describe('IndexedFormula', () => { const g0 = NamedNode.fromValue('https://example.com/graph0') 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 9ffb399bd..6cacdc9fe 100644 --- a/tests/unit/parse-test.js +++ b/tests/unit/parse-test.js @@ -2,10 +2,9 @@ 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 CanonicalDataFactory from '../../src/factories/canonical-data-factory' import defaultXSD from '../../src/xsd' +import DataFactory from '../../src/factories/rdflib-data-factory' describe('Parse', () => { describe('ttl', () => { @@ -114,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) @@ -208,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 new file mode 100644 index 000000000..e76ce8160 --- /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 => ({}) 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 697ead171..b51bc73e5 100644 --- a/tests/unit/util-test.js +++ b/tests/unit/util-test.js @@ -1,32 +1,57 @@ /* 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' -import { arrayToStatements, isNamedNode, isStatement, isTerm } from '../../src/util' +import { arrayToStatements } from '../../src/utils' +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', () => { @@ -43,16 +68,185 @@ 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() + }) + + it('handles prototype based objects', () => { + const proto = { + termType: 'NamedNode' + } + const obj = Object.create(proto) + obj.value = '' + expect(isNamedNode(obj)).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 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() + expect(isVariable(undefined)).to.be.false() + expect(isVariable(2)).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', () => { diff --git a/tsconfig.json b/tsconfig.json index 4b78c20c4..485ae3d98 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,13 +3,16 @@ "allowSyntheticDefaultImports": true, "checkJs": false, "allowJs": true, - "target": "es6", - "module": "es6", + "target": "es2019", + "module": "esnext", "strict": true, "noImplicitAny": false, + "noUnusedLocals": true, "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/webpack.config.js b/webpack.config.js index 5198dab44..5d76939a2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -26,6 +26,7 @@ module.exports = (env, args) => { } ] }, + resolve: { extensions: ['.js', '.ts'] }, plugins: [ new WrapperPlugin({ // Fall back to window.fetch when solid-auth-client is not present,