@inrupt/solid-client
Version:
Make your web apps work with Solid Pods.
295 lines (292 loc) • 11.8 kB
JavaScript
import { v4 } from 'uuid';
import { internal_isValidUrl, resolveLocalIri, isNamedNode } from '../datatypes.mjs';
import { hasServerResourceInfo, SolidClientError } from '../interfaces.mjs';
import { subjectToRdfJsQuads } from '../rdfjs.internal.mjs';
import { getSourceUrl } from '../resource/resource.mjs';
import { internal_addAdditionsToChangeLog, internal_addDeletionsToChangeLog, internal_getReadableValue } from './thing.internal.mjs';
import { isLocalNodeIri, getLocalNodeName, isBlankNodeId, freeze, getLocalNodeIri } from '../rdf.internal.mjs';
import { internal_toIriString } from '../interfaces.internal.mjs';
import { getTermAll } from './get.mjs';
import { DataFactory } from 'n3';
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/**
* Extract Quads with a given Subject from a [[SolidDataset]] into a [[Thing]].
*
* @param solidDataset The [[SolidDataset]] to extract the [[Thing]] from.
* @param thingUrl The identifier of the desired [[Thing]] (URL or Blank Node identifier).
* @param options Not yet implemented.
*/
function getThing(solidDataset, thingUrl, options = {}) {
var _a;
if (!internal_isValidUrl(thingUrl) && !thingUrl.match(/^_:/)) {
throw new ValidThingUrlExpectedError(thingUrl);
}
const graph = typeof options.scope !== "undefined"
? internal_toIriString(options.scope)
: "default";
const thingsByIri = (_a = solidDataset.graphs[graph]) !== null && _a !== void 0 ? _a : {};
const thingIri = internal_toIriString(thingUrl);
const resolvedThingIri = isLocalNodeIri(thingIri) && hasServerResourceInfo(solidDataset)
? resolveLocalIri(getLocalNodeName(thingIri), getSourceUrl(solidDataset))
: thingIri;
const thing = thingsByIri[resolvedThingIri];
if (typeof thing === "undefined") {
return null;
}
return thing;
}
/**
* Get all [[Thing]]s in a [[SolidDataset]].
*
* @param solidDataset The [[SolidDataset]] to extract the [[Thing]]s from.
* @param options Not yet implemented.
*/
function getThingAll(solidDataset, options = { acceptBlankNodes: false }) {
var _a;
const graph = typeof options.scope !== "undefined"
? internal_toIriString(options.scope)
: "default";
const thingsByIri = (_a = solidDataset.graphs[graph]) !== null && _a !== void 0 ? _a : {};
return Object.values(thingsByIri).filter((thing) => !isBlankNodeId(thing.url) || options.acceptBlankNodes);
}
/**
* Insert a [[Thing]] into a [[SolidDataset]], replacing previous instances of that Thing.
*
* @param solidDataset The SolidDataset to insert a Thing into.
* @param thing The Thing to insert into the given SolidDataset.
* @returns A new SolidDataset equal to the given SolidDataset, but with the given Thing.
*/
function setThing(solidDataset, thing) {
var _a;
const thingIri = isThingLocal(thing) && hasServerResourceInfo(solidDataset)
? resolveLocalIri(getLocalNodeName(thing.url), getSourceUrl(solidDataset))
: thing.url;
const defaultGraph = solidDataset.graphs.default;
const updatedDefaultGraph = freeze({
...defaultGraph,
[thingIri]: freeze({ ...thing, url: thingIri }),
});
const updatedGraphs = freeze({
...solidDataset.graphs,
default: updatedDefaultGraph,
});
const subjectNode = DataFactory.namedNode(thingIri);
const deletedThingPredicates = (_a = solidDataset.graphs.default[thingIri]) === null || _a === void 0 ? void 0 : _a.predicates;
const deletions = typeof deletedThingPredicates !== "undefined"
? subjectToRdfJsQuads(deletedThingPredicates, subjectNode, DataFactory.defaultGraph())
: [];
const additions = subjectToRdfJsQuads(thing.predicates, subjectNode, DataFactory.defaultGraph());
return internal_addAdditionsToChangeLog(internal_addDeletionsToChangeLog(freeze({
...solidDataset,
graphs: updatedGraphs,
}), deletions), additions);
}
/**
* Remove a Thing from a SolidDataset.
*
* @param solidDataset The SolidDataset to remove a Thing from.
* @param thing The Thing to remove from `solidDataset`.
* @returns A new [[SolidDataset]] equal to the input SolidDataset, excluding the given Thing.
*/
function removeThing(solidDataset, thing) {
var _a;
let thingIri;
if (isNamedNode(thing)) {
thingIri = thing.value;
}
else if (typeof thing === "string") {
thingIri =
isLocalNodeIri(thing) && hasServerResourceInfo(solidDataset)
? resolveLocalIri(getLocalNodeName(thing), getSourceUrl(solidDataset))
: thing;
}
else if (isThingLocal(thing)) {
thingIri = thing.url;
}
else {
thingIri = asIri(thing);
}
const defaultGraph = solidDataset.graphs.default;
const updatedDefaultGraph = { ...defaultGraph };
delete updatedDefaultGraph[thingIri];
const updatedGraphs = freeze({
...solidDataset.graphs,
default: freeze(updatedDefaultGraph),
});
const subjectNode = DataFactory.namedNode(thingIri);
const deletedThingPredicates = (_a = solidDataset.graphs.default[thingIri]) === null || _a === void 0 ? void 0 : _a.predicates;
const deletions = typeof deletedThingPredicates !== "undefined"
? subjectToRdfJsQuads(deletedThingPredicates, subjectNode, DataFactory.defaultGraph())
: [];
return internal_addDeletionsToChangeLog(freeze({
...solidDataset,
graphs: updatedGraphs,
}), deletions);
}
function createThing(options = {}) {
var _a;
if (typeof options.url !== "undefined") {
const { url } = options;
if (!internal_isValidUrl(url)) {
throw new ValidThingUrlExpectedError(url);
}
const thing = freeze({
type: "Subject",
predicates: freeze({}),
url,
});
return thing;
}
const name = (_a = options.name) !== null && _a !== void 0 ? _a : generateName();
const localNodeIri = getLocalNodeIri(name);
const thing = freeze({
type: "Subject",
predicates: freeze({}),
url: localNodeIri,
});
return thing;
}
/**
* @param input An value that might be a [[Thing]].
* @returns Whether `input` is a Thing.
* @since 0.2.0
*/
function isThing(input) {
return (typeof input === "object" &&
input !== null &&
typeof input.type === "string" &&
input.type === "Subject");
}
function asUrl(thing, baseUrl) {
if (isThingLocal(thing)) {
if (typeof baseUrl === "undefined") {
throw new Error("The URL of a Thing that has not been persisted cannot be determined without a base URL.");
}
return resolveLocalIri(getLocalNodeName(thing.url), baseUrl);
}
return thing.url;
}
/** @hidden Alias of [[asUrl]] for those who prefer IRI terminology. */
const asIri = asUrl;
/**
* Gets a human-readable representation of the given Thing to aid debugging.
*
* Note that changes to the exact format of the return value are not considered a breaking change;
* it is intended to aid in debugging, not as a serialisation method that can be reliably parsed.
*
* @param thing The Thing to get a human-readable representation of.
* @since 0.3.0
*/
function thingAsMarkdown(thing) {
let thingAsMarkdown = "";
if (isThingLocal(thing)) {
thingAsMarkdown += `## Thing (no URL yet — identifier: \`#${getLocalNodeName(thing.url)}\`)\n`;
}
else {
thingAsMarkdown += `## Thing: ${thing.url}\n`;
}
const predicateIris = Object.keys(thing.predicates);
if (predicateIris.length === 0) {
thingAsMarkdown += "\n<empty>\n";
}
else {
for (const predicate of predicateIris) {
thingAsMarkdown += `\nProperty: ${predicate}\n`;
const values = getTermAll(thing, predicate);
thingAsMarkdown += values.reduce((acc, value) => {
return `${acc}- ${internal_getReadableValue(value)}\n`;
}, "");
}
}
return thingAsMarkdown;
}
/**
* @param thing The [[Thing]] of which a URL might or might not be known.
* @return `true` if `thing` has no known URL yet.
* @since 1.7.0
*/
function isThingLocal(thing) {
return isLocalNodeIri(thing.url);
}
/**
* This error is thrown when a function expected to receive a [[Thing]] but received something else.
* @since 1.2.0
*/
class ThingExpectedError extends SolidClientError {
constructor(receivedValue) {
const message = `Expected a Thing, but received: [${receivedValue}].`;
super(message);
this.receivedValue = receivedValue;
}
}
/**
* This error is thrown when a function expected to receive a valid URL to identify a property but received something else.
*/
class ValidPropertyUrlExpectedError extends SolidClientError {
constructor(receivedValue) {
const value = isNamedNode(receivedValue)
? receivedValue.value
: receivedValue;
const message = `Expected a valid URL to identify a property, but received: [${value}].`;
super(message);
this.receivedProperty = value;
}
}
/**
* This error is thrown when a function expected to receive a valid URL value but received something else.
*/
class ValidValueUrlExpectedError extends SolidClientError {
constructor(receivedValue) {
const value = isNamedNode(receivedValue)
? receivedValue.value
: receivedValue;
const message = `Expected a valid URL value, but received: [${value}].`;
super(message);
this.receivedValue = value;
}
}
/**
* This error is thrown when a function expected to receive a valid URL to identify a [[Thing]] but received something else.
*/
class ValidThingUrlExpectedError extends SolidClientError {
constructor(receivedValue) {
const value = isNamedNode(receivedValue)
? receivedValue.value
: receivedValue;
const message = `Expected a valid URL to identify a Thing, but received: [${value}].`;
super(message);
this.receivedValue = value;
}
}
/**
* Generate a string that can be used as the unique identifier for a Thing
*
* This function works by starting with a date string (so that Things can be
* sorted chronologically), followed by a random number generated by taking a
* random number between 0 and 1, and cutting off the `0.`.
*
* @internal
* @returns An string that's likely to be unique
*/
const generateName = () => {
return v4();
};
export { ThingExpectedError, ValidPropertyUrlExpectedError, ValidThingUrlExpectedError, ValidValueUrlExpectedError, asIri, asUrl, createThing, getThing, getThingAll, isThing, isThingLocal, removeThing, setThing, thingAsMarkdown };